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;
	color: #b6e925;
/*	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" : "";
        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 allow **multiple instances** of your Elementor widget (each with its own animation and style) on the **same page**, you need to **uniquely scope** your styles, IDs, and script variables so they don't clash.

Here’s how to **modify your code** to support **multiple widgets per page**:

### ✅ Key Fixes

1. **Avoid duplicated IDs**: Replace IDs (`#fit`, `#title`) with unique classes using the widget ID.
2. **Wrap scripts in closures/functions**: So variables like `mouse`, `txt`, etc., don't leak into global scope.
3. **Use `this->get_id()` to create unique classes and IDs**.

### ✅ Updated `render()` Method

Replace your `render()` method with the following updated version:

protected function render() {
    $settings = $this->get_settings_for_display();
    $text = esc_html($settings['content']);
    $effect = esc_attr($settings['font_effect']);
    $widget_id = $this->get_id(); // unique ID for this instance

    $container_id = "variable-font-container-{$widget_id}";
    $title_id = "variable-font-title-{$widget_id}";
    $class_scope = "variable-font-{$widget_id}";

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

        .<?= $class_scope ?> h1 {
            font-family: 'Compressa VF';
            text-rendering: optimizeSpeed;
            color: #b6e925;
            user-select: none;
            line-height: 0.8em;
            margin: 0 auto;
            text-transform: uppercase;
            font-weight: 100;
            text-align: center;
        }

        .<?= $class_scope ?> h1.flex {
            display: flex;
            justify-content: space-between;
        }

        .<?= $class_scope ?> h1 span {
            transform: translateY(-10px);
            user-select: none;
        }

        .<?= $class_scope ?> h1.stroke span {
            position: relative;
            color: #211D26;
        }

        .<?= $class_scope ?> h1.stroke span:after {
            content: attr(data-char);
            -webkit-text-stroke-width: 3px;
            -webkit-text-stroke-color: #FE6730;
            position: absolute;
            left: 0;
            color: transparent;
            z-index: -1;
        }
    </style>

    <div id="<?= $container_id ?>" class="<?= $class_scope ?>" data-effect="<?= $effect ?>">
        <h1 id="<?= $title_id ?>"><?= $text ?></h1>
    </div>

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

    <script>
    (function() {
        const container = document.getElementById("<?= $container_id ?>");
        const title = document.getElementById("<?= $title_id ?>");
        const chars = [];
        let mouse = { x: window.innerWidth, y: window.innerHeight };
        let cursor = { x: window.innerWidth, y: window.innerHeight };
        let maxDist;

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

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

        function Char(container, char) {
            const span = document.createElement("span");
            span.setAttribute("data-char", char);
            span.innerText = char;
            container.appendChild(span);

            return {
                getDist() {
                    const rect = span.getBoundingClientRect();
                    return Math.dist(mouse, {
                        x: rect.x + rect.width / 2,
                        y: rect.y
                    });
                },
                getAttr(dist, min, max) {
                    const wght = max - Math.abs((max * dist / maxDist));
                    return Math.max(min, wght + min);
                },
                update(args) {
                    const dist = this.getDist();
                    const wdth = args.wdth ? ~~this.getAttr(dist, 5, 200) : 100;
                    const wght = args.wght ? ~~this.getAttr(dist, 100, 800) : 400;
                    const alpha = args.alpha ? this.getAttr(dist, 0, 1).toFixed(2) : 1;
                    const ital = args.ital ? this.getAttr(dist, 0, 1).toFixed(2) : 0;

                    span.style.opacity = alpha;
                    span.style.fontVariationSettings = `'wght' ${wght}, 'wdth' ${wdth}, 'ital' ${ital}`;
                }
            };
        }

        const str = title.innerText;
        title.innerHTML = "";
        for (let i = 0; i < str.length; i++) {
            const _char = Char(title, str[i]);
            chars.push(_char);
        }

        title.classList.add("flex");
        maxDist = title.getBoundingClientRect().width / 2;

        function render() {
            for (let i = 0; i < chars.length; i++) {
                chars[i].update({
                    wght: true,
                    wdth: true,
                    ital: true,
                    alpha: false
                });
            }
        }

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

        function resize() {
            const fontSize = container.clientWidth / (str.length / 2);
            title.style.fontSize = fontSize + "px";
        }

        window.addEventListener("resize", resize);
        resize();
        animate();
    })();
    </script>
    <?php
}

### ✅ Result

Now you can drop multiple instances of this widget on the same page — and **each one works independently** with its own animation and style.

Let me know if you'd like to:
– Add more effects (`effect-two` etc.)
– Use external JS files instead of inline
– Add color gradient or font-size variations per effect

Happy building!

Leave a Comment

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