loading hour glass

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


Also published on Medium.

2 thoughts on “Loading Experience

  1. Adam Marsden says:

    Awesome job! Love that you’ve removed jQuery as a dependency.

    I made a change to the CSS & JS on my post so the content is accessible even if JS is disabled now.

    1. Paul Kinchla says:

      Thanks for sharing your technique Adam!

Comments are closed.