Elementor Counter Animation Viewport

User
jQuery(document).ready(function($) {
$('.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

Assistant
To trigger the animation only when the Elementor counter enters the viewport, you can use a combination of jQuery and the `Intersection Observer API` or a jQuery plugin like `waypoints`. Here's an example of how you can achieve this using the `Intersection Observer API`:

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

User
but above runs counter at once not one by one
Assistant
To ensure the counters run **one by one** as they enter the viewport (sequentially, not all at once), you can introduce a delay mechanism that triggers the next counter only after the previous one completes. Here's how to modify the code:

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

User
still runs at once
Assistant
To ensure counters animate **truly one by one**, you need to carefully structure the logic to ensure the next counter does not start until the previous one has fully completed. Here's a revised solution that achieves this:

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

Leave a Comment

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