Variable Font Hover Effect
April 2, 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>
@font-face {
src: url('<?php echo VFW_PLUGIN_URL ?>fonts/NeueMetana-Thin.woff');
font-family:'Neue Metana';
font-style: normal;
}
.hero-content {
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;
line-height: 1.1;
user-select: none;
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() {
var widget = document.getElementById("<?php echo $effect2_id; ?>");
if (!widget) return;
title = widget.querySelector("h1");
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>
—
### ✅ **Steps to fix:**
1. **Wrap the animated text in a container** with a hover listener.
2. **Only run the animation/render logic when the mouse is hovering** over that container.
3. Use a `hovered` flag to control the rendering.
4. Optionally add `mouseenter` and `mouseleave` events.
—
### ✅ **Apply these changes to both scripts (`effect-one` and `effect-two`)**.
—
## 🔧 Fix for `Effect One` Script (in `render()` block for `effect-one`)
Replace this portion:
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();
**With this version:**
let hovered = false;
titles.forEach(function(el) {
el.addEventListener("mouseenter", () => hovered = true);
el.addEventListener("mouseleave", () => hovered = false);
});
function animateAll() {
mouse.x += (cursor.x - mouse.x) / 20;
mouse.y += (cursor.y - mouse.y) / 20;
if (hovered) {
fontObjects.forEach(function(font) {
font.render();
});
}
requestAnimationFrame(animateAll);
}
animateAll();
—
## 🔧 Fix for `Effect Two` Script (inside `jQuery(document).ready(…)`)
Inside the `VFont` class in the `this.animate = function()` method, **replace:**
this.animate = function() {
mouse.x += (cursor.x - mouse.x) / 4;
mouse.y += (cursor.y - mouse.y) / 4;
requestAnimationFrame(this.animate.bind(this));
this.render();
}
**With this:**
let hovered = false;
this.animate = function() {
mouse.x += (cursor.x - mouse.x) / 4;
mouse.y += (cursor.y - mouse.y) / 4;
if (hovered) {
this.render();
}
requestAnimationFrame(this.animate.bind(this));
}
// Add hover detection
if (title) {
title.addEventListener("mouseenter", () => hovered = true);
title.addEventListener("mouseleave", () => hovered = false);
}
—
### ✅ Optional CSS tweak (add pointer cursor):
.variable-font-content {
cursor: pointer;
}
—
Let me know if you'd also like to toggle effects by click or tap for mobile users.
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() {
var widget = document.getElementById("");
if (!widget) return;
title = widget.querySelector("h1");
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();
});
for second effect animation only on text foucs not work
– Add a `hovered` flag.
– Update the `VFont` class to listen to `mouseenter` and `mouseleave` events on the `title` element (the `h1` tag).
– Skip calling `this.render()` if not hovered.
Here’s your **updated and fixed script** with that logic added:
—
### ✅ **Modified JavaScript for Effect Two (`VFont`) to trigger animation only on hover:**
Replace this whole block:
this.animate = function() {
mouse.x += (cursor.x - mouse.x) / 4;
mouse.y += (cursor.y - mouse.y) / 4;
requestAnimationFrame(this.animate.bind(this));
this.render();
}
**With:**
let hovered = false;
this.animate = function() {
mouse.x += (cursor.x - mouse.x) / 4;
mouse.y += (cursor.y - mouse.y) / 4;
if (hovered) {
this.render();
}
requestAnimationFrame(this.animate.bind(this));
}
if (title) {
title.addEventListener("mouseenter", () => hovered = true);
title.addEventListener("mouseleave", () => hovered = false);
}
—
### ✅ Complete `VFont` class with hover-based animation:
If you’d prefer a quick copy-paste version of the full `VFont` class with the hover fix added, here it is:
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 = [];
let hovered = false;
this.init = function() {
var widget = document.getElementById("<?php echo $effect2_id; ?>");
if (!widget) return;
title = widget.querySelector("h1");
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();
// Hover event
if (title) {
title.addEventListener("mouseenter", () => hovered = true);
title.addEventListener("mouseleave", () => hovered = false);
}
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 = "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;
if (hovered) {
this.render();
}
requestAnimationFrame(this.animate.bind(this));
}
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;
}
—
Now, the animation will only apply **when the user hovers over the text** (the `h1` element).
Let me know if you'd also like this to work on tap or focus for mobile accessibility!
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() {
var widget = document.getElementById("");
if (!widget) return;
title = widget.querySelector("h1");
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();
});
add in above code
– A `hovered` flag is introduced.
– `mouseenter` and `mouseleave` events are used to toggle animation.
– `render()` only runs when hovered.
—
### ✅ **Updated Full Code With Hover Animation Only**
Replace your entire `VFont` function with this updated version:
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 = [];
var hovered = false;
this.init = function() {
var widget = document.getElementById("<?php echo $effect2_id; ?>");
if (!widget) return;
title = widget.querySelector("h1");
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();
// Add hover event listeners
title.addEventListener("mouseenter", () => hovered = true);
title.addEventListener("mouseleave", () => hovered = false);
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 = "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;
if (hovered) {
this.render();
}
requestAnimationFrame(this.animate.bind(this));
}
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;
}
—
### ✅ Final Step: Use this instance line after the class:
var txt = new VFont();
—
This will ensure the animation only kicks in **while the user hovers on the heading**, keeping performance optimized and the effect more deliberate.
Let me know if you want this behavior extended for touch devices too (e.g. on tap/focus).
When first time hover text blink some times
The **text blinking on first hover** is a common issue — it usually happens because of layout shifts when:
1. Characters are replaced with “ elements dynamically.
2. The browser recalculates the layout and causes a flash or reflow.
3. The font-variation or font-size settings are applied too late (after the DOM is rendered).
—
### ✅ **Fix Strategy**
We can prevent the blinking by:
– **Hiding the text during processing**, then **fading it in** smoothly after `Char()` spans are inserted and `style` is applied.
– This avoids the browser showing unstyled or partially styled text.
– Also ensure `opacity` and `transition` are applied correctly.
—
### ✅ **Steps to Fix**
#### 1. Add this to your CSS (“ block or inline in JS):
.variable-font-content {
opacity: 0;
transition: opacity 0.3s ease-in-out;
visibility: hidden;
}
.variable-font-content.visible {
opacity: 1;
visibility: visible;
}
#### 2. In your `VFont` class, update `this.init()` to apply `visible` class only after rendering is ready:
Find this part inside `this.init()`:
str = title.innerText;
title.innerHTML = "";
Then modify **below that** like this:
str = title.innerText;
title.innerHTML = "";
title.style.visibility = "hidden"; // hide during setup
Then at the **end of `this.init()`**, after everything is built, add this line:
title.classList.add("visible");
So `this.init()` becomes something like:
this.init = function() {
var widget = document.getElementById("<?php echo $effect2_id; ?>");
if (!widget) return;
title = widget.querySelector("h1");
str = title.innerText;
title.innerHTML = "";
title.style.visibility = "hidden"; // hide initially
for (var i = 0; i < str.length; i++) {
var _char = new Char(title, str[i]);
chars.push(_char);
}
this.set();
// Hover listeners
title.addEventListener("mouseenter", () => hovered = true);
title.addEventListener("mouseleave", () => hovered = false);
window.addEventListener("resize", this.setSize.bind(this));
// Reveal now that everything is ready
setTimeout(() => {
title.classList.add("visible");
}, 10);
}
—
### ✅ Result
Now:
– The text is hidden during the transition/setup.
– When `Char()` spans are ready, the entire heading fades in smoothly.
– The initial blink or "layout flash" won't happen anymore.
Let me know if you’d like to enhance this with a loading animation or fade-in from bottom etc.
<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
let hovered = false;
titles.forEach(function(el) {
el.addEventListener("mouseenter", () => hovered = true);
el.addEventListener("mouseleave", () => hovered = false);
});
function animateAll() {
mouse.x += (cursor.x - mouse.x) / 20;
mouse.y += (cursor.y - mouse.y) / 20;
if (hovered) {
fontObjects.forEach(function(font) {
font.render();
});
}
requestAnimationFrame(animateAll);
}
animateAll();
</script>
<style>
@font-face {
src: url('<?php echo VFW_PLUGIN_URL ?>fonts/NeueMetana-Thin.woff');
font-family:'Neue Metana';
font-style: normal;
}
.hero-content {
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;
line-height: 1.1;
user-select: none;
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 = [];
var hovered = false;
this.init = function() {
var widget = document.getElementById("<?php echo $effect2_id; ?>");
if (!widget) return;
title = widget.querySelector("h1");
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();
// Add hover event listeners
title.addEventListener("mouseenter", () => hovered = true);
title.addEventListener("mouseleave", () => hovered = false);
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 = "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;
if (hovered) {
this.render();
}
requestAnimationFrame(this.animate.bind(this));
}
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>
"updates": [
{
"pattern": ".*",
"replacement": "n
<style>
n .variable-font-content {n opacity: 0;n visibility: hidden;n transition: opacity 0.3s ease-in-out;n }n .variable-font-content.visible {n opacity: 1;n visibility: visible;n }n
</style>
n
"name": "elementor_variable_font_widget",
"type": "code/php",
"content": "n
<style>
n .variable-font-content {n opacity: 0;n visibility: hidden;n transition: opacity 0.3s ease-in-out;n }n .variable-font-content.visible {n opacity: 1;n visibility: visible;n }n
</style>
n
"updates": [
{
"pattern": ".*",
"replacement": "n
<style>
n .variable-font-content {n opacity: 0;n visibility: hidden;n transition: opacity 0.3s ease-in-out;n }n .variable-font-content.visible {n opacity: 1;n visibility: visible;n }n
</style>
n
– CSS classes (`.variable-font-content` and `.visible`) to hide the text during setup.
– A smooth fade-in after initialization using JavaScript.
Let me know if you want this animation to also support tap/focus on mobile or have a fade-up effect as well!
<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
let hovered = false;
titles.forEach(function(el) {
el.addEventListener("mouseenter", () => hovered = true);
el.addEventListener("mouseleave", () => hovered = false);
});
function animateAll() {
mouse.x += (cursor.x - mouse.x) / 20;
mouse.y += (cursor.y - mouse.y) / 20;
if (hovered) {
fontObjects.forEach(function(font) {
font.render();
});
}
requestAnimationFrame(animateAll);
}
animateAll();
</script>
<style>
@font-face {
src: url('<?php echo VFW_PLUGIN_URL ?>fonts/NeueMetana-Thin.woff');
font-family:'Neue Metana';
font-style: normal;
}
.hero-content {
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:'Neue Metana';
text-rendering: optimizeSpeed;
color: #000;
line-height: 1.1;
user-select: none;
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 = [];
var hovered = false;
this.init = function() {
var widget = document.getElementById("<?php echo $effect2_id; ?>");
if (!widget) return;
title = widget.querySelector("h1");
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();
// Add hover event listeners
title.addEventListener("mouseenter", () => hovered = true);
title.addEventListener("mouseleave", () => hovered = false);
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 = "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;
if (hovered) {
this.render();
}
requestAnimationFrame(this.animate.bind(this));
}
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>
"updates": [
{
"pattern": ".*",
"replacement": "n
<style>
n .variable-font-content {n opacity: 0;n visibility: hidden;n transition: opacity 0.3s ease-in-out;n }n .variable-font-content.visible {n opacity: 1;n visibility: visible;n }n
</style>
n
– Adding `.variable-font-content` with `opacity: 0` and `visibility: hidden`.
– Introducing a `.visible` class with a smooth transition to `opacity: 1` and `visibility: visible`.
Make sure you apply the `visible` class in your JavaScript after the spans are fully initialized, like this:
title.classList.add("visible");
Let me know if you want me to update the JavaScript part directly for you as well.
"updates": [
{
"pattern": ".*",
"replacement": "
<style>
n @font-face {n src: url("https://res.cloudinary.com/dr6lvwubh/raw/upload/v1529908256/CompressaPRO-GX.woff2");n font-family: "Compressa VF";n font-style: normal;n }n .variable-font-content {n opacity: 0;n visibility: hidden;n transition: opacity 0.3s ease-in-out;n }n .variable-font-content.visible {n opacity: 1;n visibility: visible;n }n
</style>
';nn echo 'n n ' . esc_html($text) . 'n n ';nn echo '
<script>
n (function($) {n $(document).ready(function () {n var maxDist;n var mouse = { x: 0, y: 0 };n var cursor = { x: window.innerWidth, y: window.innerHeight };nn Math.dist = function(a, b) {n var dx = b.x - a.x;n var dy = b.y - a.y;n return Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));n }nn window.addEventListener("mousemove", function(e) {n cursor.x = e.clientX;n cursor.y = e.clientY;n });nn window.addEventListener("touchmove", function(e) {n var t = e.touches[0];n cursor.x = t.clientX;n cursor.y = t.clientY;n }, { passive: false });nn var Char = function(container, char) {n var span = document.createElement("span");n span.setAttribute("data-char", char);n span.innerText = char;n container.appendChild(span);n this.getDist = function() {n this.pos = span.getBoundingClientRect();n return Math.dist(mouse, {n x: this.pos.x + (this.pos.width / 1.75),n y: this.pos.yn });n }n this.getAttr = function(dist, min, max) {n var wght = max - Math.abs((max * dist / maxDist));n return Math.max(min, wght + min);n }n this.update = function(args) {n var dist = this.getDist();n this.wdth = args.wdth ? ~~this.getAttr(dist, 5, 200) : 100;n this.wght = args.wght ? ~~this.getAttr(dist, 100, 800) : 400;n this.alpha = args.alpha ? this.getAttr(dist, 0, 1).toFixed(2) : 1;n this.ital = args.ital ? this.getAttr(dist, 0, 1).toFixed(2) : 0;n this.draw();n }n this.draw = function() {n var style = "";n style += "opacity: " + this.alpha + ";";n style += "font-variation-settings: 'wght' " + this.wght + ", 'wdth' " + this.wdth + ", 'ital' " + this.ital + ";";n span.style = style;n }n return this;n }nn var VFont = function() {n this.scale = false;n this.flex = false;n this.alpha = false;n this.stroke = false;n this.width = true;n this.weight = true;n this.italic = false;n var title, str, chars = [];n var hovered = false;nn this.init = function() {n var widget = document.getElementById("' . esc_js($effect_id) . '");n if (!widget) return;nn title = widget.querySelector("h1");n str = title.innerText;n title.innerHTML = "";nn for (var i = 0; i < str.length; i++) {n var _char = new Char(title, str[i]);n chars.push(_char);n }nn this.set();nn title.addEventListener("mouseenter", () => hovered = true);n title.addEventListener("mouseleave", () => hovered = false);nn window.addEventListener("resize", this.setSize.bind(this));nn setTimeout(() => {n title.classList.add("visible");n }, 10);n }nn this.set = function() {n title.className += this.flex ? " flex variable-font-content" : " variable-font-content";n title.className += this.stroke ? " stroke" : "";n this.setSize();n }nn this.setSize = function() {n var fontSize = window.innerWidth / (str.length / 2.7);n title.style = "font-size: " + fontSize + "px;";n if (this.scale) {n var scaleY = (window.innerHeight / title.getBoundingClientRect().height).toFixed(2);n var lineHeight = scaleY * 0.8;n title.style = "font-size: " + fontSize + "px; transform: scale(1," + scaleY + "); line-height: " + lineHeight + "em;";n }n }nn this.animate = function() {n mouse.x += (cursor.x - mouse.x) / 4;n mouse.y += (cursor.y - mouse.y) / 4;n if (hovered) {n this.render();n }n requestAnimationFrame(this.animate.bind(this));n }nn this.render = function() {n maxDist = title.getBoundingClientRect().width / 2;n for (var i = 0; i < chars.length; i++) {n chars[i].update({n wght: this.weight,n wdth: this.width,n ital: this.italic,n alpha: this.alphan });n }n }nn this.init();n this.animate();n return this;n }nn var txt = new VFont();n });n })(jQuery);n
</script>
';n }n}"
}
]
}
– It includes a `visible` class with transition effects to fix blinking on first hover.
– The text fades in smoothly after initialization.
– JavaScript now triggers animation only on hover.
Let me know if you’d like to add tap support for mobile or trigger on scroll into view!