Categories:

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:.

Page Transition Demo

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 #pageloader 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