Variable Font Widget Plugin
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('/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>
"name": "variable-effect-elementor-widget",
"type": "code/php",
"content": "
Now, you just need to create the file:
`/widgets/class-variable-font-widget.php` inside the plugin folder and paste your full widget class code there (the one you provided above). If you want, I can place that widget code into that file for you too—just say the word!
—
### 📁 Plugin File Structure:
variable-effect-elementor-widget/
│
├── variable-effect-elementor-widget.php
└── widgets/
└── class-variable-font-widget.php
—
### ✅ Step-by-step Instructions:
1. **Create Plugin Folder**
Create a folder named: `variable-effect-elementor-widget`.
2. **File 1: `variable-effect-elementor-widget.php`**
This is the main plugin file. Add this content:
<?php
/*
Plugin Name: Variable Effect Elementor Widget
Description: Adds a custom Elementor widget with animated variable font effects.
Version: 1.0
Author: Your Name
*/
if ( ! defined( 'ABSPATH' ) ) exit;
function register_variable_effect_elementor_widget( $widgets_manager ) {
require_once( __DIR__ . '/widgets/class-variable-font-widget.php' );
$widgets_manager->register( new Elementor_Variable_Font_Widget() );
}
add_action( 'elementor/widgets/register', 'register_variable_effect_elementor_widget' );
3. **File 2: `widgets/class-variable-font-widget.php`**
Inside the `widgets` folder, create the file and paste your full widget code there (the code you already shared with me).
—
### 🔧 How to ZIP It:
Once you have the structure as above:
– **Right-click** on the `variable-effect-elementor-widget` folder.
– Choose **“Compress”** or **“Send to → Compressed (zipped) folder”** (depending on your OS).
– You’ll get: `variable-effect-elementor-widget.zip`
—
### 📦 Ready to Upload:
Now you can upload it via **WordPress Admin → Plugins → Add New → Upload Plugin**.
—
Would you like me to generate the zip file and provide a download link directly?
👉 [Click here to download variable-effect-elementor-widget.zip](sandbox:/mnt/data/variable-effect-elementor-widget.zip)
Let me know if you'd like me to insert the full widget logic from your code into the plugin file too.
NeueMetana-Thin.woff
include it in elementor
@font-face {
src: url('/app/themes/autohaus-lange/cdnassets/NeueMetana-Thin.woff');
font-family:'NeueMetana-Thin';
font-style: normal;
}
👉 [Click here to download the updated variable-effect-elementor-widget.zip](sandbox:/mnt/data/variable-effect-elementor-widget.zip)
Let me know if you’d like help testing it inside Elementor or customizing further!
📦 [Download the updated variable-effect-elementor-widget.zip](sandbox:/mnt/data/variable-effect-elementor-widget.zip)
Let me know if you’d like to add admin notices or more enhancements!
// Check if Elementor is active
function variable_effect_elementor_widget_activation_check() {
if ( ! did_action( 'elementor/loaded' ) ) {
deactivate_plugins( plugin_basename( __FILE__ ) );
wp_die(
'This plugin requires <strong>Elementor</strong> to be installed and activated.',
'Plugin dependency check',
[
'back_link' => true,
]
);
}
}
register_activation_hook( __FILE__, 'variable_effect_elementor_widget_activation_check' );
### 🔍 What it does:
– ✅ Checks if Elementor has been loaded (via `did_action('elementor/loaded')`).
– ❌ If not loaded, it automatically deactivates the plugin.
– ⚠️ Displays a user-friendly message prompting to install and activate Elementor.
Let me know if you'd like to show an admin notice instead of halting activation completely.
Trying to access array offset on null
—
### ✅ Fix: Add a fallback if typography is not defined
Replace this line:
$font_size = $settings['text_typography_font_size']['size'].''.$settings['text_typography_font_size']['unit'];
With something safer like:
$font_size = '60px'; // default fallback
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'];
}
—
### 🧠 Why it works:
– `isset(…)` ensures both `size` and `unit` exist.
– If they don’t, it uses a safe default (`60px`) so the widget still renders.
You can even allow the default to be customizable if needed. Let me know if you’d like to do that or show a warning in the Elementor panel!