Multiple Widget Instances

User
<style>
@font-face {
	src: url('https://res.cloudinary.com/dr6lvwubh/raw/upload/v1529908256/CompressaPRO-GX.woff2');
	font-family:'Compressa VF';
	font-style: normal;
}


#fit h1 {
	font-family:'Compressa VF';
	text-rendering: optimizeSpeed;
/*	width: 100%;*/
	user-select: none;
	line-height: 0.8em;
	margin: 0 auto;
	text-transform: uppercase;
	font-weight: 100;
	text-align: center;
/*	width: 100vw;*/
}
#fit h1 span {
	transform: translateY(-10px);
	user-select: none;
	font-family:'Compressa VF';

}
#fit h1.flex {
	display: flex;
	justify-content: space-between;
	
}
#fit h1.stroke span {
	position: relative;
	color: #211D26;
	line-height: inherit;
}
#fit h1.stroke span:after {
	content: attr(data-char);
	-webkit-text-stroke-width: 3px;
	-webkit-text-stroke-color: #FE6730;
	position: absolute;
	left: 0;
	line-height: inherit;
	color: transparent;
	z-index: -1;
}
</style>
<script>

</script>

';
self::$script_loaded = true;
}
?>

<script>
var maxDist;
var mouse = { x: 0, y: 0 };
var cursor = {
    x: window.innerWidth,
    y: window.innerHeight
};

Math.dist = function(a, b) {
    var dx = b.x - a.x;
    var dy = b.y - a.y;
    return Math.sqrt(Math.pow(dx, 2), Math.pow(dy, 2));
}

window.addEventListener("mousemove", function(e) {
    cursor.x = e.clientX;
    cursor.y = e.clientY;
});

window.addEventListener("touchmove", function(e) {
    var t = e.touches[0];
    cursor.x = t.clientX;
    cursor.y = t.clientY;
}, {
    passive: false
});

var Char = function(container, char) {
    var span = document.createElement("span");
    span.setAttribute('data-char', char);
    span.innerText = char;
    container.appendChild(span);
    this.getDist = function() {
        this.pos = span.getBoundingClientRect();
        return Math.dist(mouse, {
            x: this.pos.x + (this.pos.width / 1.75),
            y: this.pos.y
        });
    }
    this.getAttr = function(dist, min, max) {
        var wght = max - Math.abs((max * dist / maxDist));
        return Math.max(min, wght + min);
    }
    this.update = function(args) {
        var dist = this.getDist();
        this.wdth = args.wdth ? ~~this.getAttr(dist, 5, 200) : 100;
        this.wght = args.wght ? ~~this.getAttr(dist, 100, 800) : 400;
        this.alpha = args.alpha ? this.getAttr(dist, 0, 1).toFixed(2) : 1;
        this.ital = args.ital ? this.getAttr(dist, 0, 1).toFixed(2) : 0;
        this.draw();
    }
    this.draw = function() {
        var style = "";
        style += "opacity: " + this.alpha + ";";
        style += "font-variation-settings: 'wght' " + this.wght + ", 'wdth' " + this.wdth + ", 'ital' " + this.ital + ";";
        span.style = style;
    }
    return this;
}

var VFont = function() {
    this.scale = false;
    this.flex = true;
    this.alpha = false;
    this.stroke = false;
    this.width = true;
    this.weight = true;
    this.italic = true;
    var title, str, chars = [];

    this.init = function() {
        title = document.getElementById("title");
        str = title.innerText;
        title.innerHTML = "";
        for (var i = 0; i < str.length; i++) {
            var _char = new Char(title, str[i]);
            chars.push(_char);
        }
        this.set();
        window.addEventListener("resize", this.setSize.bind(this));
    }

    this.set = function() {
        title.className = "";
        title.className += this.flex ? " flex variable-font-content " : "";
        title.className += this.stroke ? " stroke" : "";
        this.setSize();
    }

    this.setSize = function() {
        var mainDiv = document.getElementById("fit"); // assuming the outer div has an id "main"
        var fontSize = mainDiv.clientWidth / (str.length / 2);
        title.style = "font-size: " + fontSize + "px;";
        if (this.scale) {
            var scaleY = (window.innerHeight / title.getBoundingClientRect().height).toFixed(2);
            var lineHeight = scaleY * 0.8;
            title.style = "font-size: " + fontSize + "px; transform: scale(1," + scaleY + "); line-height: " + lineHeight + "em;"
        }
    }

    this.animate = function() {
        mouse.x += (cursor.x - mouse.x) / 20;
        mouse.y += (cursor.y - mouse.y) / 20;
        requestAnimationFrame(this.animate.bind(this));
        this.render();
    }

    this.render = function() {
        maxDist = title.getBoundingClientRect().width / 2;
        for (var i = 0; i < chars.length; i++) {
            chars[i].update({
                wght: this.weight,
                wdth: this.width,
                ital: this.italic,
                alpha: this.alpha
            });
        }
    }
    this.init();
    this.animate();
    return this;
}

