ساخت Roadmap Animation اسکرولی در Elementor

در این آموزش یاد می‌گیرید چگونه با استفاده از Elementor و چند خط کد JavaScript، یک Roadmap Animation اسکرولی بسازید. با حرکت کاربر در صفحه، هر مرحله به‌صورت خودکار فعال می‌شود، نقطه مربوط به آن تغییر حالت می‌دهد و متن و انیمیشن همان بخش نمایش داده می‌شود.

اسم کلاس ها :

timeline-lottie-1 الی timeline-lottie-4

timeline-text-1 الی timeline-text-4

timeline-dot-1 الی timeline-dot-4

timeline-marker

JavaScript
<script>
document.addEventListener('DOMContentLoaded', function () {
    const marker = document.querySelector('.timeline-marker');

    const dots = document.querySelectorAll(
        '.timeline-dot-1, .timeline-dot-2, .timeline-dot-3, .timeline-dot-4'
    );

    if (!marker || !dots.length) {
        console.warn('Timeline marker or dots not found.');
        return;
    }

    const lottieAnimations = [null, null, null, null];

    function findLottieAnimations() {
        const lottieLibrary =
            window.lottie || window.bodymovin;

        if (
            !lottieLibrary ||
            typeof lottieLibrary.getRegisteredAnimations !== 'function'
        ) {
            console.warn('Lottie library not ready.');
            return;
        }

        const registeredAnimations =
            lottieLibrary.getRegisteredAnimations();

        [1, 2, 3, 4].forEach(function (index) {
            const widget = document.querySelector(
                '.timeline-lottie-' + index
            );

            if (!widget) {
                console.warn(
                    'Lottie widget ' + index + ' not found.'
                );
                return;
            }

            const animation =
                registeredAnimations.find(function (registeredAnimation) {
                    return (
                        registeredAnimation.wrapper &&
                        widget.contains(registeredAnimation.wrapper)
                    );
                }) || null;

            lottieAnimations[index - 1] = animation;

            if (animation) {
                animation.stop();
                console.log('Lottie ' + index + ' found.');
            } else {
                console.warn(
                    'Lottie ' + index + ' not matched.'
                );
            }
        });
    }

    function elementsOverlap(elementA, elementB) {
        const a = elementA.getBoundingClientRect();
        const b = elementB.getBoundingClientRect();

        return !(
            a.bottom < b.top ||
            a.top > b.bottom ||
            a.right < b.left ||
            a.left > b.right
        );
    }

    function getDotIndex(dot) {
        for (let index = 1; index <= 4; index++) {
            if (
                dot.classList.contains(
                    'timeline-dot-' + index
                )
            ) {
                return index;
            }
        }

        return null;
    }

    function checkTimelineCollision() {
        dots.forEach(function (dot) {
            const index = getDotIndex(dot);

            if (!index) {
                return;
            }

            const isActive =
                elementsOverlap(marker, dot);

            const lottieWidget = document.querySelector(
                '.timeline-lottie-' + index
            );

            const textWidget = document.querySelector(
                '.timeline-text-' + index
            );

            const step = document.querySelector(
                '.timeline-step-' + index
            );

            dot.classList.toggle(
                'is-active',
                isActive
            );

            if (lottieWidget) {
                lottieWidget.classList.toggle(
                    'is-active',
                    isActive
                );
            }

            if (textWidget) {
                textWidget.classList.toggle(
                    'is-active',
                    isActive
                );
            }

            if (step) {
                step.classList.toggle(
                    'is-active',
                    isActive
                );
            }

            const animation =
                lottieAnimations[index - 1];

            if (!animation) {
                return;
            }

            if (isActive) {
                animation.play();
            } else {
                animation.pause();
            }
        });
    }

    let ticking = false;

    function requestTimelineUpdate() {
        if (ticking) {
            return;
        }

        ticking = true;

        requestAnimationFrame(function () {
            checkTimelineCollision();
            ticking = false;
        });
    }

    window.addEventListener(
        'scroll',
        requestTimelineUpdate,
        { passive: true }
    );

    window.addEventListener(
        'resize',
        requestTimelineUpdate
    );

    setTimeout(function () {
        findLottieAnimations();
        checkTimelineCollision();
    }, 1500);
});
</script>

<style>
/* دایره‌ها */

.timeline-dot-1,
.timeline-dot-2,
.timeline-dot-3,
.timeline-dot-4 {
    transition:
        transform 0.25s ease,
        background-color 0.25s ease;
}

.timeline-dot-1.is-active,
.timeline-dot-2.is-active,
.timeline-dot-3.is-active,
.timeline-dot-4.is-active {
    background: #F2C300;
    transform: scale(1.35);
    
}


/* حالت غیرفعال Lottie */

.timeline-lottie-1,
.timeline-lottie-2,
.timeline-lottie-3,
.timeline-lottie-4 {
    filter: grayscale(1);
    opacity: 0.4;
    transform: scale(1);

    transition:
        filter 0.5s ease,
        opacity 0.5s ease,
        transform 0.5s ease;
}


/* حالت فعال Lottie */

.timeline-lottie-1.is-active,
.timeline-lottie-2.is-active,
.timeline-lottie-3.is-active,
.timeline-lottie-4.is-active {
    filter: grayscale(0);
    opacity: 1;
    transform: scale(1.03);
}


/* حالت غیرفعال متن */

.timeline-text-1,
.timeline-text-2,
.timeline-text-3,
.timeline-text-4 {
    opacity: 0.15;
    transform: translateY(14px);

    transition:
        opacity 0.5s ease,
        transform 0.5s ease;
}


/* حالت فعال متن */

.timeline-text-1.is-active,
.timeline-text-2.is-active,
.timeline-text-3.is-active,
.timeline-text-4.is-active {
    opacity: 1;
    transform: translateY(0);
}
</style>