In a previous post, we delved into the Turbolinks lifecycle and how we might manipulate this to animate one page into another. As an experiment, the goal was to go a step further than fading things in and out and animate elements from one state to another, whilst persisting these elements across pages.
The problem
Whilst we achieved our goal, we did it whilst sacrificing page loading times. We delayed our server request until all our animations were complete. In practice, this is a bad UX decision as we’re intentionally increasing page load times and creating room for error (If our animations don’t end, our site is broken!).
The ideal scenario
We’d ideally animate our elements out of the page whilst making our AJAX request but delay the rendering of the new page instead. This is actually being worked on right now by Turbolinks collaborators. See proof of concept here, along with the source code. Essentially,turbolinks:before-render
is now cancelable and event.data.render()
can start the page rendering when we choose. Nice!
Until that’s (hopefully!) released and aside from altering the source code like the above example, it’s worth looking at creating general animations without interrupting the Turbolinks cycle. Thanks to this RailsScript wiki page for the concept.
Once again, we can add our animation classes as data attributes.
<h1 "data-animate-in"="animate-slide-up" "data-animate-out"="animate-slide-down">Homepage</h1>
And the Javascript.
function setupTransitions() { $(document).on('turbolinks:request-start', animateOut); $(document).on('turbolinks:load', animateIn); $(document).on('turbolinks:before-cache', removeAnimations); }
So this time we’re using turbolinks:request-start
, the latest event that happens before the request is made. We can animate our elements out here and fade in a loading animation incase the page load takes a while, then when ready, turbolinks:load
can animate elements in. There is a downside to this technique, however: the elements animating out will be interrupted whenever the response is received, so a fast page load will likely cut off these animations. This prevents us from persisting and moving elements around between pages; however, we’re not interrupting the flow of pages resulting in a much safer way of navigating.
Here’s the rest of the Javascript for a working example
let transitionTimer; function setupTransitions() { $(document).on('turbolinks:request-start', animateOut); $(document).on('turbolinks:load', animateIn); $(document).on('turbolinks:before-cache', removeAnimations); } function animateOut() { runAnimations('data-animate-out'); // Fade in our loader transitionTimer = setTimeout(() => { $('.loader').removeClass('loader--hide'); }, 500); } function animateIn() { runAnimations('data-animate-in'); // Remove animation classes once they've finished setTimeout(removeAnimations, 1000); } // Get elements by data-attr, then add this attr as a class function runAnimations(dataAttr) { $(`[${dataAttr}]`).each((ind, el) => { const $el = $(el); $el.addClass($el.attr(dataAttr)); }); } function removeAnimations() { // Clear the timeout for the loader window.clearTimeout(transitionTimer); // Clear up class names before caching const $els = $('[data-animate-out], [class*=animate-]'); removeAllAnimateClasses($els); } // Remove class names beginning with 'animate-' function removeAllAnimateClasses(els) { $(els).removeClass(function(index, className) { return (className.match(/(^|\s)animate-\S+/g) || []).join(' '); }); }
Here’s the result with a fast connection (notice the animation out cutting off):
And here’s a slower connection (with the loader):
We’ve demonstrated a few techniques now for animating with Turbolinks. You can see the source code on Github for our original animations whilst persisting across pages.
Keep your eyes peeled for a third post that dives into the possibilities of Turbolinks supporting these kinds of animations! You can sign up to our newsletter on this page to be one of the first to know when we’ve published it.