var txt = new VFont();

// Start animation
txt.animate();
</script>
Assistant
To make your custom Elementor widget support **multiple instances on the same page**, you'll need to ensure:

1. **Unique IDs for HTML elements per instance**.
2. **Per-instance JavaScript scoping**, so that each widget operates independently.

Here’s how you can adjust your widget to support multiple instances:

### ✅ 1. Modify `render()` to use a unique ID:

protected function render() {
    $settings = $this->get_settings_for_display();
    $text = $settings['content'];
    $effect = $settings['font_effect'];

    global $post;
    $current_page_id = is_object($post) ? $post->ID : 0;
    $widget_id = $this->get_id();

    if ($effect === 'effect-one') {
        $unique_id = 'variable-font-' . $widget_id . '-page-' . $current_page_id;

        echo "<div id='{$unique_id}-container' class='variable-font-wrapper' data-id='{$unique_id}'>";
        echo "<h1 id='{$unique_id}-title' class='variable-font-content'>$text</h1>";
        echo "</div>";
    }

    if ( ! self::$script_loaded ) {
        echo '<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.5/dat.gui.min.js"></script>';
        self::$script_loaded = true;
    }

    // Include styles once (optional: move this to enqueue system)
    ?>
    <style>
        @font-face {
            font-family: 'Compressa VF';
            font-style: normal;
            src: url('https://res.cloudinary.com/dr6lvwubh/raw/upload/v1529908256/CompressaPRO-GX.woff2');
        }
        .variable-font-content {
            font-family: 'Compressa VF';
            text-rendering: optimizeSpeed;
            user-select: none;
            line-height: 0.8em;
            margin: 0 auto;
            text-transform: uppercase;
            font-weight: 100;
            text-align: center;
        }
        .variable-font-content span {
            transform: translateY(-10px);
            user-select: none;
        }
        .variable-font-content.flex {
            display: flex;
            justify-content: space-between;
        }
        .variable-font-content.stroke span {
            position: relative;
            color: #211D26;
            line-height: inherit;
        }
        .variable-font-content.stroke span:after {
            content: attr(data-char);
            -webkit-text-stroke-width: 3px;
            -webkit-text-stroke-color: #FE6730;
            position: absolute;
            left: 0;
            line-height: inherit;
            color: transparent;
            z-index: -1;
        }
    </style>

    <script>
        (function(){
            const containerId = "<?php echo esc_js($unique_id); ?>";
            const titleId = containerId + '-title';
            const container = document.getElementById(containerId + '-container');
            const title = document.getElementById(titleId);
            if (!container || !title) return;

            const cursor = { x: window.innerWidth, y: window.innerHeight };
            const mouse = { x: 0, y: 0 };

            window.addEventListener("mousemove", (e) => {
                cursor.x = e.clientX;
                cursor.y = e.clientY;
            });

            window.addEventListener("touchmove", function(e) {
                const t = e.touches[0];
                cursor.x = t.clientX;
                cursor.y = t.clientY;
            }, { passive: false });

            function dist(a, b) {
                const dx = b.x - a.x;
                const dy = b.y - a.y;
                return Math.sqrt(dx * dx + dy * dy);
            }

            function Char(span, char) {
                span.setAttribute('data-char', char);
                span.innerText = char;

                return {
                    getDist: () => {
                        const pos = span.getBoundingClientRect();
                        return dist(mouse, {
                            x: pos.x + pos.width / 1.75,
                            y: pos.y
                        });
                    },
                    update: (args, maxDist) => {
                        const d = Math.min(maxDist, this.getDist());
                        const attr = (min, max) => Math.max(min, max - Math.abs((max * d / maxDist)) + min);
                        const style = `
                            opacity: ${args.alpha ? attr(0, 1).toFixed(2) : 1};
                            font-variation-settings: 
                                'wght' ${args.wght ? ~~attr(100, 800) : 400}, 
                                'wdth' ${args.wdth ? ~~attr(5, 200) : 100}, 
                                'ital' ${args.ital ? attr(0, 1).toFixed(2) : 0};
                        `;
                        span.style = style;
                    },
                    span
                };
            }

            function VFont() {
                const chars = [];
                const settings = {
                    alpha: false,
                    stroke: false,
                    flex: true,
                    width: true,
                    weight: true,
                    italic: true,
                    scale: false
                };

                const text = title.innerText;
                title.innerHTML = '';
                for (let i = 0; i < text.length; i++) {
                    const span = document.createElement('span');
                    const char = Char(span, text[i]);
                    title.appendChild(span);
                    chars.push(char);
                }

                const applyStyles = () => {
                    title.className = 'variable-font-content';
                    if (settings.flex) title.classList.add('flex');
                    if (settings.stroke) title.classList.add('stroke');
                }

                const resize = () => {
                    const size = container.clientWidth / (text.length / 2);
                    title.style.fontSize = `${size}px`;
                }

                const animate = () => {
                    mouse.x += (cursor.x - mouse.x) / 20;
                    mouse.y += (cursor.y - mouse.y) / 20;
                    requestAnimationFrame(animate);
                    const maxDist = title.getBoundingClientRect().width / 2;
                    chars.forEach(char => char.update(settings, maxDist));
                }

                applyStyles();
                resize();
                window.addEventListener('resize', resize);
                animate();
            }

            VFont();
        })();
    </script>
    <?php
}

