Implement barba.js with WordPress

Many tutorials explain how to make page transitions with Barba.js. While they show something nice, in this article, I will show you the difficulties I faced when I applied Barba inside a WordPress project and how I solved them.

What creates the problem?

  1. WordPress already has header and footer templates which makes the developer a bit confused about where to put the wrapper and the container.
  2. WordPress already generates some HTML tags and attributes that need to take care of. For example, if you are depending on the body tag classes to customize your templates design, you will get surprised when you see your body class is not updating properly.
  3. scripts in the new page do not get executed because they are already out of Barba wrapper.

The Solutions

For the first point, I will show an example taking in mind that the top menu could be different in some pages, In other word, we want the top menu to be updated.

Regarding the second point, we should make sure that we are updating the body class attribute when we receive the new page server response.

Structuring Barba.js in WordPress

According to Barba.js documentation, we should include two HTML elements to make it works.

Required tags for barba.js
<div id="barba-wrapper">
    <div class="barba-container">
        ... content to be changed
    </div>
</div>

The wrapper is the main Barba section that contains all your page structure, so everything inside of this wrapper and outside of the container will not be updated by Barba.

The container defines a section in which content is updated automatically when you navigate between your pages.

So basically, since we are going to update the top menu, it should be inside the container. For that, we should separate the top menu template from the header template and here is an example:

header.php template
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>
<div id="barba-wrapper">
footer.php template
</div>
<?php wp_footer(); ?>
</body>
</html>

Note that the #barba-wrapper element is opened in the header.php template and closed in the footer.php template

Also, note that the wp_footer() function has been called after closing #barba-wrapper which means that if you are calling your scripts in footer, they will not be reloaded on navigating to a new page. This will make your page transition much faster than the regular navigation.

Now, let us create our top menu template and include it in (for example) single.php template

topmenu.php template
<header>
    <nav>
        <!-- Setup your top menu here //-->
    </nav>
</header>
single.php template
<div class="barba-container">
    <?php get_template_part('topmenu'); ?>
    <!-- The rest of your single.php content //-->
</div>

With that, we have solved the issue number one. We have just separated the content that the user will see than the HTML tags that should be generated in header.php

Secondly, we will have to update the body classes according to the new page. Barba comes with some events you can explore here.

One of the events provided by Barba is newPageReady. It fires when the new container has been loaded and injected in the wrapper. This function takes 4 parameters: currentStatus, prevStatus, HTMLElementContainer and newPageRawHTML.

Using the newPageReady Event
Barba.Dispatcher.on('newPageReady', function(currentStatus, oldStatus, container, newPageRawHTML) {
    var response = newPageRawHTML.replace(/(<\/?)body( .+?)?>/gi, '$1notbody$2>', newPageRawHTML);
    var bodyClasses = $(response).filter('notbody').attr('class');
    $('body').attr('class', bodyClasses);
});

Firing Scripts

This problem is the main reason that lets developers think twice about using Barba in WordPress. Even if we already know from the beginning that we are going to use Barba in our project and also even if we structured our scripts based on that approach, the plugins that we may add later in our project, may add more scripts to our theme, making it unpredictable to which scripts are included and also where we should call the scripts in the new page.

Well, you need to fire the scripts again. I myself do this on my website. After updating the body classes, I check out which class it has, and based on that, I run a part of my JS code. Let me give you a hint on how I structured my app.js file:

A suggested structure for app.js
var scripts = {
    init: function () {
        if ($('body').hasClass('page-template-homepage')) {
	    this.homepage();
	} else if ($('body').hasClass('single-post')) {
	    this.single_post();
	}
    },
    homepage: function () {
        // homepage scripts
    },
    single_post: function () {
        // single post script
    }
};

The scripts.init() will be called when the user lands to your website for the first time and also when you make a transition to a new page. With that, you execute only the required script on each page. So your Barba dispatcher will look like:

Recall scripts.init()
Barba.Dispatcher.on('newPageReady', function(currentStatus, oldStatus, container, newPageRawHTML) {
    var response = newPageRawHTML.replace(/(<\/?)body( .+?)?>/gi, '$1notbody$2>', newPageRawHTML);
    var bodyClasses = $(response).filter('notbody').attr('class');
    $('body').attr('class', bodyClasses);
    // After updating body classes, we recall the init function
    scripts.init();
});

