Elementor Counter Animation Viewport
January 15, 2025 ยท AI Conversations
$('.elementor-counter-number').css('opacity', 0); // Hide numbers initially
$('.elementor-counter').each(function(index) {
var counter = $(this);
var countTo = parseFloat(counter.find('.elementor-counter-number').data('to-value')); // Get the target value as a float
var hasDecimals = countTo % 1 !== 0; // Check if the number has decimals
counter.find('.elementor-counter-number').text(0); // Set the initial count to zero
// Start counting with a delay
setTimeout(function() {
counter.find('.elementor-counter-number').css('opacity', 1); // Make the number visible
$({ countNum: 0 }).animate({ countNum: countTo }, {
duration: 2000, // Duration for counting
easing: 'linear',
step: function() {
// Display the number with or without decimals
counter.find('.elementor-counter-number').text(
hasDecimals ? this.countNum.toFixed(2) : Math.floor(this.countNum)
);
},
complete: function() {
// Ensure it shows the final value with or without decimals
counter.find('.elementor-counter-number').text(
hasDecimals ? this.countNum.toFixed(2) : Math.floor(this.countNum)
);
}
});
}, index * 1500); // Delay between each counter
});
});
how to know if elementor counter viewport is reached
### Updated Code:
jQuery(document).ready(function($) {
function animateCounter(counter) {
var countTo = parseFloat(counter.find('.elementor-counter-number').data('to-value')); // Get the target value as a float
var hasDecimals = countTo % 1 !== 0; // Check if the number has decimals
counter.find('.elementor-counter-number').text(0); // Set the initial count to zero
$({ countNum: 0 }).animate({ countNum: countTo }, {
duration: 2000, // Duration for counting
easing: 'linear',
step: function() {
// Display the number with or without decimals
counter.find('.elementor-counter-number').text(
hasDecimals ? this.countNum.toFixed(2) : Math.floor(this.countNum)
);
},
complete: function() {
// Ensure it shows the final value with or without decimals
counter.find('.elementor-counter-number').text(
hasDecimals ? this.countNum.toFixed(2) : Math.floor(this.countNum)
);
}
});
}
// Set up the Intersection Observer
const observer = new IntersectionObserver(
function(entries, observer) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
// The element is in the viewport
const counter = $(entry.target);
if (!counter.data('animated')) { // Prevent re-animating
animateCounter(counter);
counter.data('animated', true); // Mark as animated
}
}
});
},
{ threshold: 0.5 } // Trigger when 50% of the element is visible
);
// Observe each counter element
$('.elementor-counter').each(function() {
observer.observe(this);
});
});
### Explanation:
1. **`Intersection Observer API`**:
– Observes the `.elementor-counter` elements and detects when they enter the viewport.
– The `threshold` option specifies the visibility percentage required to trigger the observer.
2. **`animateCounter` Function**:
– Handles the animation logic for each counter when it enters the viewport.
3. **Prevent Re-Animation**:
– The `data('animated')` attribute ensures the counter animates only once. Once it is animated, this attribute is set to `true`.
4. **Viewport Trigger**:
– The counter starts animating only when at least 50% of its height is visible (`threshold: 0.5`).
### Advantages:
– Efficient: Uses native browser APIs, which are faster and do not require additional libraries.
– Flexible: The threshold can be adjusted as per requirements.
If you prefer to use a jQuery-based library, consider using the `waypoints` plugin for similar functionality. Let me know if you'd like an example with `waypoints`!
### Updated Code for Sequential Animation
jQuery(document).ready(function($) {
function animateCounter(counter, delay) {
var countTo = parseFloat(counter.find('.elementor-counter-number').data('to-value')); // Get the target value as a float
var hasDecimals = countTo % 1 !== 0; // Check if the number has decimals
counter.find('.elementor-counter-number').text(0); // Set the initial count to zero
setTimeout(function() {
$({ countNum: 0 }).animate({ countNum: countTo }, {
duration: 2000, // Duration for counting
easing: 'linear',
step: function() {
// Display the number with or without decimals
counter.find('.elementor-counter-number').text(
hasDecimals ? this.countNum.toFixed(2) : Math.floor(this.countNum)
);
},
complete: function() {
// Ensure it shows the final value with or without decimals
counter.find('.elementor-counter-number').text(
hasDecimals ? this.countNum.toFixed(2) : Math.floor(this.countNum)
);
}
});
}, delay); // Delay before starting the counter
}
const observer = new IntersectionObserver(
function(entries, observer) {
let delay = 0; // Start with no delay
entries.forEach(function(entry) {
if (entry.isIntersecting) {
const counter = $(entry.target);
if (!counter.data('animated')) { // Prevent re-animating
animateCounter(counter, delay);
counter.data('animated', true); // Mark as animated
delay += 2500; // Add delay for the next counter
}
}
});
},
{ threshold: 0.5 } // Trigger when 50% of the element is visible
);
// Observe each counter element
$('.elementor-counter').each(function() {
observer.observe(this);
});
});
### Key Changes for Sequential Animation:
1. **Delay Mechanism**:
– A `delay` variable is introduced that increments for each counter.
– The delay ensures counters animate one after another.
2. **Dynamic Delay Addition**:
– `delay += 2500;` adds a delay of 2.5 seconds for the next counter. Adjust this value to match the duration of the animation plus a small gap.
3. **Preserves Sequential Behavior in Viewport**:
– The counters animate sequentially as they come into view, ensuring only one is active at a time.
4. **Prevent Re-Animation**:
– The `data('animated')` attribute ensures counters are not re-animated if they come back into the viewport.
### Example Workflow:
– Counter 1 animates immediately after entering the viewport.
– Counter 2 starts animating 2.5 seconds after Counter 1 starts.
– Counter 3 starts 2.5 seconds after Counter 2 starts, and so on.
This ensures a smooth, sequential animation while respecting viewport visibility. Let me know if you need further customization!
### Final Solution for True Sequential Animation
jQuery(document).ready(function($) {
function animateCounter(counter) {
return new Promise((resolve) => {
var countTo = parseFloat(counter.find('.elementor-counter-number').data('to-value')); // Get the target value as a float
var hasDecimals = countTo % 1 !== 0; // Check if the number has decimals
counter.find('.elementor-counter-number').text(0); // Set the initial count to zero
$({ countNum: 0 }).animate({ countNum: countTo }, {
duration: 2000, // Duration for counting
easing: 'linear',
step: function() {
// Display the number with or without decimals
counter.find('.elementor-counter-number').text(
hasDecimals ? this.countNum.toFixed(2) : Math.floor(this.countNum)
);
},
complete: function() {
// Ensure it shows the final value with or without decimals
counter.find('.elementor-counter-number').text(
hasDecimals ? this.countNum.toFixed(2) : Math.floor(this.countNum)
);
resolve(); // Resolve the promise to signal completion
}
});
});
}
const observer = new IntersectionObserver(
async function(entries, observer) {
for (let entry of entries) {
if (entry.isIntersecting) {
const counter = $(entry.target);
if (!counter.data('animated')) {
counter.data('animated', true); // Mark as animated
await animateCounter(counter); // Wait for the animation to complete before starting the next
}
}
}
},
{ threshold: 0.5 } // Trigger when 50% of the element is visible
);
// Observe each counter element
$('.elementor-counter').each(function() {
observer.observe(this);
});
});
### Explanation of the Key Changes:
1. **`Promise` in `animateCounter`**:
– The `animateCounter` function returns a `Promise` that resolves when the animation is complete. This allows sequential handling using `await`.
2. **`async` Function in Intersection Observer**:
– The observer callback is now `async`, enabling `await` to pause execution until the current counter's animation finishes.
3. **Sequential Execution**:
– The `for…of` loop ensures each counter is animated only after the previous one completes.
4. **Prevent Re-Animation**:
– The `data('animated')` flag ensures that counters animate only once, even if they re-enter the viewport.
### How It Works:
1. **Viewport Detection**:
– As each `.elementor-counter` enters the viewport, the observer triggers the callback.
2. **Sequential Animation**:
– The first counter starts animating.
– The second counter waits for the first to finish (via `await animateCounter()`).
– The third waits for the second, and so on.
3. **Single Animation at a Time**:
– Only one counter animates at any given time, ensuring true sequential behavior.
### Adjustments:
– **Animation Duration**: Adjust the `duration` in `animate` to control how long each counter takes.
– **Threshold**: Modify the `threshold` to control how much of the counter must be visible to start the animation.
This solution ensures counters animate sequentially in a visually pleasing manner. Let me know if you need any further clarification or customization!