Event listeners with barba.js and the weird bug that came with it
Setting the scene
For one of our projects we used barba.js, a small library that makes page transitions smooth and snappy. It basically makes your site feel like a single page application.
For one of our pages we implemented scroll functionality. This is done by using an event listener, which typically goes like this:
After a regular page load everything worked as expected. But after a barba.js page transition the event listener would be initialised a second time. Because there was no actual page load, the window object now had 2 identical on scroll callbacks, which completely broke the functionality we were trying to add.
Searching for a fix
So, how do you remove an event listener? This could have been done by completely overriding the ‘on scroll’ hook on the window object, which would have solved the problem. But this is a very bad way of adding functionality to the window object. By defining the window’s ‘on scroll hook’, all the other ‘on scroll functionality’ would have been replaced by this one callback. What if we wanted to change something else on scroll later? Clearly, it was not exactly what we were looking for.
What we needed to do was remove the previous event listener before we created a new one. This is made possible by using the ‘removeEventListener’ method. It requires you to pass the exact same event name and callback function to the ‘removeEventListener’ method that you used to declare the event listener with ‘addEventListener’.
This is what adding and removing an event listener would look like in a project similar to ours:
At first glance this looks to be correct, but what you see above this line won’t work. The event listener callback function will overwrite the ‘this’ keyword every time you’d use ‘this’ to access a class variable in ‘this.handleOnScroll’. And it would fail because of it. You can solve this issue by specifically binding ‘this’ to the callback function, like so:
Looking good! Now we’re able to add and remove event listeners. Let’s refactor a bit and set the event listener callback function to a global ‘window.onScrollCallback’ variable, so we can access it later in another instance of our class. Before adding an event listener, we check if the global variable exists. And if it does, that means the event listener is active, so we remove it from the window object:
Turns out the scroll events were still being duplicated. I checked if the global callback function existed in the second ‘SomethingOnScroll’ class instance, and it clearly did. Though it would keep adding new event listeners without removing any. So, as it turns out, the callback functions are not the same after all. Or are they?
I turned the event listeners documentation upside down, added console.log outputs everywhere and read more Stack Overflow posts on the subject than I’m comfortable sharing. I was convinced I had missed some vital knowledge about event listeners. And then, it finally hit me:
I thought I was adding and removing the same callback function because I was using the same global variable to declare it. I was wrong, because using the ‘Function.prototype.bind’ method creates a new instance of that function with the ‘this’ keyword set to the declared value. (Documentation)
I was adding an altered version of the globally declared callback function. That’s why it wouldn’t remove the event listener later. It simply couldn’t match both functions. Binding ‘this’ to the function, while declaring the global callback, fixed the problem entirely.
While solving this problem, I was focused on understanding event listeners because I was sure the problem originated from my lack of specific knowledge about them. Turns out I did know how they worked, and I simply overlooked the processes behind the ‘Function.prototype.bind’ function.
In this blogpost I tried to simulate my tunnel vision for you. The entire story is built around event listeners and how they work, when in reality I was looking in the wrong place for a solution.
Don’t get lost in the first explanation you come up with for an existing problem. Take a step back, zoom out and go over the flow of the program - step by step. That is how you solve problems faster and more efficiently.
Sharen is caren
Schrijf je in voor de TT-specials
Blijf jij graag op de hoogte van alles wat wij doen bij TT? Schrijf je dan hier in voor onze nieuwsbrief!