Smooth Page Transitions with Barba.js and GSAP

Article

Smooth Page Transitions with Barba.js and GSAP

Hello everyone, I haven't written an article so long time.
I was in Jaapan for three weeks and I enjoyed spending time with my family, grand mothers and friends. :)

In this blog post, we will take a deep dive into a piece of JavaScript code that uses the Barba.js and GSAP libraries to create smooth, visually appealing page transitions.
I can not upload a video because my plan of this CMS is not a business, so you can copy and paste a following HTML, CSS and JS code into your code editor and try it your self.


index.html

<body data-barba="wrapper">
    <div class="load-container">
        <div class="loading-screen"></div>
        <div class="txt_wrapper">
            <p class="txt">Loading</p>
        </div>
        <img class="load-img"
            src="https://plus.unsplash.com/premium_photo-1694412513955-543c762c1a04?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1587&q=80"
            alt="">
    </div>


    <main data-barba="container" data-barba-namespace="home-section">
        <div class="content-inner">
            <h1 class="title animate">
                home page
            </h1>
            <div class="animate button">
                <a href="about.html">go to about</a>
            </div>
            <div class="test">
                <p class="animate">
                    Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolores fugiat maxime quod dicta laborum
                    ullam minima velit reprehenderit dolorum. Blanditiis eveniet, hic nam assumenda accusantium officia
                    dignissimos ullam laboriosam odio! Lorem ipsum dolor sit amet consectetur adipisicing elit. Animi
                    explicabo blanditiis neque molestias itaque? Doloremque error, illum illo non laborum deserunt
                    soluta iure ad perferendis necessitatibus, ratione totam rerum cumque.
                </p>
                <div class="animate button">
                    <a href="about.html">go to about</a>
                </div>
            </div>
            <div class="test">
                <p class="animate">
                    Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolores fugiat maxime quod dicta laborum
                    ullam minima velit reprehenderit dolorum. Blanditiis eveniet, hic nam assumenda accusantium officia
                    dignissimos ullam laboriosam odio! Lorem ipsum dolor sit amet consectetur adipisicing elit. Animi
                    explicabo blanditiis neque molestias itaque? Doloremque error, illum illo non laborum deserunt
                    soluta iure ad perferendis necessitatibus, ratione totam rerum cumque.
                </p>
                <div class="animate button">
                    <a href="about.html">go to about</a>
                </div>
            </div>
        </div>
    </main>
</body>


<script src="https://cdn.jsdelivr.net/npm/@barba/core@2.9.7/dist/barba.umd.min.js"></script>
<script src="gsap.min.js"></script>
<script src="main.js"></script>


CSS

html,
body {
    margin: 0;
    padding: 0;
    background: #e8e4da;
    color: #655f4d;
}


.loading-screen {
    position: relative;
    padding-left: 0;
    padding-right: 0;
    padding-top: 0;
    background-color: #655f4d;
    will-change: height;


    /* Loading screen from left to right */
    width: 100%;
    /* Loading screen from top to bottom */
    height: 0;
}


.load-container {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    height: 100vh;
    overflow: hidden;
    z-index: 10;
    pointer-events: none;
}


.load-img {
    opacity: 0;
    transition: all 1s cubic-bezier(0.19, 1, 0.22, 1);
    width: 150px;
    height: auto;
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 9;
    will-change: opacity;
    overflow: hidden;
}


.content-inner {
    max-width: 1000px;
    width: 90%;
    height: 100%;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    margin: 0 auto;
    padding: 100px 0;
}


h1 {
    text-transform: uppercase;
    font-family: "Cosi Azure";
    font-size: 84px;
}


.button a {
    font-family: Arial, Helvetica, sans-serif;
    text-decoration: none;
    color: black;
    border: 1px solid black;
    padding: 24px 40px;
    text-transform: uppercase;
    letter-spacing: 4px;
    font-size: 16px;
    transition: 0.3s;
}


.button {
    margin: 30px 0;
}


.button:hover a {
    background: white;
    color: darkcyan;
    transition: 0.3s;
}


.test {
    padding: 200px 0;
    height: 150vh;
}


.test p {
    font-size: 18px;
}


.img {
    width: 200px;
    height: auto;
}


.txt_wrapper {
    overflow: hidden;
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 11;
    text-align: center;
}


.txt {
    font-size: 100px;
    overflow: hidden;
    color: white;
    opacity: 0;
}


.reveal_txt {
    opacity: 1;
    transform: translate3d(0, -150px, 0);
    display: inline-block;
    transition: all 1.35s cubic-bezier(0.19, 1, 0.22, 1);
    will-change: transform;
    transform-style: preserve-3d;
}


.reveal_txt.Fadein {
    opacity: 1;
    transform: translate3d(0, 0px, 0);
}


.reveal_txt.Fadeout {
    transform: translate3d(0, 150px, 0);
}



@media screen and (max-width: 769px) {
    .txt {
        font-size: 50px;
    }
}


JavaScript

/**
 * reveal loading text and fadeout text
 */
let loadingText = document.querySelector('.txt');