This will not only organize your script but will improve the performance too. You may also add a global object that is called on each page

What about scripts files

So far we have solved most of the cases that you may face, but still may face two other issues:

  • When you get scripts in the container of the new page.
  • When there is a script that is in the footer of the newly requested page

Actually the second issue is just more comprehensive than the first one, hence, we are going to use one solution for them. I just mentioned them both to let you know what may occur + willing to hear your ideas about better solutions.

When a visitor lands to your site – regardless the first page he enters – a couple of scripts will be executed, let us store our scripts src(s) inside an array by looping through all the script tags like this:

Place it at the end of your app.js
var initialized_scripts = [];
var script_tags = document.getElementsByTagName("script");
$(script_tags).each(function(i, s) {
    var src = $(s).attr('src');
    if (src) {
        initialized_scripts.push(src);
    }
});

Now, and when the user visits another page, we will loop again looking for any other script that needs to be imported or evaluated. So within the Barba newPageReady dispatcher, we will get the scripts like this:

Looking for new scripts
var new_imports = [];
var new_evaluations = '';
var script_tags = $(response).find('script');
$(script_tags).each(function(i, s) {
    var src = $(s).attr('src');
    if (src) {
        // if not already initialized add it
        if (initialized_scripts.indexOf(src) == -1) {
            new_imports.push(src);
        }
    } else {
        // it is an inline script, will evaluate it
        new_evaluations += script_tags[i].innerHTML;
    }
});

So far, we have an array with new scripts and another variable that includes all the new inline scripts.

Now, we will create a function that gets all the script files inside new_imports array

Get multiple scripts
$.getMultiScripts = function(arr) {
    var _arr = $.map(arr, function(src) {
        return $.getScript(src);
    });
    _arr.push($.Deferred(function( deferred ){
        $( deferred.resolve );
    }));
    return $.when.apply($, _arr);
}

This function is based on jQuery $.getScript function, it will resolve when all scripts are loaded. Basically, we will use it like this:

Using $.getMultiScripts
$.getMultiScripts(script_array).done(function() {
     // all done
}).fail(function(error) {
     // one or more scripts failed to load
}).always(function() {
     // always called, both on success and error
});

With that, let us call this function

Using $.getMultiScripts
$.getMultiScripts(new_imports).done(function() {
    eval(new_evaluations);
}).fail(function(error) {
    // one or more scripts failed to load
}).always(function() {
    // always called, both on success and error
});

What we are doing is that we load all (and only) the new scripts, and after that we evaluate the inline scripts.

Note: If you are using barba v2, please refer to this article

Also feel free to check this Github repository.

Finally, here are some websites that I made with WordPress, and used Barba for navigation

  1. https://www.fjobeir.com
  2. https://www.property2invest.com
  3. http://www.mkgroup.com.sa/

Latest Comments

lasixSmume

I’m not sure where you’re getting your information, but great topic. I needs to spend some time learning more or understanding more.
Thanks for excellent information I was looking for this info for my mission.

Reply
tracfone special

Hey there would you mind sharing which blog platform you’re working with?
I’m looking to start my own blog in the near future but I’m
having a hard time making a decision between BlogEngine/Wordpress/B2evolution and Drupal.

The reason I ask is because your layout seems different then most blogs and I’m looking for something unique.
P.S My apologies for getting off-topic but I had to
ask!

Reply
Tarun Patnayak