### ✅ Key Improvements:
– Each widget instance uses a **unique ID** (like `variable-font-39-page-5`).
– The script is wrapped in an **IIFE** (`(function(){ … })()`) to isolate scope per widget.
– Styles and scripts will only apply to the instance being rendered.

### ✅ Optional: Enqueue font and JS globally

Instead of embedding fonts/scripts inline, use `enqueue_scripts()` in your widget or plugin to load the font and `dat.gui` only once.

Let me know if you'd like help moving the styles and fonts into separate enqueued files too.

User
$output = "";
$output .= "$text";
$output .= "";
echo $output;
make this for above
Assistant
To make the code snippet you provided work for **multiple Elementor widget instances on the same page**, you need to:

1. Assign **unique IDs** to the container and the title.
2. Ensure the **class names don’t conflict** across instances.

Here’s the corrected and enhanced version of your code snippet:

$unique_id = 'variable-font-' . $this->get_id() . '-page-' . $current_page_id;

$output  = "<div id='{$unique_id}-container' class='variable-font-wrapper' data-page-id='{$current_page_id}'>";
$output .= "<h1 id='{$unique_id}-title' class='variable-font-content'>{$text}</h1>";
$output .= "</div>";

echo $output;

### ✅ What’s changed:
– `id='fit'` → `id='{$unique_id}-container'` — makes it unique per widget.
– `id='title'` → `id='{$unique_id}-title'` — also unique per widget.
– Added a wrapper class `variable-font-wrapper` (optional) for easier CSS targeting.

> Be sure your JS uses these same dynamic IDs when selecting and manipulating the DOM.

Let me know if you want help dynamically adapting your JS to handle multiple of these widgets on a single page.

User
animation stopeed work
Assistant
Thanks! Since you changed the IDs to unique ones (`{$unique_id}-title` etc.), your JS now **can’t find the old static IDs (`#fit`, `#title`)**, which breaks the animation.

Let’s fix it so the JavaScript dynamically targets **each instance** of the widget using their unique IDs.

### ✅ Step-by-step Fix

Update your **JavaScript block** in `render()` to this:

<script>
(function(){
    const uniqueId = "<?php echo esc_js($unique_id); ?>";
    const container = document.getElementById(uniqueId + "-container");
    const title = document.getElementById(uniqueId + "-title");

    if (!container || !title) return;

    const cursor = { x: window.innerWidth, y: window.innerHeight };
    const mouse = { x: 0, y: 0 };

    window.addEventListener("mousemove", (e) => {
        cursor.x = e.clientX;
        cursor.y = e.clientY;
    });

    window.addEventListener("touchmove", (e) => {
        const t = e.touches[0];
        cursor.x = t.clientX;
        cursor.y = t.clientY;
    }, { passive: false });

    function dist(a, b) {
        const dx = b.x - a.x;
        const dy = b.y - a.y;
        return Math.sqrt(dx * dx + dy * dy);
    }

    function Char(span, char) {
        span.setAttribute('data-char', char);
        span.innerText = char;

        return {
            span: span,
            getDist: () => {
                const pos = span.getBoundingClientRect();
                return dist(mouse, {
                    x: pos.x + pos.width / 1.75,
                    y: pos.y
                });
            },
            update: (args, maxDist) => {
                const d = Math.min(maxDist, this.getDist());
                const attr = (min, max) => Math.max(min, max - Math.abs((max * d / maxDist)) + min);
                const style = `
                    opacity: ${args.alpha ? attr(0, 1).toFixed(2) : 1};
                    font-variation-settings:
                        'wght' ${args.wght ? ~~attr(100, 800) : 400},
                        'wdth' ${args.wdth ? ~~attr(5, 200) : 100},
                        'ital' ${args.ital ? attr(0, 1).toFixed(2) : 0};
                `;
                span.style = style;
            }
        };
    }

    function VFont(container, title) {
        const chars = [];
        const settings = {
            alpha: false,
            stroke: false,
            flex: true,
            width: true,
            weight: true,
            italic: true,
            scale: false
        };

        const text = title.innerText;
        title.innerHTML = '';
        for (let i = 0; i < text.length; i++) {
            const span = document.createElement('span');
            const char = Char(span, text[i]);
            title.appendChild(span);
            chars.push(char);
        }

        function applyStyles() {
            title.className = 'variable-font-content';
            if (settings.flex) title.classList.add('flex');
            if (settings.stroke) title.classList.add('stroke');
        }

        function resize() {
            const size = container.clientWidth / (text.length / 2);
            title.style.fontSize = `${size}px`;
        }

        function animate() {
            mouse.x += (cursor.x - mouse.x) / 20;
            mouse.y += (cursor.y - mouse.y) / 20;
            requestAnimationFrame(animate);
            const maxDist = title.getBoundingClientRect().width / 2;
            chars.forEach(char => char.update(settings, maxDist));
        }

        applyStyles();
        resize();
        window.addEventListener("resize", resize);
        animate();
    }

    VFont(container, title);
})();
</script>

### ✅ Summary of Fixes

– Dynamically pulls `uniqueId` for current widget instance.
– Uses `document.getElementById(uniqueId + "-title")` and `-container`.
– Wraps each widget’s logic in a scoped IIFE so it doesn't conflict with other instances.
– Restores the animation and sizing logic per instance.

Let me know if you'd also like to **enable effect-two** support, or **cleanly separate the CSS/JS into external files**.

User
okay
#fit h1 {
font-family:'Compressa VF';
text-rendering: optimizeSpeed;
/* width: 100%;*/
user-select: none;
line-height: 0.8em;
margin: 0 auto;
text-transform: uppercase;
font-weight: 100;
text-align: center;
/* width: 100vw;*/
}
#fit h1 span {
transform: translateY(-10px);
user-select: none;
font-family:'Compressa VF';

}
#fit h1.flex {
display: flex;
justify-content: space-between;

}
#fit h1.stroke span {
position: relative;
color: #211D26;
line-height: inherit;
}
#fit h1.stroke span:after {
content: attr(data-char);
-webkit-text-stroke-width: 3px;
-webkit-text-stroke-color: #FE6730;
position: absolute;
left: 0;
line-height: inherit;
color: transparent;
z-index: -1;
}
now adjust above

Assistant
Great! Since you're now using **unique IDs per widget instance**, you'll want to **replace all the `#fit h1` selectors** in your CSS with a **class-based approach** so it applies correctly to **each instance**.

Let’s refactor your CSS using a `.variable-font-wrapper` and `.variable-font-content` structure (which you've already added in the updated markup).

### ✅ Updated CSS (Scoped for multiple instances)

.variable-font-wrapper h1 {
	font-family: 'Compressa VF';
	text-rendering: optimizeSpeed;
	user-select: none;
	line-height: 0.8em;
	margin: 0 auto;
	text-transform: uppercase;
	font-weight: 100;
	text-align: center;
}

.variable-font-wrapper h1 span {
	transform: translateY(-10px);
	user-select: none;
	font-family: 'Compressa VF';
}

.variable-font-wrapper h1.flex {
	display: flex;
	justify-content: space-between;
}

.variable-font-wrapper h1.stroke span {
	position: relative;
	color: #211D26;
	line-height: inherit;
}

.variable-font-wrapper h1.stroke span:after {
	content: attr(data-char);
	-webkit-text-stroke-width: 3px;
	-webkit-text-stroke-color: #FE6730;
	position: absolute;
	left: 0;
	line-height: inherit;
	color: transparent;
	z-index: -1;
}

### ✅ Why this works:
– `.variable-font-wrapper h1` replaces `#fit h1` so it applies to all widget instances.
– Uses **class-based selectors**, which can repeat safely across the page.
– The widget output already wraps each instance with `class='variable-font-wrapper'`, so it's correctly scoped.

Let me know if you'd like to move these styles into a separate CSS file and enqueue it properly, or if you're ready to build support for **`effect-two`** next.

Leave a Comment

Your email address will not be published. Required fields are marked *