Search code examples
javascriptcssanimationscrollgsap

How to set elements back to relative after keeping it on fixed


https://codepen.io/BlazgoCompany/pen/dyBBRLp

I am trying to make a website with lots of scroll animations (the text has been masked). The first animation with the zooming in star works very well, but the second animation doesn't work. It plays the animation like normal, but once it is done, it doesn't go back to the regular scrolling. It should also continue with the text below it after the animation is done.

I am pretty sure I need to set it to relative but can't seem to get it to work.

Also, please do not suggest using ScrollTrigger because it will not stay on CodePen forever, and I do not want to get Club GSAP.

I am unable to use Stack Snippets for some reason so here's the full code:

<div class="top-right">
    <span class="cta-login">
        <button class="my-dash btn-base">Lorem Ipsum</button>
    </span>
</div>
<div class="nav">
    <span class="logo">
        <!-- Logo Placeholder -->
    </span>
    <span class="cta-login">
        <button class="login btn-base" onclick="openModal()">Login</button>
    </span>
</div>
<div class="hero">
    <h1 class="hero-text">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</h1>
</div>
<div class="content">
    <small class="subtitle">Sed do eiusmod tempor incididunt.</small>
    <h2 style="margin-bottom: 0">
        <span class="brand gradient teal text">Lorem.</span>
        <span class="brand gradient blue text">Ipsum.</span>
    </h2>
    <h2 class="brand gradient red text">Dolor Sit Amet.</h2>
    <p style="color: #999">Consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. <span class="highlight">Ut enim</span> ad minim veniam, quis nostrud <span class="highlight">exercitation</span> ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
    <button class="getstarted">Get Started</button>
    <div class="spacer-240"></div>
    <h1 class="tw-heading gray">Placeholder Heading</h1>
    <div class="gen-card">
        <h2>Placeholder Title</h2>
        <h4>Placeholder Subtitle</h4>
        <p>Placeholder text for description.</p>
    </div>
    <div class="gen-card">
        <h2>Placeholder Title</h2>
        <h4>Placeholder Subtitle</h4>
        <p>Placeholder text for description.</p>
    </div>
    <div class="gen-card">
        <h2>Placeholder Title</h2>
        <h4>Placeholder Subtitle</h4>
        <p>Placeholder text for description.</p>
    </div>
    <div class="gen-card">
        <h2>Placeholder Title</h2>
        <h4 class="anim brand red text">Placeholder Subtitle</h4>
        <p>Placeholder text for description.</p>
    </div>
    This text should appear after this scroll animation (but it doesn't!)
</div>

@import url('https://fonts.googleapis.com/css?family=Nunito:200,200i');
    @import url('https://fonts.googleapis.com/css?family=Roboto:300,400,500');
    @import url('https://fonts.googleapis.com/css?family=Varela+Round');

    /* Import fonts */
    /* Global Styles */
    body {
        background: black;
        overflow-x: hidden;
        min-height: 800vh;
        margin: 0;
        font-family: sans-serif;
        /* Fallback font */
    }

    input {
        padding: 12px;
        width: 80%;
        border-radius: 8px;
        border: none;
        left: 50%;
        position: relative;
        transform: translate(-50%);
        margin-top: 42px;
    }

    .hero {
        padding: 16px;
        position: fixed;
        width: 100%;
        height: 100vh;
        background: radial-gradient(circle at 0% -10%, var(--gradient-color), transparent 38%);
    }

    .hero-text {
        color: white !important;
        font-size: 64px;
        font-family: "Varela Round", sans-serif;
        /* Default to Varela Round, fallback to sans-serif */
        display: inline-block;
        position: relative;
        z-index: 10;
    }

    .hero-text svg {
        transform: translateY(-4px);
        display: inline;
        vertical-align: middle;
        background-color: transparent;
    }

    .content {
        color: white;
        display: none;
        font-family: "Varela Round";
        text-align: center;
    }

    .highlight {
        color: #e3b505;
    }

    .btn-base,
    .modal .login,
    .backbtn {
        border-radius: 20px;
        border: none;
        padding: 0 12px;
        height: 30px;
        background: #e3b505;
        font-size: 16px;
        font-weight: bold;
    }

    .btn-base:hover,
    .modal .login:hover,
    .backbtn:hover {
        background: #c8a104;
    }

    .getstarted {
        padding: 8px 16px;
        border-radius: 32px;
        height: auto;
    }

    .nav {
        position: fixed;
        top: -36px;
        background: radial-gradient(circle at 0% center, #066ab2, transparent 80%);
        backdrop-filter: blur(8px);
        width: calc(100% - 32px);
        height: 36px;
        z-index: 1000;
        left: 50%;
        transform: translate(-50%, 0);
        max-width: 500px;
        border-radius: 32px;
    }

    button:active {
        transform: scale(0.9)
    }

    button {
        transition: transform 0.1s;
    }


    .btncontainer {
        margin-top: 48px;
        left: 50%;
        position: relative;
        transform: translate(-50%);
        display: inline-block;
    }

    .btn-close {
        position: absolute;
        right: 8px;
        top: 8px;
        width: 32px;
        height: 32px;
        background: #95190C;
        color: white
    }

    .btn-close:hover {
        background: #711409;
    }

    .loader {
        height: 24px;
        width: 24px;
        border: 3px solid transparent;
        border-bottom: 3px solid black;
        border-radius: 32px;
        animation: spin 1s linear infinite;
    }

    @keyframes spin {
        0% {
            transform: rotate(0deg);
        }

        100% {
            transform: rotate(360deg);
        }

    }

    .login-modal h1 {
        font-size: 24px;
        font-weight: bold;
        text-align: center;
        margin-bottom: -20
    }

    .brand.gradient.teal.text {
        background: #107E7D;
        background: -webkit-linear-gradient(#28E2DF, #0C5A59);
        -webkit-background-clip: text;
        -webkit-text-fill-color: transparent;
    }

    .brand.gradient.blue.text {
        background: #066AB1;
        background: -webkit-linear-gradient(#89CBFB, #05528A);
        -webkit-background-clip: text;
        -webkit-text-fill-color: transparent;
    }

    .brand.gradient.blue.extra {
        background: #066AB1;
        background: -webkit-linear-gradient(#C4E5FD, #05528A);
        -webkit-background-clip: text;
        -webkit-text-fill-color: transparent;
    }

    .brand.gradient.red.text {
        margin-top: 0;
        background: #971A0C;
        background: -webkit-linear-gradient(#F6877B, #711409);
        -webkit-background-clip: text;
        -webkit-text-fill-color: transparent;
    }

    h2 {
        font-size: 32px;
    }

    .subtitle {
        font-size: 14px;
        background: -webkit-linear-gradient(#eee, #3f3f3f);
        -webkit-background-clip: text;
        -webkit-text-fill-color: transparent;
    }

    .subtitle::after,
    .subtitle::before {
        content: '';
        width: 64px;
        height: 1px;
        display: inline-block;
    }

    .subtitle::before {

        transform: translate(-8px, -4px);
        background: linear-gradient(-90deg, #aaa 0%, transparent 100%);
        right: 120%;
        top: 50%;
    }

    .subtitle::after {
        transform: translate(8px, -4px);
        background: linear-gradient(90deg, #aaa 0%, transparent 100%);
        left: 120%;
        top: 50%;
    }

    h1.gray.tw-heading {
        font-size: 48px;
        background: -webkit-linear-gradient(#eee, #333);
        -webkit-background-clip: text;
        -webkit-text-fill-color: transparent;
    }

    .spacer-240 {
        height: 240px;
    }

    .gen-card {
        width: calc(25% - 14px);
        margin: 0 4px;
        display: inline-block;
        height: 360;
        background: transparent;
        backdrop-filter: blur(4px);
        opacity: 0;
        border-radius: 16px;
        text-align: center;
        padding: 12px;
        border: 1px solid #555
    }

    .gen-card h2 {
        margin: 0;
    }

    .fixed {
        position: fixed;
    }
    .relative{
        position: relative;
    }

    /* .anim.brand.red.text{
        margin-top: 0;
        background: #971A0C;
        background: -webkit-linear-gradient(#F6877B, #711409);
        -webkit-background-clip: text;
        -webkit-text-fill-color: transparent;
    } */

    function setTransformOriginToChild() {
        const parent = document.querySelector('.hero-text');
        const child = document.querySelector('.hero-text .star');

        const parentRect = parent.getBoundingClientRect();
        const childRect = child.getBoundingClientRect();

        const childCenterX = (childRect.left - 10 + childRect.right) / 2;
        const childCenterY = (childRect.top + 4 + childRect.bottom) / 2;

        const transformOriginX = (childCenterX - parentRect.left) / parentRect.width * 100;
        const transformOriginY = (childCenterY - parentRect.top) / parentRect.height * 100;

        parent.style.transformOrigin = `${transformOriginX}% ${transformOriginY}%`;
    }



    window.addEventListener('resize', setTransformOriginToChild);


    
    // Function to split text into multiple spans
    function splitTextIntoSpans() {
        // Get the container element
        const container = document.querySelector('.hero-text');

        // Get the text content of the container
        const text = container.textContent;

        // Create a document fragment to hold the new span elements
        const fragment = document.createDocumentFragment();

        // Define words to style
        const wordsToStyle = {
            'future': '#066AB2',
            'sit': '#066AB2'
        };

        // Helper function to create span elements for text
        function createSpansForText(text, color = '') {
            const fragment = document.createDocumentFragment();
            for (let char of text) {
                const span = document.createElement('span');
                span.textContent = char;
                if (color) {
                    span.classList.add("brand")
                    span.classList.add("gradient")
                    span.classList.add("blue")
                    span.classList.add("text")
                    span.classList.add("extra")
                }
                fragment.appendChild(span);
            }
            return fragment;
        }

        // Process the text letter by letter
        let index = 0;
        while (index < text.length) {
            // Find the next word or space
            let nextSpace = text.indexOf(' ', index);
            if (nextSpace === -1) {
                nextSpace = text.length;
            }

            var word = text.slice(index, nextSpace);

            // Add a space character after the word
            if (nextSpace < text.length) {
                word += ' ';
            }

            // Create spans for each letter of the word
            const color = wordsToStyle[word.trim()] || '';
            const wordFragment = createSpansForText(word, color);
            fragment.appendChild(wordFragment);

            // If the word  add a special span element after it
            if (word.trim() === 'sit') {
                const starSpan = document.createElement('span');
                starSpan.className = 'star';
                starSpan.innerHTML = `
      <svg width="32" height="24">
        <path d="M 2 12 q 10 0 10 -10 q 0 10 10 10 q -10 0 -10 10 q 0 -10 -10 -10 M 0 12 A 1 1 0 0 0 24 12 A 1 1 0 0 0 0 12"></path>
      </svg>`;
                fragment.appendChild(starSpan);
            }

            // Move to the next word
            index = nextSpace + 1;
        }

        // Clear the original container
        container.textContent = '';

        // Append the fragment to the container
        container.appendChild(fragment);
    }
    function calculateTravelDistance() {
        // Get the .star element
        const starElement = document.querySelector('.hero-text .star');

        // Get the bounding rectangle of the .star element
        const starRect = starElement.getBoundingClientRect();

        // Get the center of the .star element
        const starCenterX = starRect.left + starRect.width / 2;
        const starCenterY = starRect.top + starRect.height / 2;

        // Get the center of the screen
        const screenCenterX = window.innerWidth / 2;
        const screenCenterY = window.innerHeight / 2;

        // Calculate the amount to move the element
        const moveX = screenCenterX - starCenterX;
        const moveY = screenCenterY - starCenterY;

        return { moveX, moveY };
    }
    // Call the function to split the text
    window.addEventListener('load', () => {
        splitTextIntoSpans();
        setTransformOriginToChild()
        tl = gsap.timeline({
        })
        tl.set(".hero-text span", { filter: "blur(32px)" })
        tl.set(".hero", { "--gradient-color": "#FFD700" })
        tl.set(".hero-text svg", { fill: "white" })
        tl.to(".hero-text span", { filter: "blur(0px)", duration: 0.5, stagger: 0.05 })
        tl.to(".top-right", { opacity: 0, duration: 0.8 }, "<+=1.2")
        tl.to(".hero-text", { y: calculateTravelDistance().moveY, x: calculateTravelDistance().moveX, duration: 3, fill: "black", ease: "power4.in" }, "+=0.5")
        tl.to(".hero-text", { scale: 90, duration: 3, ease: "power4.in" }, "<")
        tl.to(".hero-text svg", { duration: 1, fill: "black", background: "transparent" }, "-=1")
        tl.to(".hero", { duration: 1, "--gradient-color": "#000" }, "-=1")
        tl.to(".nav", { duration: 1, top: "16px" }, "-=1")
        tl.pause(0.5)
    });
    var gentlmarker = 1000000
    var gentl = 0
    document.addEventListener("scroll", () => {
        //get scroll position
        const scrollPosition = window.scrollY;
        if ((scrollPosition / 200) + 0.5 < 5.8) {
            gsap.set(".hero", { display: "block" })
            gsap.set(".content", { display: "none" })
            tl.pause((scrollPosition / 200) + 0.5)
        }
        else {
            gsap.set(".hero", { display: "block" })
            gsap.set(".content", { display: "none" })
            tl.pause((scrollPosition / 200) + 0.5)
            gsap.set(".hero", { display: "none" })
            gsap.set(".content", { position: "relative", top: 1060 + window.innerHeight, display: "block" })
        }
        if(document.querySelector(".tw-heading").getBoundingClientRect().top < 200 && scrollPosition > 1800){
            //"pin" the tw-heading and the gencards for 300 px of scrolling
            gsap.set(".tw-heading", {position: "fixed", top: 200-32, left: "50%", xPercent: -50, width: "100%"})
            gc = document.querySelectorAll(".gen-card")
            gcw = gc[0].getBoundingClientRect().width
            gsap.set(".gen-card", {position: "fixed", opacity: 1, top: 264, right: -gcw})
            gentl=gsap.timeline({})
            // gentl.to(gc[0], {position: "fixed", top: 264, left: 8})
            // gentl.to(gc[1], {position: "fixed", top: 264, left: 8+gcw+8})
            // gentl.to(gc[2], {position: "fixed", top: 264, left: 8+gcw+8+gcw+8})
            // gentl.to(gc[3], {position: "fixed", top: 264, left: 8+gcw+8+gcw+8+gcw+8})
            

            // Common properties for the animation
            const commonProps = {
                position: "fixed",
                top: 264
            };

            // Define the start position and end positions for gc elements
            const startPosition = -200;
            const endPositionFirst = -gcw;
            const endPositionLast = 32;

            // Create a timeline animation with stagger
            gentl.to(gc, {
                ...commonProps,
                left: endPositionFirst,
                stagger: 0.2, // Stagger by 0.1 seconds for each element
                duration: 1,  // Adjust duration to your preference
                ease: "power1.inOut" // Example easing function
            })
            .to(gc[gc.length - 1], {
                left: endPositionLast,
                duration: 1, // Same duration as above to keep consistency
                ease: "power1.inOut" // Use the same easing function
            }, "-=1")
            .to(gc[3], {
                width: "50%",
                left: "50%",
                x: "-50%",
                duration: 1, // Same duration as above to keep consistency
                ease: "power1.inOut" 
            })
            .addLabel("fgenfocus")
            .to(gc[3].querySelector("h2"), {
                opacity: 0
            }, "fgenfocus")
            .to(gc[3].querySelector("h2"), {
                opacity: 0,
                height: "0px",
                margin: 0
            }, "fgenfocus")
            .add(function(){
              gc[3].querySelector("h4").innerHTML="Now the th scrolling should continue as normal"
            }, "fgenfocus")
            .to(gc[3].querySelector("h4"), {
                fontSize: "32px",
                margin: 0
            }, "fgenfocus")


            // gentl.to([gc[0], gc[1], gc[2]], {width: 0})
            gentl.pause()
            gentlmarker = scrollPosition
            

        }
        if(scrollPosition>gentlmarker){
            gc = document.querySelectorAll(".gen-card")
            gcw = gc[0].getBoundingClientRect().width
            
            console.log(gentlmarker)
            console.log((scrollPosition-gentlmarker)/300)
            console.log("----------------")
            gentl.pause((scrollPosition-gentlmarker)/300)
        }

        if((scrollPosition-gentlmarker)/300 < 0){
            gsap.set(".tw-heading", {position: "static", xPercent: 0})
            gsap.set(".gen-card", { opacity: 0 })
        }
       

    })




    







    document.querySelector(".getstarted").addEventListener("click", () => {
        let clickTl = gsap.timeline({})
        clickTl.to(".getstarted", { scale: 100, duration: 2, ease: "power4.in" })
        clickTl.to(".getstarted", { background: "black", color: "black", duration: 1, ease: "power4.in" }, "-=1")
    })

Thanks!


Solution

  • Make sure that your animation finishes and that .gen-card reset after they're done.

    Use this Javascript:

    document.addEventListener("scroll", () => {
        const scrollPosition = window.scrollY;
    
        if (scrollPosition / 200 + 0.5 < 5.8) {
            gsap.set(".hero", { display: "block" });
            gsap.set(".content", { display: "none" });
            tl.pause(scrollPosition / 200 + 0.5);
        } else {
            gsap.set(".hero", { display: "none" });
            gsap.set(".content", { display: "block", top: 1060 + window.innerHeight });
        }
    
        const twHeadingTop = document.querySelector(".tw-heading").getBoundingClientRect().top;
    
        // Trigger the second animation
        if (twHeadingTop < 200 && scrollPosition > 1800) {
            gsap.set(".tw-heading", {
                position: "fixed",
                top: 200 - 32,
                left: "50%",
                xPercent: -50,
            });
    
            const gc = document.querySelectorAll(".gen-card");
            const gcw = gc[0].getBoundingClientRect().width;
    
            gsap.set(gc, {
                position: "fixed",
                opacity: 1,
                top: 264,
                right: -gcw,
            });
    
            gentl = gsap.timeline({})
                .to(gc, {
                    position: "fixed",
                    top: 264,
                    left: -gcw,
                    stagger: 0.2,
                    duration: 1,
                    ease: "power1.inOut",
                })
                .to(gc[gc.length - 1], {
                    left: 32,
                    duration: 1,
                    ease: "power1.inOut",
                }, "-=1")
                .to(gc[3], {
                    width: "50%",
                    left: "50%",
                    x: "-50%",
                    duration: 1,
                    ease: "power1.inOut",
                })
                .addLabel("fgenfocus")
                .to(gc[3].querySelector("h2"), {
                    opacity: 0,
                }, "fgenfocus")
                .to(gc[3].querySelector("h2"), {
                    opacity: 0,
                    height: "0px",
                    margin: 0,
                }, "fgenfocus")
                .add(() => {
                    gc[3].querySelector("h4").innerHTML = "Now the scrolling should continue as normal";
                }, "fgenfocus")
                .to(gc[3].querySelector("h4"), {
                    fontSize: "32px",
                    margin: 0,
                }, "fgenfocus");
    
            gentl.pause();
            gentlmarker = scrollPosition;
        }
    
        if (scrollPosition > gentlmarker) {
            const gc = document.querySelectorAll(".gen-card");
            gcw = gc[0].getBoundingClientRect().width;
    
            gentl.pause((scrollPosition - gentlmarker) / 300);
        }
    
        if ((scrollPosition - gentlmarker) / 300 < 0) {
            gsap.set(".tw-heading", { position: "static", xPercent: 0 });
            gsap.set(".gen-card", { position: "static", opacity: 1 });
        }
    
        if (scrollPosition > gentlmarker + 300) {
            gsap.set(".tw-heading", { position: "relative" });
            gsap.set(".gen-card", { position: "relative", top: "auto" });
        }
    });