sneas / img-comparison-slider

Image comparison slider. Compare images before and after. Supports React, Vue, Angular.
https://img-comparison-slider.sneas.io
MIT License
580 stars 45 forks source link

Feature Request: Enable Animation #77

Open aapatre opened 2 years ago

aapatre commented 2 years ago

Hi @sneas, hope you are doing well Is it possible to add a feature to animate the divider? :)

As done in this slider: https://github.com/OnurErtugral/react-image-comparison-slider

Thanks & Regards, Antariksh

sneas commented 2 years ago

Hi Antariksh,

There is no such functionality per-se, but you can build one yourself based on what the component already has. The code example would be:

const slider = document.getElementById('slider');

let direction = 'right';

let move = () => {
    slider.value = slider.value + 0.15 * (direction === 'right' ? 1  : -1);
    if (slider.value >= 100) {
       direction = 'left';
    }

    if (slider.value <= 0) {
        direction = 'right';
    }
}

let animationId;

let animate = () => {
  move();
  animationId = requestAnimationFrame(animate);
}

animationId = requestAnimationFrame(animate);
aapatre commented 2 years ago

Thanks a lot, @sneas I will try this out :)

Vantadaga commented 3 months ago

Is it possible to make this work with this project?

saas786 commented 1 month ago

I've implemented this code and think it could be helpful to others. Sharing it here in case someone finds it useful. Feedback welcome to improve my code.

function animateImageComparison() {
    const slider = document.getElementById('slider');

    if (!slider) {
        return;
    }

    const originalValue = slider.value;
    // Amount to move the handle.
    const shakeAmount = 5;
    const shakeDuration = 300;
    const resetDuration = 300;

    let animationFrameId = null;

    const animateValue = (startValue, endValue, duration, onComplete) => {
        const startTime = performance.now();

        const step = (currentTime) => {
            const elapsedTime = currentTime - startTime;
            const progress = Math.min(elapsedTime / duration, 1);
            slider.value = startValue + (endValue - startValue) * progress;

            if (progress < 1) {
                animationFrameId = requestAnimationFrame(step);
            }
            else {
                onComplete();
            }
        };

        animationFrameId = requestAnimationFrame(step);
    };

    const cancelAnimation = () => {
        if (animationFrameId) {
            cancelAnimationFrame(animationFrameId);
            animationFrameId = null;
        }
    };

    const shake = () => {
        const moveRight = () => {
            animateValue(originalValue, Math.min(originalValue + shakeAmount, 100), shakeDuration, moveLeft);
        };

        const moveLeft = () => {
            animateValue(Math.min(originalValue + shakeAmount, 100), Math.max(originalValue - shakeAmount, 0), shakeDuration, reset);
        };

        const reset = () => {
            animateValue(slider.value, originalValue, resetDuration, cancelAnimation);
        };

        moveRight();
    };

    const handleVisibilityChange = (entries, observer) => {
        entries.forEach((entry) => {
            if (entry.isIntersecting) {
                shake();
                // Stop observing after the element is in view.
                observer.disconnect();
            }
        });
    };

    const observer = new IntersectionObserver(handleVisibilityChange, {
        // Adjust the threshold as needed.
        threshold: 0.1,
    });

    observer.observe(slider);

    // Cancel ongoing animation if the page is unloaded or slider is out of view.
    window.addEventListener('beforeunload', cancelAnimation);

    document.addEventListener('visibilitychange', () => {
        if (document.hidden) {
            cancelAnimation();
        }
    });
}

window.addEventListener('load', animateImageComparison);
sneas commented 3 weeks ago

This is amazing, @saas786 ! Thanks a lot!