How I managed to create a powerful positioning system

This is an introduction post about the new major release of Popper.js, if you don’t care about its history, jump to the “Popper.js v1” section.

In the past, I spent several hours writing the same code over and over to position my tooltips and popovers inside web applications. Every time I worked on a new project, there were different necessities on how the elements should be positioned on the screen according to different environmental factors.

I reached the apex while I was working on a big Ember.js application that thanks to some bad UX decisions, wanted to show a tooltip or a popover on almost any element you hovered.

In such a code base, we started forking the tooltip system of Bootstrap 3, and incrementally hacked to add new features.

An important requirement was the ability to position a tooltip without having to move its DOM node as a direct child of body, this is because with the ancient version of the framework we used would have broken the whole app.

Finding an existing solution

After almost a year working on this project and maintaining our custom implementation, I decided to switch to Tether, since it seemed very powerful and stable.

Unfortunately, after having played with it for several hours, its limitations began to emerge. It automatically manipulated the DOM node, moving it as a direct child of body. It added classes and inline styles without ways to control them. It also lacked some core features we needed from a positioning engine and the performance was not optimal.

I spent a bunch of hours digging through GitHub in search for alternatives, but I literally found nothing. It seemed like nobody ever needed a library like that, or that nobody managed to create one good enough to be released.

I decided to dedicate a weekend to rewrite our whole positioning engine from scratch. The result was Popper.js, an open source JavaScript library that worked pretty well on all the cases we faced in our app.

Popper.js v0

In its infancy, the library was just a single Javascript file written with several functions, focusing on keeping it small and lightweight.

There were some automated functional tests, but they didn’t cover the countless use cases.

I wanted to make it easily extensible, so I based it on a middleware system. The idea was to compute the position of the element and so let the middlewares take care to modify it according to the requirements.
For example, once it has the position of the popper and the defined boundaries; a modifier can detect if the popper will overflow and flip its placement to avoid to being cut off.

After the release I got some awesome feedback from the community and some great people helped me fix several bugs and introduce new features.
Some big companies like Atlassian and Microsoft started using it and were able to contribute to the source code and introduce new features like the support for shadow DOM.

While working on it, I realized that we needed a better code structure to be able to maintain it effectively.

This is where the development of version 1 started.

Popper.js v1

To organize the code base, I decided to switch to ES6 modules.
I added Rollup as code bundler and Babel as a transpiler, I also moved the automated tests from a headless Chrome setup to a SauceLabs cross-browser test suite.

During the development, I divided the functions and the modifiers/middlewares in their own files to be able to reuse them across the library. This helped to reduce the size since it allowed me to reuse much more code than previously.

The update process

I also refactored the whole update process, which is the action that gets called every time Popper needs to update the position of an element.

This process gets called on each frame, so it has to work at 60fps to keep the experience fluid. To make it possible, the whole process must be lean and avoid to access to the DOM if not strictly necessary.

This is how it works:

Me trying to draw a flowchart. Disclaimer: colors are completely random

We managed to use as little as 0.5ms for each frame (on my 2015 MacBook). We throttle it to avoid useless computations that would get ignored by the browser if executed more than once per frame. This makes it possible to compute the position of a popper during the scroll of the page or an animation without lag.

Below you can find a capture I did while I was scrolling the container up and down several times repeatedly.

Timeline captured during intensive scrolling, pure art, I know 😎

I’m very satisfied by the result!
Just 102.2ms used on a total time of 2.86 seconds, only 25ms spent during rendering and 27ms during painting.

Reducing the library size

Another important point I always wanted to keep in mind is the size of the library. Popper.js v1, without the built-in modifiers, weights just ~10kB minified. When it gets gzipped, the weight goes down to 3.76kB!

If we add the built-in modifiers, which add a lot of additional features to the core, we reach ~17kB minified, and just 5.83 kB once gzipped. That’s pretty impressive, considering that Tether weights 25kB minified and 7.52kB gzipped supporting way less features.

To make sure the minified builds weighed as little as possible, I switched from Uglify to Babili. This made possible to squeeze every bit during the minification process.

Demo time!

After all these words, below you can finally find a demo of Popper.js in action.

This is an animated SVG created by Sarah Drasner, I added a tooltip below the pendulum to show you how it handles the situation. In this case the tooltip element is in a different context from the one of the pendulum, this means that the position is completely computed by Popper.js without any help from CSS.

CodePen credits to Sarah Drasner.

React, Vue.js & co.

While I worked on the new release, I changed my job position, from Ember to a React code base (thank god!).

This allowed me to understand problems that Popper.js needed to take in account in order to support React or any other view library that wants to directly control the DOM manipulation.

With this in mind, Popper.js v1 now concentrates all the DOM manipulations into a single modifier, the applyStyle one.
This makes it possible to disable and replace it with a version compatible with React or any other view library.

I wanted to have some examples ready for the launch of the new version, so I asked Travis (souporserious) Arnold, the author of react-tether to take a look at Popper.js and consider to develop a wrapper around it like he did with Tether.
He was very impressed by Popper and decided to create react-popper , it’s still in early stage but I count to see great progresses now that Popper.js v1 has been officially released!

In the README of Popper.js you can find a section where I collected the libraries that allow you to use it in various frameworks and libraries, feel free to contact me if you created one and you want to see it listed there!

I really hope you will like what we did with Popper.js, I’m sure it will save a lot of time to a bunch of Front-End Developers and I hope to see new tooltip and popover libraries based on it that will finally be able to focus on the features they want to add instead of trying to reinvent the wheel every time.

I’m eager to read your feedbacks in the comments!

Thanks Travis Arnold for having helped me with the review of this article.
Thanks Sarah Drasner for the original animated SVG.