Hi Feras, Thanks for this guide.
I first actually saw this video on youtube ( https://www.youtube.com/watch?v=aMucZErEdZg&t=266s )

And what I did was I set attribute to body and page container using jquerys attr method.
And tried executing the code as showed in video. It somehow worked, I mean, as I click a link and the animation triggers and then the animation ends but instead of going to the targeted page, the website reloads and I’m on the same page.

Can you please guide me.

here’s the code –
function pageTransition() {

var tl = gsap.timeline();

tl.to(‘ul.transition li’, { duration: .5, scaleY: 1, transformOrigin: “bottom left”, stagger: .2 })
tl.to(‘ul.transition li’, { duration: .5, scaleY: 0, transformOrigin: “bottom left”, stagger: .1, delay: .1 })
}

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

barba.init({

sync: true,

transitions: [{

async leave(data) {

const done = this.async();

pageTransition();
await delay(1500);
done();
}

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

Thanks in advance.

Reply
Patsy

I blog quite often and I really appreciate your information. This article has truly peaked my interest.
I am going to take a note of your website and keep checking
for new information about once per week. I opted in for your Feed too.

Reply
FGO

Have you ever considered writing an e-book or guest authoring on other blogs? I have a blog centered on the same topics you discuss and would really like to have you share some stories/information. I know my audience would value your work. If you are even remotely interested, feel free to shoot me an email.|

Reply
Diesel

Hey would you mind letting me know which webhost you’re working with?
I’ve loaded your blog in 3 different internet browsers and I must say this blog loads
a lot faster then most. Can you suggest a good hosting provider at a fair price?
Thank you, I appreciate it!

Reply
Feras

Hi Diesel, I do not think it is about the hosting, I developed my website theme by myself, so it has the most minimum functionalities, I do not have many plugins and I use Barba prefetching and server cache. This combination will make your website way faster

Santiago

I really like it when people get together and share ideas.

Great site, continue the good work!

Reply
Filozofia Nauka

Seriously Thank you for your help, this site has been a great relief from the books,

Reply
Metale Kristine

very helpful post. Thanks again.love the blog. very interesting. sincerly, Kristine

Reply
Bali web design

thanks for your tutorial, this is what i looking for, I am currently creating a website using WP and barba.js

Reply
Feras

I hope it will help you to make something cool

galinabublik

Thank you very much for this info!
What you can say about custom ajax + barba
I want submit a form of filter custom post type by ajax and change url by js by window.history.push()

Did you do some like that? Will it work?

Reply
Feras Jobeir

Thanks for your comment, After submitting your form, use this function Barba.Pjax.goTo('https://www.fjobeir.com');

Ivan

How to import barba? Because in my app.js I am getting error that Barba is not define, any solution?

Reply
mateusbapt

Hi Feras, great and useful post. Thanks for this.

Would you have any sample code working / source of this on Git?

Reply
tomettom

Thank for the quick response!
Since I’m using V2 of Barba, I figured the structure would be more like:

barba.hooks.beforeEnter(() => {
var parser = new DOMParser();
var htmlDoc = parser.parseFromString(
newPageRawHTML.replace(
/(/gi,
“$1notbody$2>”,
newPageRawHTML
),
“text/html”
);
var bodyClasses = htmlDoc.querySelector(“notbody”).getAttribute(“class”);
body.setAttribute(“class”, bodyClasses);
});

But it doesn’t work

Reply
Feras Jobeir

Hello again, this is not for V2 actually, the concept is different there

Feras Jobeir

I will write another post for Barba 2, but unfortunately, not on my close schedule. Will contact you once I post it.

tomettom

Could you help me adapt this code for Vanilla JS?
Barba.Dispatcher.on(‘newPageReady’, function(currentStatus, oldStatus, container, newPageRawHTML) {
var response = newPageRawHTML.replace(/(/gi, ‘$1notbody$2>’, newPageRawHTML);
var bodyClasses = $(response).filter(‘notbody’).attr(‘class’);
$(‘body’).attr(‘class’, bodyClasses);
});

Reply
Feras Jobeir

Hello my friend, would you please try this
Barba.Dispatcher.on('newPageReady', function(currentStatus, oldStatus, container, newPageRawHTML) {
var parser = new DOMParser();
var htmlDoc = parser.parseFromString(newPageRawHTML.replace(/(<\/?)body( .+?)?>/gi, '$1notbody$2>', newPageRawHTML), 'text/html');
var bodyClasses = htmlDoc.querySelector('notbody').getAttribute('class');
body.setAttribute('class', bodyClasses);
});

Feras Jobeir

Happy to hear that dear

Reply
Norsky

Thanks man
Though it didn’t completly solved my problem (basically because of different css files on different pages probably, the layout of the page get broken on navigation, using siteorigin page builder).

Appreciate your effort 🙂

Reply
Feras Jobeir

Hello Norsky
The inline styles should be parsed automatically by the browser. For link tags, you can use the jQuery appendTo function …. I will try to update the post and provide an example soon.

Leave a Reply

Your email address will not be published. Required fields are marked *