Creating a simple page transition using CSS and JavaScript
Created: Sept 1st, 16'
Transitions are used everywhere on the web, popularized with the advent of Ajax, where a "spinner" was often part of the expected UI that shows up while content is being fetched. Today we see transitions making an entrance (or exit for that matter) in all types of scenarios, one of them being while a page is loading. You saw an example of this while loading this page- reload to see it again, or click here for another example of a page transition:.
In this tutorial, lets see how to build such a page transition using JavaScript and CSS3, and along the way admire the power of CSS3 transitions and keyframe animations in making the whole affair extremely lightweight.
Building the basic interface/ markup
We'll begin by creating the UI for the page transition- a DIV that covers and overlays the entire page:
/* The CSS */ #pageloader{ position: fixed; left: 0; top: 0; width: 100%; height: 100%; display: flex; justify-content: center; /* center children content horizontally */ align-items: center; /* center children content vertically */ z-index: 10000; background: white; }
<!-- Markup --> <div id="pageloader"> <!- Add spinner markup later --> </div>
The "#pageloader
" DIV uses position:fixed
to ensure it spans every inch of the browser window, and
CSS Flexbox to center any children content added inside the DIV.
Note: For brevity certain CSS vendor prefixes are omitted from the illustrated CSS, though they are present inside the actual demo.
Moving on, we now need a spinner or loading animation to show inside the DIV to indicate to visitors that the page is being loaded. Instead of starting from scratch, we'll simply take advantage of one of the pre-coded CSS3 based spinners from Spinkit, settling on the familiar 3 circles spinner:
/* The CSS */ .spinner { width: 70px; text-align: center; opacity: 1; transition: opacity .4s; } .spinner > div { width: 18px; height: 18px; background-color: #333; border-radius: 100%; display: inline-block; -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both; animation: sk-bouncedelay 1.4s infinite ease-in-out both; } .spinner .bounce1 { -webkit-animation-delay: -0.32s; animation-delay: -0.32s; } .spinner .bounce2 { -webkit-animation-delay: -0.16s; animation-delay: -0.16s; } @keyframes sk-bouncedelay { 0%, 80%, 100% { -webkit-transform: scale(0); transform: scale(0); } 40% { -webkit-transform: scale(1.0); transform: scale(1.0); } }
<!-- Markup --> <div id="pageloader"> <div class="spinner"> <div class="bounce1"></div> <div class="bounce2"></div> <div class="bounce3"></div> </div> </div>
Inisde the #pageloade
r DIV, we simply drop in the markup for the
spinner as provided straight into it, and call it a day (ok, half a
day, we still need to implement the JavaScript logic). And here's
the end result so far, a DIV that covers the whole page with a CSS3
spinner inside it:
Using JavaScript to determine when to hide the transition
All page transitions must end, and an appropriate time to bid it farewell is when the page has fully loaded. JavaScript's window.onload
event is perfect for this part of the puzzle, but only after some
tweaking. You see, some pages finish loading faster than others, and
for barren pages that take little time to fully load, it means the
page transition would simply flash by before being dismissed. On the
other hand there will also be pages that take a long time to
completely load, with which a transition that obscures the page the
whole time would frustrate users to no end To account for
these two extremes, we'll engineer the page transition so that its
dismissal while based on when the page has loaded, is constrained by
a minimum and maximum time.
Lets see the core JavaScript now that dismisses our document transition after the page has loaded or 500 milliseconds, whichever is longer, but no longer than 3000 milliseconds:
;function(){ // basic code to dismiss page transition after page has loaded var minloadingtime = 100 var maxloadingtime = 3000 var startTime = new Date() var elapsedTime var dismissonloadfunc, maxloadingtimer window.addEventListener('load', dismissonloadfunc = function(){ // when page loads clearTimeout(maxloadingtimer) // cancel dismissal of transition after maxloadingtime time elapsedTime = new Date() - startTime // get time elapsed once page has loaded var hidepageloadertimer = (elapsedTime > minloadingtime)? 0 : minloadingtime - elapsedTime setTimeout(function(){ document.getElementById('pageloader').classList.add('dimissloader') // dismiss transition }, hidepageloadertimer) }, false) maxloadingtimer = setTimeout(function(){ // force dismissal of page transition after maxloadingtime time window.removeEventListener('load', dismissonloadfunc, false) // cancel onload event function call document.getElementById('pageloader').classList.add('dimissloader') // dismiss transition }, maxloadingtime) })();
The code inside the window onload
event takes care
of hiding the transition DIV once the document has loaded and
no sooner than the minimum minloadingtime
setting value. We declare two variables, startTime
, and elapsedTime
, to keep
track of the starting time of the document transition, and when the
page has loaded, respectively. Inside the window onload
event, we
calculate the time it took to load the document (elapsedTime
) and
compare it to the minloadingtime
setting to see if the transition
should be dismissed immediately at this point, or continue to show
until the minloadingtime
setting duration has been reached:
var hidepageloadertimer = (elapsedTime > minloadingtime)? 0 : minloadingtime - elapsedTime
On the other end of the aisle, the code maxloadingtimer = setTimeout(...)
that
follows the onload
code block is responsible for
dismissing the transition DIV if it's still visible past the maxloadingtime
setting value. Inside it, we first de-register the onload
event function
(dismissonloadfunc
) from firing inside the event before
proceeding to dismiss the page transition immediately.
To actually dismiss the page transition, a CSS class "dismissloader
"
is added to the transition DIV that contains the necessary
styles to hide it gracefully via a fade out. We could have resorted
to using JavaScript to hide the transition DIV, though why not
leverage CSS3 to easily add additional effects to the process? Here is the "dismissloader
"
class that should be defined on top of the existing CSS:
#pageloader.dimissloader{ opacity: 0; visibility: hidden; transition: opacity 1s, visibility 0s 1s; }
Notice how for the transition
property, we set the
opacity
property to fade out over 1 second, while for the visibility
property, set it to disappear immediately (0s), but after a delay of
1 second, to let the opacity
property finish transitioning
first. In CSS3, the visibility
property is a "binary" property that
cannot be transitioned (it's either hidden or visible), but luckily
it can be delayed using CSS3. That's what we're doing here to make
sure the document transition not only fades out, but is totally
hidden at the end so the transition DIV doesn't interfere with the rest
of the document.
Checking for browser support for @keyframe
animations, hiding
document scrollbars
Our page transition makes use of CSS transitions, keyframe
animations (for the spinner), and the
CSS
classList API (for adding/removing CSS classes), all of
which come with their own mixed bag of browser support. For our
purpose we want to make sure the browser supports @keyframe
animations and the classList
API before giving the transition any
screen time; otherwise it should just be dismissed ASAP.
To check for CSS3 @keyframe
animation support, we can simply
turn to
this snippet by MDN, while making sure the browser supports
Element.classList
is even simpler. The following final JavaScript
code
incorporates the aforementioned checks to hide the page transition
immediately if any one of them fails. It also hides the document
scrollbars while the transition is visible:
;(function(){ // Final page transition code var animation = false, animationstring = 'animation', keyframeprefix = '', domPrefixes = 'Webkit Moz O ms Khtml'.split(' '), pfx = '', elm = document.createElement('div'); if( elm.style.animationName !== undefined ) { animation = true; } if( animation === false ) { for( var i = 0; i < domPrefixes.length; i++ ) { if( elm.style[ domPrefixes[i] + 'AnimationName' ] !== undefined ) { pfx = domPrefixes[ i ]; animationstring = pfx + 'Animation'; keyframeprefix = '-' + pfx.toLowerCase() + '-'; animation = true; break; } } } var minloadingtime = 100 var maxloadingtime = 3000 var startTime = new Date() var elapsedTime var dismissonloadfunc, maxloadingtimer if (animation && document.documentElement && document.documentElement.classList){ document.documentElement.classList.add('hidescrollbar') window.addEventListener('load', dismissonloadfunc = function(){ // when page loads clearTimeout(maxloadingtimer) // cancel dismissal of transition after maxloadingtime time elapsedTime = new Date() - startTime // get time elapsed once page has loaded var hidepageloadertimer = (elapsedTime > minloadingtime)? 0 : minloadingtime - elapsedTime setTimeout(function(){ document.getElementById('pageloader').classList.add('dimissloader') // dismiss transition }, hidepageloadertimer) setTimeout(function(){ document.documentElement.classList.remove('hidescrollbar') }, hidepageloadertimer + 100) // 100 is the duration of the fade out effect }, false) maxloadingtimer = setTimeout(function(){ // force dismissal of page transition after maxloadingtime time window.removeEventListener('load', dismissonloadfunc, false) // cancel onload event function call document.getElementById('pageloader').classList.add('dimissloader') // dismiss transition setTimeout(function(){ document.documentElement.classList.remove('hidescrollbar') }, 100) // 100 is the duration of the fade out effect }, maxloadingtime) } else{ document.getElementById('pageloader').style.display = 'none' } })();
At the top we include
the code for checking @keyframes support, which sets the
animation
variable to true
if the browser
supports CSS3 animations. The main body that dismisses the page
transition after a certain length of time is then only executed if
the browser supports both CSS animations and the
classList API (element.classList
).
Otherwise, the transition DIV is hidden immediately.
Notice inside the main BODY how we're also adding a CSS class "hidescrollbar
"
to the document root while the transition is visible just for good
measure; as the class name implies, this is to hide any document
scrollbars while the transition is in session. Here is the
corresponding CSS definition that makes that happen:
html.hidescrollbar{ overflow: hidden; } html.hidescrollbar body{ overflow: hidden; }
Here is the end result again, a lightweight page intro transition!
Bonus- implementing an "outro" transition when the page unloads
Here's a bonus for the page transition we just looked at- adding an "outro" effect that kicks in when the user leaves the current page. A common outro effect entails fading out the current page before the browser loads a new one.
Recall that we utilized window's onload
event as a
trigger for the original, "intro" transition. For an outro, another
one of window's events comes in handy- the
beforeunload event. It fires just before the page is about
to be unloaded. Using this event, we can add a CSS class to the
document BODY that applies a transition just before the user is
whisked away to another page, such as fading the page out. Here is
the additional CSS and JavaScript that accomplishes this:
/* Outro BODY fadeout CSS class */ body.fadeout{ opacity: 0; transition: all 1s; }
/* JavaScript to add .fadeout class to page just before it unloads */ window.addEventListener('beforeunload', function(){ document.body.classList.add('fadeout') }, false)
We've defined the .fadeout
class to fade out the
BODY element over the course of 1 second when dynamically added to
that element. In reality the transition may not reach the finish
line before the page is unloaded and a new page takes its place, as
the "beforeunload
" event doesn't wait for any
transition to finish before kicking in. In general, however, it
takes around .5s to 1s after clicking on a link before the current
page is overwritten, hence the 1s setting. The result is an easy outro effect, albeit slightly unrefined. Here is the original page
transition demo with such an outro transition added:
Page Transition Demo with additional outro added
Click on one of the links at the bottom of the page above to exit the page and see the fade out effect.
Lastly, for those of you who cannot accept an outro transition that can be cut short at times, a more precise approach would be to use JavaScript to hijack outgoing links so they don't trigger immediately, but rather, after the desired outro transition has finished running. But that's for another day and another tutorial.
End of Tutorial