Do you want template or code?

1. Get all-in-one template

Northbound Elementor Template

Get code and classes here:

Classes:

				
					scroll-container-parent

sticky-section

scroll-container

panel

tag
				
			

Use this javascript code:

				
					<style>
    body {
        overflow-x: hidden;
    }
    .tag {
        backdrop-filter: blur(20px);
    }
    .scroll-container {
        overflow: visible;
        will-change: transform;
        transition: transform 0.2s ease; /* Smooth transition for transform */
    }
    .panel {
        will-change: transform;
        aspect-ratio: 1.5;
        transition: background-size 0.2s ease;
    }
    .panel:hover {
        background-size: 130% !important;
        transition: 0.2s;
    }
    .overflow-hidden {
        overflow: hidden;
    }
    
   .sticky-section {
    transition: position 0.2s ease; /* Smooth transition for position change */
    }
</style>

<script>
document.addEventListener('DOMContentLoaded', function() {
    const scrollContainerParent = document.querySelector('.scroll-container-parent');
    const scrollContainer = document.querySelector('.scroll-container');
    const stickySection = document.querySelector('.sticky-section');
    const panels = document.querySelectorAll('.panel');

    let totalPanelWidth = 0;
    panels.forEach(panel => {
        totalPanelWidth += panel.offsetWidth;
    });

    const panelLeftSpace = panels[0].getBoundingClientRect().left;
    const viewportWidth = window.innerWidth;

    function handleScroll() {
        const isSticky = stickySection.classList.contains('elementor-sticky--active');
        const isPositionFixed = window.getComputedStyle(stickySection).position === 'fixed';
        const isPositionAbsolute = window.getComputedStyle(stickySection).position === 'absolute';

        if (isSticky && isPositionFixed) {
            const scrollTop = window.scrollY;
            const rect = scrollContainerParent.getBoundingClientRect();
            const elementTop = scrollTop + rect.top;
            const parentHeight = scrollContainerParent.offsetHeight;
            const viewportHeight = window.innerHeight;

            const startScroll = elementTop;
            const endScroll = elementTop + parentHeight - viewportHeight;

            let percentage = ((scrollTop - startScroll) / (endScroll - startScroll)) * 100;
            percentage = Math.max(0, Math.min(percentage, 100));

            const translateX = -(totalPanelWidth - viewportWidth + (panelLeftSpace * 2) + viewportWidth / 3) * (percentage / 100);

            // Use requestAnimationFrame to apply the transform
            requestAnimationFrame(() => {
                scrollContainer.style.transform = `translateX(${translateX}px)`;
            });
        }
        else if (!isSticky && !isPositionAbsolute) {
            // Reset transform when not sticky and not absolute
            requestAnimationFrame(() => {
                scrollContainer.style.transform = 'translateX(0)';
            });
        }
    }

    if (scrollContainer && scrollContainerParent && stickySection) {
        window.addEventListener('scroll', handleScroll);
        // Initial call to set up the correct transform
        handleScroll();
    } else {
        console.error('.scroll-container, .scroll-container-parent, or .sticky-section not found!');
    }
});
</script>

				
			

Background color:
#FBFBFB

Images I used in my video:

Youtube tutorial:

Want more GSAP tutorials?

Want more GSAP blog post?