Elementor Variable Font Widget

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


.fit-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>
Assistant
To reduce the **extra word spacing** in the **Effect One** title (`effect-one`), you'll want to review and refine these key aspects:

### ✅ 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

Assistant
Thanks for sharing the full widget code! If you're seeing **too much character spacing** (aka letter-spacing or word spacing) in the **Effect One** title, it's likely due to the way you're **adding “ elements around each character** — and how the `display`, spacing, or font settings are applied.

### 🔧 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?

User
when effect 2 place in first place then its text showing bigger even it defined
Assistant
Ah, good catch! The issue where **Effect Two text appears larger** when it’s the **first effect shown** (i.e., placed/rendered first) is likely due to **JavaScript dynamically calculating font size** using `window.innerWidth` **before the DOM fully renders or before correct widths are available.**

### 🧠 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.

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


.fit-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>
Assistant
User is building a custom Elementor widget in WordPress that applies variable font effects with selectable animation styles (Effect One and Effect Two).
Assistant
Thanks for sharing the full updated code! Based on what you said earlier — **Effect Two text appears larger when placed before Effect One** — and looking at your code, here’s what’s going wrong and how you can fix it:

## 🧠 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!

User
but the problem with hello world like test which issmall
Assistant
Ah, got it — thanks for the clarity!

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.

Leave a Comment

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