Barba.js Tutorial
You’ve got a great idea for a new website project, and you’ve decided that you are going to publish it on Awwwards.com. Your project is almost done, and you noticed that something is missing. Every page has that boring browser default loading, but you want smooth and fluid transitions between your website’s pages. What do you need? You need Barba.js.
What is Barba.js?
Barba.js is a small, easy-to-use library that helps create fluid and smooth transitions between your website’s pages. It makes your website run like a SPA (Single Page Application) and helps reduce the delay between your pages, minimize browser HTTP requests, and enhance your user’s web experience.
You will need some basic knowledge of HTML, CSS, and JavaScript. So, let’s get right into it.
Markup
Barba needs to know a little bit about your site architecture. By default, it uses this markup in your pages:
<body data-barba="wrapper"> <!-- put here content that will not change between your pages, like <header> or <nav> --> <main data-barba="container" data-barba-namespace="home"> <!-- put here the content you wish to change between your pages, like your main content <h1> or <p> --> </main> <!-- put here content that will not change between your pages, like <footer> --> </body>
Wrapper
The wrapper is the main Barba section that contains all your page structure and the Barba container. Beware, everything inside of this wrapper and outside of the container will not be updated by Barba: you can put your <header>, <footer>, or <nav> safely here. It is mainly defined on the <body> tag, but you can add it on a div or section, for example.
Container
The container defines a section in which content is updated automatically when you navigate between your pages. Beware, everything inside of this container will be updated by Barba: you can put your <footer> safely here. It is mainly defined on the <main> tag, but you can add it on a div or section, for example.
Namespace
The namespace allows you to define a unique name for each page. Barba mainly uses this namespace for Transition rules and Views.
You can easily override every data-barba attribute, but we won’t do that for this simple tutorial.
Basic Transition
One of the most important things that you will use is transitions. A transition runs between two pages of your site, leading the user to see a fluid and smooth animation instead of a browser “force reload” with a blank page.
A basic transition is made of a leave animation, which is executed when leaving the current page, and an enter animation is executed when entering the next page:
barba.init({ transitions: [{ name: 'default-transition', leave() { // create your stunning leave animation here }, enter() { // create your amazing enter animation here } }] });
Animation
The important part for a good transition is animation. As Barba is not an animation library, you will need to import one in order to animate elements on the interface to create your transition.
There is a lot of javascript animation libraries on the web, but we will use GSAP. For the demonstration, here is a basic opacity transition with gsap that consist of making the current page transparent, while the next page become opaque:
barba.init({ transitions: [{ name: 'opacity-transition', leave(data) { return gsap.to(data.current.container, { opacity: 0 }); }, enter(data) { return gsap.from(data.next.container, { opacity: 0 }); } }] });
Custom Code
If you need to run onload events, you can trigger them with Hooks in transitions, but if you need custom code for a specific page, the correct way is to use Views. Views are conditioned by a unique page namespace.
barba.init({ views: [{ namespace: 'home', beforeEnter() { // update the menu based on user navigation menu.update(); }, afterEnter() { // refresh the parallax based on new page content parallax.refresh(); } }] });
Lifecycle
Barba makes your site work like a Single Page Application (SPA), allowing you to create smooth transitions without having to reload the whole site. Barba’s default behavior:
- prefetch the next page
- store it in the cache
- add the data-barba=”container” of the next page at the end of the data-barba=”wrapper” element
- remove the current data-barba=”container” from the DOM when transition is done
Here is a small diagram that describe Barba’s main concept when navigating between two pages:
During the transition process, Barba doesn’t apply any styles to the container, so you need to manage it by yourself with an animation library, this is where the magic happen.
If you followed this tutorial and set up everything as shown, you probably noticed that the new page is jumping when the old container fades out. That is because both the new and old containers are in the DOM structure at some point. And it is necessary that the old container is positioned absolute, so that the new container is “above” the old one, not under it. To fix that, we will change the leave hook a little bit:
leave(data) { // ADD THIS data.current.container.style.position = absolute; data.current.container.style.top = 0; data.current.container.style.left = 0; data.current.container.style.width = '100%'; return gsap.to(data.current.container, { opacity: 0 }); },
You can download this simple example here, or you can visit the official Barba.js documentation for more detailed and advanced examples.
Barba.js & GSAP Advanced Animation
Now, let’s build a more complex transition between pages. First, download our “simple example,” and we will continue there.
Since we have only two pages, having at least one more would be nice. Let’s rename our page.html to about-us.html and add a new one named contact.html. After that, replace the about-us.html HTML code:
<nav> <ul> <li><a href="index.html">Home</a></li> <li class="current-menu-item"><a href="about-us.html">About Us</a></li> <li><a href="contact.html">Contact</a></li> </ul> </nav> <main data-barba="container" data-barba-namespace="about-us"> <div class="container"> <p>This is About Us page.</p> </div> </main>
Now, you can copy HTML code from the About Us page, paste it into the Contact page, and replace the .current-menu-item class, data-barba-namespace attribute, and “This is Contact page.” paragraph.
You may notice that the <nav> tag is outside the .barba-container element, meaning it will not update on the page transition. That’s why we added updateMenu() function. But now that we have more than two pages, the function does not work properly because it always sets the .current-menu-item class on the Home or About Us navigation item.
To fix that, in index.html (and CSS), change the data-barba-namespace attribute value to “index” because we will compare the namespace with the href attribute in the navigation permalink. Now we can update the updateMenu() function and fix Barba transition functions:
... leave(data) { data.current.container.style.position = 'absolute'; data.current.container.style.top = 0; data.current.container.style.left = 0; data.current.container.style.width = '100%'; // REMOVE THIS updateMenu(); return gsap.to(data.current.container, { opacity: 0 }); }, beforeEnter(data) { // ADD THIS updateMenu(data.next.namespace); }, ... function updateMenu(namespace) { const activeNavItem = document.querySelector('nav li.current-menu-item'), currentNavItemLink = document.querySelector('nav li:not(.current-menu-item) a[href="' + namespace + '.html"]'); if (currentNavItemLink) { const currentNavItem = currentNavItemLink.parentElement; if (currentNavItemLink !== activeNavItem) { if (activeNavItem) activeNavItem.classList.remove('current-menu-item'); currentNavItem.classList.add('current-menu-item'); } } }
Remember to update the script on every HTML file because Barba.js loads scripts that are present in the DOM only on the first load. Or copy-paste the script code into a new script.js file and replace the script code in every file with the following:
<script type="text/javascript" src="js/script.js" id="main-js"></script>
You can do the same for the styling inside of the <head> tag.
“Wow, this website is so cool!” – you are probably think that right now, but let’s give it a little bit of text and design… Bim, bim, bam, bam! Done! Don’t worry. You will have everything for the download at the end of this post.
If you added enough text and spaces for the page to have a scrollbar, you probably noticed that if you scroll down and switch to another page with less content, there is a blank space that disappears after the page transition, and the page is not scrolled to the top. We can fix that by setting window scrollbar position to 0 right after the current Barba container leaves:
afterLeave(data) { window.scrollTo(0, 0); },
Great! Now we have a functioning website without bugs (I hope), and we can finally start building a complex page transition animation.
First, let’s add some HTML elements that we will use for the transition right after the opening <body> tag:
<div class="transitioner"> <div class="transitioner-layer"></div> </div>
Then, add some styling for this “transitioner”:
.transitioner { position: fixed; inset: 0; z-index: 10000; /* We want it above everything */ overflow: hidden; } span.transitioner-layer { display: block; position: absolute; inset: 0; background-color: lightyellow; --polygon-increment-1: 0%; --polygon-increment-2: 0%; --polygon-increment-3: 0%; --polygon-increment-4: 0%; --polygon-increment-5: 0%; --polygon-increment-6: 0%; clip-path: polygon( calc(100% - var(--polygon-increment-1)) calc(100% / 11 * 1), 50% calc(100% / 11 * 1), calc(100% - var(--polygon-increment-2)) calc(100% / 11 * 1), calc(100% - var(--polygon-increment-2)) calc(100% / 11 * 2), 50% calc(100% / 11 * 2), calc(100% - var(--polygon-increment-3)) calc(100% / 11 * 2), calc(100% - var(--polygon-increment-3)) calc(100% / 11 * 3), 50% calc(100% / 11 * 3), calc(100% - var(--polygon-increment-4)) calc(100% / 11 * 3), calc(100% - var(--polygon-increment-4)) calc(100% / 11 * 4), 50% calc(100% / 11 * 4), calc(100% - var(--polygon-increment-5)) calc(100% / 11 * 4), calc(100% - var(--polygon-increment-5)) calc(100% / 11 * 5), 50% calc(100% / 11 * 5), calc(100% - var(--polygon-increment-6)) calc(100% / 11 * 5), calc(100% - var(--polygon-increment-6)) calc(100% / 11 * 6), 50% calc(100% / 11 * 6), calc(100% - var(--polygon-increment-5)) calc(100% / 11 * 6), calc(100% - var(--polygon-increment-5)) calc(100% / 11 * 7), 50% calc(100% / 11 * 7), calc(100% - var(--polygon-increment-4)) calc(100% / 11 * 7), calc(100% - var(--polygon-increment-4)) calc(100% / 11 * 8), 50% calc(100% / 11 * 8), calc(100% - var(--polygon-increment-3)) calc(100% / 11 * 8), calc(100% - var(--polygon-increment-3)) calc(100% / 11 * 9), 50% calc(100% / 11 * 9), calc(100% - var(--polygon-increment-2)) calc(100% / 11 * 9), calc(100% - var(--polygon-increment-2)) calc(100% / 11 * 10), 50% calc(100% / 11 * 10), calc(100% - var(--polygon-increment-1)) calc(100% / 11 * 10), calc(100% - var(--polygon-increment-1)) calc(100% / 11 * 11), calc(0% + var(--polygon-increment-1)) calc(100% / 11 * 11), calc(0% + var(--polygon-increment-1)) calc(100% / 11 * 10), 50% calc(100% / 11 * 10), calc(0% + var(--polygon-increment-2)) calc(100% / 11 * 10), calc(0% + var(--polygon-increment-2)) calc(100% / 11 * 9), 50% calc(100% / 11 * 9), calc(0% + var(--polygon-increment-3)) calc(100% / 11 * 9), calc(0% + var(--polygon-increment-3)) calc(100% / 11 * 8), 50% calc(100% / 11 * 8), calc(0% + var(--polygon-increment-4)) calc(100% / 11 * 8), calc(0% + var(--polygon-increment-4)) calc(100% / 11 * 7), 50% calc(100% / 11 * 7), calc(0% + var(--polygon-increment-5)) calc(100% / 11 * 7), calc(0% + var(--polygon-increment-5)) calc(100% / 11 * 6), 50% calc(100% / 11 * 6), calc(0% + var(--polygon-increment-6)) calc(100% / 11 * 6), calc(0% + var(--polygon-increment-6)) calc(100% / 11 * 5), 50% calc(100% / 11 * 5), calc(0% + var(--polygon-increment-5)) calc(100% / 11 * 5), calc(0% + var(--polygon-increment-5)) calc(100% / 11 * 4), 50% calc(100% / 11 * 4), calc(0% + var(--polygon-increment-4)) calc(100% / 11 * 4), calc(0% + var(--polygon-increment-4)) calc(100% / 11 * 3), 50% calc(100% / 11 * 3), calc(0% + var(--polygon-increment-3)) calc(100% / 11 * 3), calc(0% + var(--polygon-increment-3)) calc(100% / 11 * 2), 50% calc(100% / 11 * 2), calc(0% + var(--polygon-increment-2)) calc(100% / 11 * 2), calc(0% + var(--polygon-increment-2)) calc(100% / 11 * 1), 50% calc(100% / 11 * 1), calc(0% + var(--polygon-increment-1)) calc(100% / 11 * 1), calc(0% + var(--polygon-increment-1)) calc(100% / 11 * 0), calc(100% - var(--polygon-increment-1)) calc(100% / 11 * 0) ); }
What!? We said it would be a “complex” animation.
And now, let’s change the transitioning animation in JS. We will use the GSAP Timeline for this. First, we will update the “leave” transition. Replace:
return gsap.to(data.current.container, { opacity: 0 });
With this:
const transitionerLayer = document.body.querySelector('.transitioner-layer'); transitioner.parentElement.style.display = 'block'; const transitionerTimeline = gsap.timeline(); transitionerTimeline.to(transitionerLayer, { duration: 0.5, '--polygon-increment-1': '0%' }); transitionerTimeline.to(transitionerLayer, { duration: 0.5, '--polygon-increment-2': '0%' }, '<16.66666667%'); transitionerTimeline.to(transitionerLayer, { duration: 0.5, '--polygon-increment-3': '0%' }, '<16.66666667%'); transitionerTimeline.to(transitionerLayer, { duration: 0.5, '--polygon-increment-4': '0%' }, '<16.66666667%'); transitionerTimeline.to(transitionerLayer, { duration: 0.5, '--polygon-increment-5': '0%' }, '<16.66666667%'); return transitionerTimeline.to(transitionerLayer, { duration: 0.5, '--polygon-increment-6': '0%' }, '<16.66666667%');
After that, update the “enter” transition. Replace:
return gsap.from(data.next.container, { opacity: 0 });
With following:
const transitionerLayer = document.body.querySelector('.transitioner-layer'); const transitionerTimeline = gsap.timeline({ onComplete: function() { transitionerLayer.parentElement.style.display = 'none'; } }); transitionerTimeline.to(transitionerLayer, { duration: 0.5, '--polygon-increment-6': '50%' }); transitionerTimeline.to(transitionerLayer, { duration: 0.5, '--polygon-increment-5': '50%' }, '<16.66666667%'); transitionerTimeline.to(transitionerLayer, { duration: 0.5, '--polygon-increment-4': '50%' }, '<16.66666667%'); transitionerTimeline.to(transitionerLayer, { duration: 0.5, '--polygon-increment-3': '50%' }, '<16.66666667%'); transitionerTimeline.to(transitionerLayer, { duration: 0.5, '--polygon-increment-2': '50%' }, '<16.66666667%'); return transitionerTimeline.to(transitionerLayer, { duration: 0.5, '--polygon-increment-1': '50%' }, '<16.66666667%');
Now, you can test the animation.
…
Yes, you only see a light-yellow screen. Why? Because we set in CSS that the transitioner is over the whole screen, and the transitioner should not be visible on the first page load.
There are two ways to fix this. The first way is to fix CSS so the transitioner has the exact style it would have once the page transition is over. But since we want to have “enter” animation on the first load, too, we will add Barba.js once() hook, which triggers only on the first-page load, and we will add the same code we added for entering animation.
And that’s it!
We can do some polishing, like hiding the scrollbar so the user can not scroll while the transition is animation. We can do that by adding the .transitioning class to the body after every page leave and removing it on every page enter.
... before(data) { document.body.classList.add('transitioning'); }, ... after(data) { document.body.classList.remove('transitioning'); }
But since we have the transition on the first-page load, we also have to add the .transitioning class to the <body> tag directly in every HTML file and add the Barba.js afterOnce() hook:
afterOnce(data) { document.body.classList.remove('transitioning'); },
That’s it for now! You can download the updated example here.
Note: If Barba.js is not working, put example files on a real server or localhost environment.
If you are interested in more tutorials, check out how to set up a WordPress theme, or if you are into animations, you can read how to start making cool animations on the website.
Happy coding!