Multiple Widget Instances
March 26, 2025 · AI Conversations
<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>
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!