Scope JS for Widgets
March 28, 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-container 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-container h1 span {
transform: translateY(-10px);
user-select: none;
font-family:'Compressa VF';
}
.fit-container h1.flex {
display: flex;
justify-content: space-between;
}
.fit-container h1.stroke span {
position: relative;
color: #211D26;
line-height: inherit;
}
.fit-container 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;
}
if(!is_admin())
{
?>
<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(dx * dx + dy * dy);
}
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(element) {
this.scale = false;
this.flex = true;
this.alpha = false;
this.stroke = false;
this.width = true;
this.weight = true;
this.italic = true;
var title = element;
var str = title.innerText;
var chars = [];
this.init = function() {
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 += this.flex ? " flex title variable-font-content " : "";
title.className += this.stroke ? " stroke" : "";
this.setSize();
}
this.setSize = function() {
var mainDiv = title.parentElement;
// var fontSize = mainDiv.clientWidth / (str.length / 2);
var fontSize = "<?php echo $font_size ?>";
title.style.fontSize = fontSize;
if (this.scale) {
var scaleY = (window.innerHeight / title.getBoundingClientRect().height).toFixed(2);
var lineHeight = scaleY * 0.8;
title.style.transform = "scale(1," + scaleY + ")";
title.style.lineHeight = 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();
return this;
}
// Initialize for all elements with class "title"
var titles = document.querySelectorAll(".title");
var fontObjects = [];
titles.forEach(function(el) {
var txt = new VFont(el);
fontObjects.push(txt);
// Set unique font size per element
var parent = el.closest('.fit-container');
if (parent && parent.dataset.fontSize) {
el.style.fontSize = parent.dataset.fontSize;
}
});
// Unified animation loop for all
function animateAll() {
mouse.x += (cursor.x - mouse.x) / 20;
mouse.y += (cursor.y - mouse.y) / 20;
requestAnimationFrame(animateAll);
fontObjects.forEach(function(font) {
font.render();
});
}
animateAll();
</script>
<style>
.hero-content {
padding: 300px 0 0 0;
position: relative;
height: 100%;
}
@media only screen and (min-width:768px) and
(max-width:1024px) and (orientation:portrait) {
.hero-content {
padding-top: 120px;
padding-bottom: 100px;
}
}
@media only screen and (min-width: 320px) and
(max-width: 768px) and (orientation: landscape) {
.hero-content {
padding-top: 120px;
padding-bottom: 100px;
}
}
@media only screen and (min-width: 320px) and
(max-width: 667px) and (orientation: portrait) {
.hero-content {
padding-top: 150px;
padding-bottom: 100px;
}
}
.hero-title h1 {
font-family:'Compressa VF';
text-rendering: optimizeSpeed;
color: #000;
user-select: none;
line-height: 100px;
margin: 0 auto;
text-transform: uppercase;
font-weight: 100;
text-align: left;
}
.hero-title h1 .line {
display: block;
}
@media only screen and (min-width:768px) and
(max-width:1024px) and (orientation:portrait) {
.hero-title h1 .line-space {
display: none;
}
.hero-title h1 {
font-size: 100px !important;
line-height: 90px;
}
}
@media only screen and (min-width: 320px) and
(max-width: 736px) and (orientation: landscape) {
.hero-title h1 .line-space {
display: none;
}
.hero-title h1 {
font-size: 70px !important;
line-height: 65px;
}
}
@media only screen and (min-width: 375px) and
(max-width: 420px) and (orientation: portrait) {
.hero-title h1 {
font-size: 50px !important;
line-height: 52px;
}
.hero-title h1 .line-space {
display: none;
}
}
@media (max-width: 375px) {
.hero-title h1 {
font-size: 35px !important;
line-height: 38px;
}
.hero-title h1 .line-space {
display: none;
}
}
.hero-title h1 span {
transform: translateY(-10px);
user-select: none;
}
.hero-title h1.flex {
display: flex;
justify-content: space-between;
}
.hero-title h1.stroke span {
position: relative;
color: #211D26;
line-height: inherit;
}
.hero-title h1.stroke span:after {
content: attr(data-char);
-webkit-text-stroke-width: 2px;
-webkit-text-stroke-color: #FE6730;
position: absolute;
left: 0;
line-height: inherit;
color: transparent;
z-index: -1;
}
</style>
<script>
(function($) {
$(document).ready(function () {
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 = false;
this.alpha = false;
this.stroke = false;
this.width = true;
this.weight = true;
this.italic = false;
var title, str, chars = [];
this.init = function() {
title = document.getElementById("titlefont2");
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 variable-font-content" : "";
this.setSize();
}
this.setSize = function() {
var fontSize = window.innerWidth / (str.length / 2.7);
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) / 4;
mouse.y += (cursor.y - mouse.y) / 4;
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();
});
})(jQuery);
</script>
—
### 🔧 The Root Issue:
Your script uses **global selectors like `.title` or `#titlefont2`** and **doesn’t scope animation or font variation logic to each widget instance**. This means both widgets are being animated together regardless of the user's choice.
—
### ✅ Solution Overview:
You need to **scope the JavaScript behavior per widget instance**, based on each widget’s unique ID or class.
—
### 🛠️ Suggested Fix:
1. **Pass a unique class or ID to each rendered widget.**
2. **Use that class/ID in JavaScript to initialize animation only on that specific instance.**
—
### ✅ Step-by-step Fix
#### 🧩 Step 1: Update Widget Output to Add Unique Class
In both `effect-one` and `effect-two` rendering blocks, add a unique wrapper class using the widget ID:
$widget_id = 'widget-' . $this->get_id();
Use that class in your HTML output:
<div id="<?php echo $widget_id; ?>" class="fit-container variable-font-widget <?php echo esc_attr($widget_id); ?>" data-font-size="<?php echo esc_attr($font_size); ?>">
Apply similar logic for `effect-two`.
—
#### 🧠 Step 2: Update JavaScript to Use Scoped Selectors
Replace global selectors like this:
var titles = document.querySelectorAll(".title");
With this:
document.querySelectorAll(".variable-font-widget").forEach(function(widget) {
var title = widget.querySelector(".title"); // or any internal heading
if (!title) return;
var vfont = new VFont(title); // Or whatever class you're using
fontObjects.push(vfont);
});
For `effect-two`, do the same with a scoped selector (instead of `#titlefont2`):
var widget = document.querySelector("#<?php echo $widget_id; ?>");
var title = widget.querySelector("h1.variable-font-content");
This ensures each widget handles its own animation independently.
—
### 🧼 Bonus Tips
– Avoid using hard-coded IDs like `#titlefont2`. Instead, dynamically assign and target them.
– Move your inline JS into an external JS file or enqueue it properly in WordPress. This improves maintainability and performance.
—
### 🚀 Result
With the changes above:
– You can safely place **multiple Variable Font widgets** on the same Elementor page.
– Each will **only animate its own content** based on the selected effect (`effect-one` or `effect-two`).
—
Would you like me to refactor your code accordingly with scoped JavaScript included?
<div class="hero-content" id="effect2<?php echo $this->get_id() ?>">
We can **fully scope your JS for `effect-two`** using this ID to avoid interference with other instances of the widget on the same page.
—
### ✅ Minimal Changes for Scoped JavaScript (Effect Two Only)
#### ✨ 1. Assign to a PHP variable:
$effect2_id = 'effect2' . $this->get_id();
Then use it like:
<div class="hero-content" id="<?php echo $effect2_id; ?>">
#### ✨ 2. Replace Global `#titlefont2` in JS
Right now, your JS is using:
title = document.getElementById("titlefont2");
Instead, **select the title inside your specific instance only**:
var widget = document.getElementById("<?php echo $effect2_id; ?>");
if (!widget) return;
title = widget.querySelector("h1.variable-font-content");
Also replace `title.innerHTML = ""` and all others with this new `title` variable.
—
### 🧠 Final JS Snippet (Scoped Version)
Inside your `effect-two` block, replace this part:
var txt = new VFont();
With this **scoped version**:
<script>
(function($) {
$(document).ready(function () {
var widget = document.getElementById("<?php echo $effect2_id; ?>");
if (!widget) return;
var title = widget.querySelector("h1.variable-font-content");
if (!title) return;
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(dx * dx + dy * dy);
}
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 = false;
this.alpha = false;
this.stroke = false;
this.width = true;
this.weight = true;
this.italic = false;
var str = title.innerText;
var chars = [];
this.init = function() {
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 += this.flex ? " flex variable-font-content" : "";
title.className += this.stroke ? " stroke variable-font-content" : "";
this.setSize();
}
this.setSize = function() {
var fontSize = window.innerWidth / (str.length / 2.7);
title.style.fontSize = fontSize + "px";
if (this.scale) {
var scaleY = (window.innerHeight / title.getBoundingClientRect().height).toFixed(2);
var lineHeight = scaleY * 0.8;
title.style.transform = "scale(1," + scaleY + ")";
title.style.lineHeight = lineHeight + "em";
}
}
this.animate = function() {
mouse.x += (cursor.x - mouse.x) / 4;
mouse.y += (cursor.y - mouse.y) / 4;
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;
}
new VFont();
});
})(jQuery);
</script>
—
This will isolate the animation **per widget**, so now multiple Variable Font widgets with different effects can live happily on the same page.
Let me know if you want the same done for `effect-one` too — I can refactor it with scoped logic in the same way.