Elementor Variable Font Widget
March 28, 2025 · AI Conversations
<style>
@font-face {
src: url('https://res.cloudinary.com/dr6lvwubh/raw/upload/v1529908256/CompressaPRO-GX.woff2');
font-family:'Compressa VF';
font-style: normal;
}
.fit-container h1 {
font-family:'Compressa VF';
text-rendering: optimizeSpeed;
/* width: 100%;*/
user-select: none;
line-height: 0.8em;
margin: 0 auto;
text-transform: uppercase;
font-weight: 100;
text-align: center;
/* width: 100vw;*/
}
.fit-container h1 span {
transform: translateY(-10px);
user-select: none;
font-family:'Compressa VF';
}
.fit-container h1.flex {
display: flex;
justify-content: space-between;
}
.fit-container h1.stroke span {
position: relative;
color: #211D26;
line-height: inherit;
}
.fit-container h1.stroke span:after {
content: attr(data-char);
-webkit-text-stroke-width: 3px;
-webkit-text-stroke-color: #FE6730;
position: absolute;
left: 0;
line-height: inherit;
color: transparent;
z-index: -1;
}
</style>
<script>
</script>
';
self::$script_loaded = true;
}
if(!is_admin())
{
?>
<script>
var maxDist;
var mouse = { x: 0, y: 0 };
var cursor = {
x: window.innerWidth,
y: window.innerHeight
};
Math.dist = function(a, b) {
var dx = b.x - a.x;
var dy = b.y - a.y;
return Math.sqrt(dx * dx + dy * dy);
}
window.addEventListener("mousemove", function(e) {
cursor.x = e.clientX;
cursor.y = e.clientY;
});
window.addEventListener("touchmove", function(e) {
var t = e.touches[0];
cursor.x = t.clientX;
cursor.y = t.clientY;
}, {
passive: false
});
var Char = function(container, char) {
var span = document.createElement("span");
span.setAttribute('data-char', char);
span.innerText = char;
container.appendChild(span);
this.getDist = function() {
this.pos = span.getBoundingClientRect();
return Math.dist(mouse, {
x: this.pos.x + (this.pos.width / 1.75),
y: this.pos.y
});
}
this.getAttr = function(dist, min, max) {
var wght = max - Math.abs((max * dist / maxDist));
return Math.max(min, wght + min);
}
this.update = function(args) {
var dist = this.getDist();
this.wdth = args.wdth ? ~~this.getAttr(dist, 5, 200) : 100;
this.wght = args.wght ? ~~this.getAttr(dist, 100, 800) : 400;
this.alpha = args.alpha ? this.getAttr(dist, 0, 1).toFixed(2) : 1;
this.ital = args.ital ? this.getAttr(dist, 0, 1).toFixed(2) : 0;
this.draw();
}
this.draw = function() {
var style = "";
style += "opacity: " + this.alpha + ";";
style += "font-variation-settings: 'wght' " + this.wght + ", 'wdth' " + this.wdth + ", 'ital' " + this.ital + ";";
span.style = style;
}
return this;
}
var VFont = function(element) {
this.scale = false;
this.flex = true;
this.alpha = false;
this.stroke = false;
this.width = true;
this.weight = true;
this.italic = true;
var title = element;
var str = title.innerText;
var chars = [];
this.init = function() {
title.innerHTML = "";
for (var i = 0; i < str.length; i++) {
var _char = new Char(title, str[i]);
chars.push(_char);
}
this.set();
window.addEventListener("resize", this.setSize.bind(this));
}
this.set = function() {
title.className += this.flex ? " flex title variable-font-content " : "";
title.className += this.stroke ? " stroke" : "";
this.setSize();
}
this.setSize = function() {
var mainDiv = title.parentElement;
// var fontSize = mainDiv.clientWidth / (str.length / 2);
var fontSize = "<?php echo $font_size ?>";
title.style.fontSize = fontSize;
if (this.scale) {
var scaleY = (window.innerHeight / title.getBoundingClientRect().height).toFixed(2);
var lineHeight = scaleY * 0.8;
title.style.transform = "scale(1," + scaleY + ")";
title.style.lineHeight = lineHeight + "em";
}
}
this.animate = function() {
mouse.x += (cursor.x - mouse.x) / 20;
mouse.y += (cursor.y - mouse.y) / 20;
requestAnimationFrame(this.animate.bind(this));
this.render();
}
this.render = function() {
maxDist = title.getBoundingClientRect().width / 2;
for (var i = 0; i < chars.length; i++) {
chars[i].update({
wght: this.weight,
wdth: this.width,
ital: this.italic,
alpha: this.alpha
});
}
}
this.init();
return this;
}
// Initialize for all elements with class "title"
var titles = document.querySelectorAll(".title");
var fontObjects = [];
titles.forEach(function(el) {
var txt = new VFont(el);
fontObjects.push(txt);
// Set unique font size per element
var parent = el.closest('.fit-container');
if (parent && parent.dataset.fontSize) {
el.style.fontSize = parent.dataset.fontSize;
}
});
// Unified animation loop for all
function animateAll() {
mouse.x += (cursor.x - mouse.x) / 20;
mouse.y += (cursor.y - mouse.y) / 20;
requestAnimationFrame(animateAll);
fontObjects.forEach(function(font) {
font.render();
});
}
animateAll();
</script>
<style>
@font-face {
src: url('/app/themes/autohaus-lange/cdnassets/NeueMetana-Thin.woff');
font-family:'NeueMetana-Thin';
font-style: normal;
}
.hero-content {
padding: 300px 0 0 0;
position: relative;
height: 100%;
}
@media only screen and (min-width:768px) and
(max-width:1024px) and (orientation:portrait) {
.hero-content {
padding-top: 120px;
padding-bottom: 100px;
}
}
@media only screen and (min-width: 320px) and
(max-width: 768px) and (orientation: landscape) {
.hero-content {
padding-top: 120px;
padding-bottom: 100px;
}
}
@media only screen and (min-width: 320px) and
(max-width: 667px) and (orientation: portrait) {
.hero-content {
padding-top: 150px;
padding-bottom: 100px;
}
}
.hero-title h1 {
font-family:'Compressa VF';
text-rendering: optimizeSpeed;
color: #000;
user-select: none;
line-height: 100px;
margin: 0 auto;
text-transform: uppercase;
font-weight: 100;
text-align: left;
}
.hero-title h1 .line {
display: block;
}
@media only screen and (min-width:768px) and
(max-width:1024px) and (orientation:portrait) {
.hero-title h1 .line-space {
display: none;
}
.hero-title h1 {
font-size: 100px !important;
line-height: 90px;
}
}
@media only screen and (min-width: 320px) and
(max-width: 736px) and (orientation: landscape) {
.hero-title h1 .line-space {
display: none;
}
.hero-title h1 {
font-size: 70px !important;
line-height: 65px;
}
}
@media only screen and (min-width: 375px) and
(max-width: 420px) and (orientation: portrait) {
.hero-title h1 {
font-size: 50px !important;
line-height: 52px;
}
.hero-title h1 .line-space {
display: none;
}
}
@media (max-width: 375px) {
.hero-title h1 {
font-size: 35px !important;
line-height: 38px;
}
.hero-title h1 .line-space {
display: none;
}
}
.hero-title h1 span {
transform: translateY(-10px);
user-select: none;
}
.hero-title h1.flex {
display: flex;
justify-content: space-between;
}
.hero-title h1.stroke span {
position: relative;
color: #211D26;
line-height: inherit;
}
.hero-title h1.stroke span:after {
content: attr(data-char);
-webkit-text-stroke-width: 2px;
-webkit-text-stroke-color: #FE6730;
position: absolute;
left: 0;
line-height: inherit;
color: transparent;
z-index: -1;
}
</style>
<script>
(function($) {
$(document).ready(function () {
var maxDist;
var mouse = { x: 0, y: 0 };
var cursor = {
x: window.innerWidth,
y: window.innerHeight
};
Math.dist = function(a, b) {
var dx = b.x - a.x;
var dy = b.y - a.y;
return Math.sqrt(Math.pow(dx, 2), Math.pow(dy, 2));
}
window.addEventListener("mousemove", function(e) {
cursor.x = e.clientX;
cursor.y = e.clientY;
});
window.addEventListener("touchmove", function(e) {
var t = e.touches[0];
cursor.x = t.clientX;
cursor.y = t.clientY;
}, {
passive: false
});
var Char = function(container, char) {
var span = document.createElement("span");
span.setAttribute('data-char', char);
span.innerText = char;
container.appendChild(span);
this.getDist = function() {
this.pos = span.getBoundingClientRect();
return Math.dist(mouse, {
x: this.pos.x + (this.pos.width / 1.75),
y: this.pos.y
});
}
this.getAttr = function(dist, min, max) {
var wght = max - Math.abs((max * dist / maxDist));
return Math.max(min, wght + min);
}
this.update = function(args) {
var dist = this.getDist();
this.wdth = args.wdth ? ~~this.getAttr(dist, 5, 200) : 100;
this.wght = args.wght ? ~~this.getAttr(dist, 100, 800) : 400;
this.alpha = args.alpha ? this.getAttr(dist, 0, 1).toFixed(2) : 1;
this.ital = args.ital ? this.getAttr(dist, 0, 1).toFixed(2) : 0;
this.draw();
}
this.draw = function() {
var style = "";
style += "opacity: " + this.alpha + ";";
style += "font-variation-settings: 'wght' " + this.wght + ", 'wdth' " + this.wdth + ", 'ital' " + this.ital + ";";
span.style = style;
}
return this;
}
var VFont = function() {
this.scale = false;
this.flex = false;
this.alpha = false;
this.stroke = false;
this.width = true;
this.weight = true;
this.italic = false;
var title, str, chars = [];
this.init = function() {
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>
1. Only the relevant HTML, CSS, and JS for the selected effect is output.
2. Each effect is scoped using unique IDs or classes to avoid shared selectors.
3. Scripts and styles are not applied globally.
### ✅ Changes Needed:
– **Use conditionally rendered and scoped class names/IDs**.
– **Prevent both scripts from running simultaneously**.
– **Avoid global selectors like `.title` or `.variable-font-content` where possible**.
—
### ✅ Main Fixes Below:
Update the **render** function to:
1. Ensure **CSS and JS selectors are unique per effect**.
2. Ensure scripts and styles only run for their specific effect.
3. Prevent loading or applying both effects on the same page load.
Here’s a cleaned and fixed version of the **`render()`** method with scoped outputs for both effects:
—
### ✅ Updated `render()` Snippet (cleaned and fixed):
protected function render() {
$settings = $this->get_settings_for_display();
$text = esc_html($settings['content']);
$font_size = $settings['text_typography_font_size']['size'] . $settings['text_typography_font_size']['unit'];
$widget_id = 'variable-font-' . $this->get_id(); // unique ID
$effect = $settings['font_effect'];
echo "<div id='$widget_id' class='variable-font-wrapper $effect' data-font-size='$font_size'>";
// Render effect-specific content
if ($effect === 'effect-one') {
echo "<style>
@font-face {
src: url('https://res.cloudinary.com/dr6lvwubh/raw/upload/v1529908256/CompressaPRO-GX.woff2');
font-family: 'Compressa VF';
}
#$widget_id h1 {
font-family: 'Compressa VF';
font-weight: 100;
text-align: center;
font-size: $font_size;
line-height: 1em;
text-transform: uppercase;
user-select: none;
}
</style>";
echo "<h1 class='title'>$text</h1>";
if (!self::$script_loaded && !is_admin()) {
echo '<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.5/dat.gui.min.js"></script>';
self::$script_loaded = true;
}
// Inline JS scoped to this widget
echo "<script>
(function(){
var container = document.querySelector('#$widget_id');
if (!container) return;
var title = container.querySelector('h1');
var text = title.innerText;
var chars = [];
var mouse = { x: 0, y: 0 };
var cursor = { x: window.innerWidth, y: window.innerHeight };
var maxDist;
window.addEventListener('mousemove', function(e) {
cursor.x = e.clientX;
cursor.y = e.clientY;
});
function dist(a, b) {
var dx = b.x - a.x;
var dy = b.y - a.y;
return Math.sqrt(dx * dx + dy * dy);
}
function Char(container, char) {
var span = document.createElement('span');
span.setAttribute('data-char', char);
span.innerText = char;
container.appendChild(span);
this.update = function() {
var rect = span.getBoundingClientRect();
var d = dist(mouse, { x: rect.x + rect.width / 2, y: rect.y });
var wght = Math.max(100, 800 - d);
span.style.fontVariationSettings = `'wght' ${wght}, 'wdth' 100`;
};
return this;
}
function init() {
title.innerHTML = '';
for (var i = 0; i < text.length; i++) {
chars.push(new Char(title, text[i]));
}
}
function animate() {
mouse.x += (cursor.x - mouse.x) / 10;
mouse.y += (cursor.y - mouse.y) / 10;
requestAnimationFrame(animate);
chars.forEach(c => c.update());
}
init();
animate();
})();
</script>";
}
else if ($effect === 'effect-two') {
echo "<style>
@font-face {
src: url('/app/themes/autohaus-lange/cdnassets/NeueMetana-Thin.woff');
font-family: 'NeueMetana-Thin';
}
#$widget_id h1 {
font-family: 'NeueMetana-Thin';
font-weight: 100;
text-transform: uppercase;
font-size: $font_size;
user-select: none;
line-height: 1em;
margin: 0;
}
</style>";
echo "<h1 class='title'>$text</h1>";
echo "<script>
(function(){
var container = document.querySelector('#$widget_id');
if (!container) return;
var title = container.querySelector('h1');
var text = title.innerText;
var chars = [];
var mouse = { x: 0, y: 0 };
var cursor = { x: window.innerWidth, y: window.innerHeight };
var maxDist;
window.addEventListener('mousemove', function(e) {
cursor.x = e.clientX;
cursor.y = e.clientY;
});
function dist(a, b) {
var dx = b.x - a.x;
var dy = b.y - a.y;
return Math.sqrt(dx * dx + dy * dy);
}
function Char(container, char) {
var span = document.createElement('span');
span.setAttribute('data-char', char);
span.innerText = char;
container.appendChild(span);
this.update = function() {
var rect = span.getBoundingClientRect();
var d = dist(mouse, { x: rect.x + rect.width / 2, y: rect.y });
var wght = Math.max(100, 800 - d);
span.style.fontVariationSettings = `'wght' ${wght}`;
};
return this;
}
function init() {
title.innerHTML = '';
for (var i = 0; i < text.length; i++) {
chars.push(new Char(title, text[i]));
}
}
function animate() {
mouse.x += (cursor.x - mouse.x) / 10;
mouse.y += (cursor.y - mouse.y) / 10;
requestAnimationFrame(animate);
chars.forEach(c => c.update());
}
init();
animate();
})();
</script>";
}
echo "</div>";
}
—
### ✅ Key Fix Summary:
– ✅ Scoped all output by `widget_id` to avoid cross-effects.
– ✅ Avoided using global selectors like `.title` and `.variable-font-content`.
– ✅ Ensured each script only runs for its selected effect.
– ✅ Prevented JS and CSS leakage across different widgets on the same page.
Let me know if you want to extract the scripts into separate files or enqueue them properly via `wp_enqueue_script()`!