Loading Experience

While reading Designer News this week I came across this post: Minimal Page Transitions with jQuery & CSS. The article outlines a simple technique for implementing animated page transitions. What I found most appealing about this technique was the low overhead for the implementation. No need for a front-end framework with a router or trying to juggle loading pages with AJAX and the history API. A simple enhancement that can improve the experience of loading pages. There were a few comments on the post discussing two things that piqued my interest:

  • The implementation required JavaScript to access the content
  • jQuery was a dependency

Disclaimer: Using jQuery in 2017 is perfectly acceptable. Don’t hate the player — hate the game. If your implementation is performant and accessible then what framework or library you use or do not use is irrelevant.

I thought it would be fun to come up with my own solution that didn’t require jQuery or block content if javaScript was unavailable.

The Code

So the first note points out that JavaScript is required to access the content. This is related to how the CSS animations are applied to the HTML element. From the article:

html {
animation: fadeSiteIn 0.5s ease forwards; /* [1] */
}

@keyframes fadeSiteIn { /* [1] */
from { opacity: 0; }
to { opacity: 1; }
}

This rule set by default hides all content on the page. This can be easily avoided by adding a JavaScript qualifier to the rule set. With the following test we make sure that the CSS for page transitions are only applied if JavaScript is available:

!function(){
"querySelector"in window.document&&"addEventListener"in window&&(window.document.documentElement.className+="js")
}();

So if JavaScript is available our HTML element will have a class of .js (our qualifier). With the following rule set we make sure that our content is viewable under all conditions:

html.js {
animation: fadeSiteIn 0.5s ease forwards; /* [1] */
animation-delay:.15s; /*optional addition for folks that load CSS in a non blocking manner*/
}

Optionally you can add an animation delay if you load CSS in a non-blocking manner. This may alter your perceived load time depending on many factors.

That brings us to using jQuery as a dependency. The following is my remix written without dependencies:

// Function to animate the scroll
function smoothScroll(anchor, duration) {

// Calculate how far and how fast to scroll
var startLocation = window.pageYOffset;
var endLocation = anchor.offsetTop;
var distance = endLocation - startLocation;
var increments = distance/(duration/16);
var stopAnimation;

// Scroll the page by an increment, and check if it's time to stop
var animateScroll = function () {
window.scrollBy(0, increments);
stopAnimation();
};

// If scrolling down
if ( increments >= 0 ) {
// Stop animation when you reach the anchor OR the bottom of the page
stopAnimation = function () {
var travelled = window.pageYOffset;
if ((travelled >= (endLocation - increments)) || ((window.innerHeight + travelled) >= document.body.offsetHeight) ) {
clearInterval(runAnimation);
}
};
}
// If scrolling up
else {
// Stop animation when you reach the anchor OR the top of the page
stopAnimation = function () {
var travelled = window.pageYOffset;
if ( travelled <= (endLocation || 0) ) {
clearInterval(runAnimation);
}
};
}
// Loop the animation function
var runAnimation = setInterval(animateScroll, 16);
};

// fix back functionality in safari
window.onpageshow = function(event) {
if (event.persisted) {
window.location.reload()
}
};

//select all links on the page
var links = document.querySelectorAll('a');

for (var i = 0; i < links.length; i++) {
if (location.hostname === links[i].hostname || !links[i].hostname.length ) {
if(links[i].href.match('#')){
smoothScroll(links[i], 250)
}
else if(links[i].href.match('mailto')){
// act like a mailto link
}
else {
links[i].addEventListener('click', function(e){
var self = this;
e.preventDefault()
addClass(htmlEl, 'exit')
setTimeout(function() {
window.location = self
}, 350)

})
}
}
}

This works pretty much the same as the example in the article with two exceptions. One — we listen on the onpageshow event for issues event.persisted. If the page has issues loading we reload the page. Specifically in mobile and desktop Safari this occurs when you use the back button. Two—we broke out the scroll animation into its own function. This is a preference that encourages reuse and decouples functionality.

Wrap it Up

I have implemented an altered version of this code on my site this week as a test case and so far I am pretty happy with the results. I get a single page app like experience without any of the overhead.

Update: So the original article has been updated to allow access to the content when JavaScript is unavailable. Progressive enhancement for the win: Minimal Page Transitions with jQuery & CSS


Originally published at Paul Kinchla