function revealingText() {
    let strText = loadingText.textContent;
    let splitText = strText.split("");
    loadingText.textContent = "";

    for (let i = 0; i < splitText.length; i++) {
        loadingText.innerHTML += "<span class='reveal_txt'>" + splitText[i] + "</span>";
    }

    let txtElement = document.querySelector('.txt');
    if (txtElement.querySelector('.reveal_txt')) {
        txtElement.style.opacity = 1;
    }

    let char = 0;

    function animate() {
        const spans = loadingText.querySelectorAll(".reveal_txt");
        const span = spans[char];

        setTimeout(() => {
            span.classList.add("Fadein");
        }, char * 35);
        char++;


        if (char < splitText.length) {
            requestAnimationFrame(animate);
        } else {
            setTimeout(complete, 1500);
        }
    }
    requestAnimationFrame(animate);

    function complete() {
        const spans = loadingText.querySelectorAll(".reveal_txt");

        for (let i = 0; i < spans.length; i++) {
            const span = spans[i];
            const delay = i * 45; // Adjust the delay duration as needed

            setTimeout(() => {
                span.classList.add("Fadeout");
            }, delay);
        }
    }
}

function delay(n, callback) {
    n = n || 2000;
    return new Promise((done) => {
        setTimeout(() => {
            done(callback);
        }, n);
    });
}


function pageTransition() {
    const tl = gsap.timeline();

    tl.to(".loading-screen", {
        duration: 1.4,
        opacity: 1,
        // width: "100%", // left to right
        // left: "0%", // left to right
        height: "100%", // top to bottom
        top: "0%", // top to bottom
        ease: "Expo.easeInOut",
    })

    /**
     * Loadgin Split texts
     */
    setTimeout(() => {
        revealingText()
    }, 700);

    /**
    * Loading Img
    */
    tl.to(".load-img", {
        duration: 1,
        opacity: 1,
        // top: '50%',
        ease: "Expo.easeInOut",
    }, '-=0.5')

    tl.to(".loading-screen", {
        duration: 1.4,
        // width: "100%", // left to right
        // left: "100%", // left to right
        height: "100%", // top to bottom
        top: "100%", // top to bottom
        ease: "Expo.easeInOut",
        delay: 0.3,
    }, '+=1')


    tl.set(".load-img", { opacity: "0" }, '-=1')
    tl.set(".loading-screen", { top: "-100%" });
    // tl.set(".loading-screen", { left: "-100%" });
}

function contentAnimation() {
    var tl = gsap.timeline();
    tl.from(".animate", { duration: 0.6, y: 40, opacity: 0, stagger: 0.4, delay: 0.2 });
}

barba.init({
    sync: true,


    /**
     *  Tells Barba to prevent page “force reload” when the user clicks on an eligible link during a transition is running.
This option is useful if you don’t want your users to break running transitions, especially if they are long.
     */
    preventRunning: true,

    transitions: [
        {
            async leave(data) {
                const done = this.async();
                pageTransition();
                await delay(1000);
                done();
            },

            async enter(data) {
                window.scrollTo({
                    top: 0,
                    left: 0,
                    behavior: "smooth",
                });
                contentAnimation();
            },

            async once(data) {
                contentAnimation();
            },
        },
    ],
});



First, Barba.js requires specific data attributes in your HTML to function correctly. These attributes help Barba.js identify the different parts of your page and apply transitions accordingly.

Here's a brief explanation of the important Barba.js attributes used in the HTML:

data-barba="wrapper"
This attribute is used on the parent element that wraps all the content you want to animate. In our example, it's applied to the <body> tag. This tells Barba.js to look for page transitions within this wrapper.

data-barba="container"
This attribute is used on the main content of your page. This is the part of your page that Barba.js will replace when transitioning between pages. In our example, it's applied to the <main> tag.


Secondly, let's break down the JavaScript code to understand how it works:

Revealing Text Animation
The revealingText() function is used to create a text reveal animation. The text content is split into individual characters, each wrapped within a span element with the class reveal_txt. These span elements are then animated one by one to create a revealing text effect.

Delay Function
The delay() function is a simple utility function that returns a Promise that resolves after a specified time. This is used to create a delay in the execution of certain parts of the code.

Page Transition
The pageTransition() function is where the magic happens. Using GSAP's timeline feature, a series of animations are created. First, the loading screen is animated to cover the entire page. Then, the revealing text animation is triggered, followed by the loading image animation. Finally, the loading screen is animated to disappear, revealing the new page.

Content Animation
The contentAnimation() function is used to animate the content of the new page. Using GSAP, each element with the class animate is animated to move up and fade in.

Barba.js Initialization
Finally, Barba.js is initialized with a series of transitions. The leave transition triggers the page transition and waits for a second before allowing Barba.js to load the new page. The enter transition scrolls the page to the top and triggers the content animation. The once transition is used for the initial page load.

In conclusion, this code snippet showcases how Barba.js and GSAP can be used together to create smooth, professional-looking page transitions. By understanding how these libraries work, you can create your own custom animations to enhance the user experience on your website!

Matane!