Elementor Variable Font Widget
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
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%;
}
.hero-title h1 {
font-family:'Neue Metana';
text-rendering: optimizeSpeed;
color: #000;
line-height: 0.8;
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>
—
### ✅ 1. **Check the HTML Structure**
Each character is wrapped in a “, so if you're inserting spaces (`" "`) between words, each space gets its own span too — and might be visually too wide.
### 🔧 Fix: Replace normal spaces with non-breaking space (` `) or manually control spacing
In the `render()` method where you output text:
$text = str_replace(' ', ' ', $settings['content']);
This ensures the space doesn't collapse or stretch more than expected.
—
### ✅ 2. **Tweak `display: flex` in `.fit-container h1.flex`**
This style:
.fit-container h1.flex {
display: flex;
justify-content: space-between;
}
is **distributing characters equally**, which creates **huge spacing** between words.
### 🔧 Fix: Use `inline-flex` or change layout logic
.fit-container h1.flex {
display: inline-flex;
justify-content: normal;
flex-wrap: wrap;
}
or better yet, remove `display: flex
### 🔧 Fix the Character Spacing (Effect One)
In your `Effect One` CSS, specifically this part:
.fit-container h1.flex {
display: flex;
justify-content: space-between;
}
The issue is likely from:
justify-content: space-between;
This distributes the characters *evenly across the entire width*, which is what’s causing the large spacing.
—
### ✅ What You Should Do:
Change this:
.fit-container h1.flex {
display: flex;
justify-content: space-between;
}
To this:
.fit-container h1.flex {
display: inline-flex;
gap: 0;
justify-content: center; /* or 'start' or 'left' depending on alignment */
}
**Alternative (Better)**:
If you're not intending to space characters manually, you might not need `flex` at all. Just let the natural flow of the inline text happen.
Update this in your JS:
title.className += this.flex ? " flex title variable-font-content " : "";
Change it to:
title.className += " title variable-font-content ";
And **remove** the `.flex` class completely from the CSS and code.
—
### 🧪 Test After Removing `flex` or Changing `justify-content`
Also, optionally, you can reduce any unintended spacing by adding:
.fit-container h1 span {
letter-spacing: normal !important;
margin: 0;
}
—
Would you like me to clean this whole widget up and modularize styles/scripts for better readability and maintainability too?
—
### 🧠 Why it Happens
In your `Effect Two` (`effect-two`) JS logic, you have this:
var fontSize = window.innerWidth / (str.length / 2.7);
title.style = "font-size: " + fontSize + "px;";
This calculates font size **relative to the screen width and number of characters**. But if the widget loads before fonts/styles/DOM are fully ready, the calculation can become **too aggressive** — especially when it's the first widget loaded.
—
### ✅ Fixes You Can Apply
#### ✅ 1. **Set a Default Font Size via Inline Style**
In your `render()` function for `effect-two`, make sure you add this to the inline “ style to force a base:
$font_size = '95px'; // or retrieve from typography control
echo '<h1 id="titlefont2" class="variable-font-content" style="font-size: ' . esc_attr($font_size) . ';">' . esc_html($text) . '</h1>';
This prevents JavaScript from calculating and overriding font size unnecessarily on first load.
—
#### ✅ 2. **Avoid Font Size Logic Based on Screen Width in JS**
In the current JS for `effect-two`, remove or comment this:
var fontSize = window.innerWidth / (str.length / 2.7);
title.style = "font-size: " + fontSize + "px;";
Instead, just **preserve the font size already applied in HTML/CSS**, like this:
// Let CSS handle font size unless scale is true
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";
}
—
#### ✅ 3. **Use `setTimeout` or `requestAnimationFrame` for JS Init Delay**
Sometimes the DOM isn’t fully ready when you're calculating font sizes. Add a small delay to allow layout to settle:
setTimeout(() => {
this.setSize();
}, 50);
Place this in your `init()` method after DOM manipulation (`title.innerHTML = "";` etc).
—
### ✅ Optional: Move Font Size Control into Elementor
If you're using Elementor’s Typography control (`$settings['text_typography_font_size']`), always prefer passing that size instead of calculating dynamically in JS.
—
Would you like me to help extract and cleanly modularize `effect-one` and `effect-two` JS into separate reusable methods or classes? Could make debugging and enhancements much smoother.
<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;
letter-spacing: normal !important;
/* width: 100vw;*/
}
.fit-container h1 span {
transform: translateY(-10px);
user-select: none;
letter-spacing: normal !important;
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%;
}
.hero-title h1 {
font-family:'Neue Metana';
text-rendering: optimizeSpeed;
color: #000;
line-height: 0.8;
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>
—
## 🧠 Problem Summary:
In `Effect Two`, the font size is **calculated dynamically in JavaScript** using this line:
var fontSize = window.innerWidth / (str.length / 2.7);
This leads to **unpredictable sizing**, especially when the widget loads **before the page layout is fully ready** — and becomes more obvious when it's the first thing rendered.
—
## ✅ Recommended Fixes:
### ✅ 1. **Use Elementor’s typography font size instead of dynamic JS**
You already have the user-defined font size in PHP:
$font_size = '95px';
if ( isset($settings['text_typography_font_size']['size'], $settings['text_typography_font_size']['unit']) ) {
$font_size = $settings['text_typography_font_size']['size'] . $settings['text_typography_font_size']['unit'];
}
Now apply that directly to the “ element for Effect Two:
<h1 id="titlefont2" class="variable-font-content" style="font-size: <?php echo esc_attr($font_size); ?>;">
<?php echo esc_html($text); ?>
</h1>
—
### ✅ 2. **Remove this line from JS for Effect Two:**
In the JavaScript under `this.setSize()` for Effect Two:
var fontSize = window.innerWidth / (str.length / 2.7);
title.style = "font-size: " + fontSize + "px;";
🔁 Replace it with:
// Use the font size from inline style set by PHP
// Do not override with JS unless this.scale is true
So the final `setSize()` method looks like:
this.setSize = function() {
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";
}
};
—
### ✅ 3. **(Optional) Add fallback CSS font-size via class**
If you want to be extra safe, you can also define a fallback font size for `.hero-title h1` in your CSS:
.hero-title h1 {
font-size: 95px;
}
So even if JS fails, your widget still looks right.
—
## ✅ Bonus: Normalize whitespace for spans
To prevent spacing issues (like wide gaps between words), do this in PHP before rendering text:
$text = str_replace(' ', ' ', $text);
Or modify the JS to skip wrapping empty spaces in spans (if you're feeling fancy 😄).
—
Let me know if you want help:
– Refactoring both effects into reusable JS functions.
– Exporting this widget into a standalone plugin.
– Adding a toggle for responsive font sizing.
Happy to help clean or modularize this for easier reuse!
You're seeing an issue where **short texts like "Hello World" in Effect Two** appear *too large*, even though font-size is defined.
This happens because in Effect Two, you're dynamically calculating font size **based on screen width divided by character count**:
var fontSize = window.innerWidth / (str.length / 2.7);
So if `str.length` is small (like "Hello World" = ~11), the divisor is small, which makes the font size **massive**.
—
### ✅ Fix the Font Size for Short Text (Effect Two)
Here are two **safe and simple solutions**:
—
### ✅ **Option 1: Use Elementor's typography size instead of JS-based calculation**
Since you're already capturing the typography setting in PHP like this:
$font_size = '95px';
if ( isset($settings['text_typography_font_size']['size'], $settings['text_typography_font_size']['unit']) ) {
$font_size = $settings['text_typography_font_size']['size'] . $settings['text_typography_font_size']['unit'];
}
Then **set it inline** in the H1 for Effect Two:
<h1 id="titlefont2" class="variable-font-content" style="font-size: <?php echo esc_attr($font_size); ?>;">
<?php echo esc_html($text); ?>
</h1>
Then **in your JavaScript, remove or skip this line**:
var fontSize = window.innerWidth / (str.length / 2.7);
title.style = "font-size: " + fontSize + "px;";
💡 This avoids the font size being calculated based on character count, which is the root of the issue with short strings.
—
### ✅ **Option 2: Limit max font size (if you want to keep the JS-based approach)**
If you really want to keep the screen-width logic, then **cap it** so small texts don’t blow up:
var fontSize = Math.min(120, window.innerWidth / (str.length / 2.7));
title.style.fontSize = fontSize + "px";
This caps the font size at `120px` max, even for small words.
—
### 🧠 Best Practice (Recommended)
Use **Elementor's font size control** and pass it from PHP → inline `style` → skip the JS font size logic unless the user explicitly enables "responsive" scaling.
—
Would you like me to help refactor this into a cleaner reusable structure or toggle for responsive scaling? That way both effects can work more predictably across content lengths.