CSS animations (and a little JavaScript trickery) can let us add page transitions and move away from the hard-cut of page loads. My jQuery plugin smoothState.js helps polish those transitions and improve UI response times.
Page transitions benefit the user experience
Imagine, for a second, how disorienting it would be if touching a doorknob teleported you to the other side of the door. Navigating the web feels like using a teleporting doorknob. Layouts change, elements rearrange or disappear, and it takes time for the user to adjust. Smooth transitions reduce the effort it takes for users to get settled into a new environment.
Native apps understand the importance of animations. It’s uncommon to find an app without page transitions, and users have grown accustomed to this higher level of usability. The web has started to feel outdated because of this shift in expectations. Many think of the web as an inferior experience to apps. Luckily, it doesn’t have to be this way.
Add page transitions with CSS @keyframes animations
Let’s take a look at an example to see how we could start adding page transitions. In our demo layout we can see the white flashes and hard-cuts typical of web pages.

CSS animations allow us to define the visual behavior of an element when it gets rendered on the page. To add animations to our site we should:
- Identify how the elements on the page will animate
- Create the keyframes we’ll need
- Write the CSS declarations
- Add classes to the layout
Identify how the elements on the page will animate
Let’s look at our sample page:

We can pick out a few opportunities for adding some transitions if we examine the layout. Google’s guide on meaningful transitions is a good set of rules for element animations. That’s a good place to start if you’re new to interaction design.
Create the @keyframes we’ll need
It looks like we’ll need three types of unique animations.
- A fade in animation for the header and the button
- A slide up animation with a slight fade for the contents of the home page
- A slide in from the right, with a slight fade, for the contents of the detail page
Let’s create those CSS @keyframes
and name them:
/*
* Keyframes
*/
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes fadeInUp {
0% {
opacity: 0;
transform: translate3d(0, 100%, 0);
}
100% {
opacity: 1;
transform: none;
}
}
@keyframes fadeInRight {
0% {
opacity: 0;
transform: translate3d(100%, 0, 0);
}
100% {
opacity: 1;
transform: none;
}
}
}
Write the CSS declarations
Now that we have our keyframes, we’ll need to attach them to classes using the CSS animation properties. Here’s what that CSS looks like:
/*
* CSS Page Transitions
* Don't forget to add vendor prefixes!
*/
.m-scene {
/** Basic styles for an animated element */
.scene_element {
animation-duration: 0.25s;
transition-timing-function: ease-in;
animation-fill-mode: both;
}
/** An element that fades in */
.scene_element--fadein {
animation-name: fadeIn;
}
/** An element that fades in and slides up */
.scene_element--fadeinup {
animation-name: fadeInUp;
}
/** An element that fades in and slides from the right */
.scene_element--fadeinright {
animation-name: fadeInRight;
}
}
It’s important to note that animations that take too long will annoy the user. There has been quite a bit of research around the usability of response times. To keep the UI feeling snappy limit the animation duration to 0.25 seconds. Run some quick and dirty user testing to make sure your animations don’t feel bothersome.
Add classes to the layout
Now that we have written out our CSS, it’s time to add the classes to the markup.
<!-- Home page -->
<div class="m-scene" id="main">
<div class="m-header scene_element scene_element--fadein">
...
</div>
<div class="m-page scene_element scene_element--fadeinup">
...
</div>
</div>
<!-- Detail page -->
<div class="m-scene" id="main">
<div class="m-aside scene_element scene_element--fadein">
...
</div>
<div class="m-right-panel m-page scene_element scene_element--fadeinright">
...
</div>
</div>
Our result is a nicer entrance to the pages:

We’ve made some progress on our page with just CSS. But even with our improvements, we still see the flash and the animations aren’t as tight as they could be. Now we’ll use smoothState.js to fix that.
Adding polish with smoothState.js
smoothState.js is a jQuery plugin that progressively enhances page loads to give us more control over page transitions. To include it in our page we’ll need to:
- Grab a copy of jQuery and add it to our page
- Download smoothState.js and add it after jQuery.
- Create and include a new .js file, after smoothState.js, where we can run the plugin. (Or use whatever build tool / concatenation process you use to do all this.)
Inside our new .js file, we’ll want to initialize smoothState on a container with an id. The container should wrap all the content on the page.
;(function ($) {
'use strict';
var content = $('#main').smoothState({
// onStart runs as soon as link has been activated
onStart : {
// Set the duration of our animation
duration: 250,
// Alterations to the page
render: function () {
// Quickly toggles a class and restarts css animations
content.toggleAnimationClass('is-exiting');
}
}
}).data('smoothState'); // makes public methods available
})(jQuery);
smoothState gives us access to an onStart.render()
callback that allows us to choreograph the way elements exit the page. We can eliminate the page’s hard-cuts by reversing the layout animations before displaying new content. To do this, run the .toggleAnimationClass()
function, passing in a class as the first argument. Now we’ll declare a new animation direction using that class.
/*
* CSS Page Transitions
* Don't forget to add vendor prefixes!
*/
.m-scene {
.scene_element {
animation-duration: 0.25s;
transition-timing-function: ease-in;
animation-fill-mode: both;
}
.scene_element--fadein {
animation-name: fadeIn;
}
.scene_element--fadeinup {
animation-name: fadeInUp;
}
.scene_element--fadeinright {
animation-name: fadeInRight;
}
/** Reverse "exit" animations */
&.is-exiting {
.scene_element {
animation-direction: alternate-reverse;
}
}
}
We’ll also want to animate the user back to the top of the page. Achieve this by using jQuery’s .animate()
function and setting the scrollTop
property to 0
.
;(function ($) {
'use strict';
var $body = $('html, body'), // Define jQuery collection
content = $('#main').smoothState({
onStart : {
duration: 250,
render: function () {
content.toggleAnimationClass('is-exiting');
// Scroll user to the top
$body.animate({ 'scrollTop': 0 });
}
}
}).data('smoothState');
})(jQuery);
The final result is a smooth transition between the pages of our site. Elements enter and exit the page with grace and the white flash is gone. We’re firing the animations even before the content loads so the user sees an immediate response from the page.

Thanks for this article. I have been struggling with CSS for a while now. but this wel definitely help me out. Thanks again! :)
I would almost have written something similar – good thing I didn’t have to because I suck at javascript.
I’ve tried a shoddy home-brew version of this before where:
preventDefault
do-page-leave-animations
which triggers some@keyframe
stuffsetTimeout
the length of those animationswindow.location
to thehref
of the link that was clickeddo-page-enter-animations
on the body which triggers some@keyframe
stuffThe thing is… if you have lots of links on the page, we can not preload all those links or it would slow down the user experience isn’t it ?
I’ve always doen this with angularJS routes and http intercepts… works wonders
I’d like to see your method for implementing a similar effect using Angular routes and intercepts…
I would also like to see this with Angular
Maybe I’m missing something here, but this sort of behavior is fairly simple to achieve with Angular. I’ve implemented it in the past like this: http://scotch.io/tutorials/javascript/animating-angularjs-apps-ngview
Hey! I have a couple of questions.
In the
keyframes
code, why do you usetranslate3d
instead oftranslate
?In the jQuery code, why is semicolon used before creating the immediately-invoked function? Is this a pattern or what?
The use of translate3D is for the use of GPU. The semicolon is here maybe in order to prevent a forgotten semicolon in the previous loaded script.
Great post! I assume this doesn’t work when a using the browser back button but still a nice simple transition without breaking the web. In our firm we find ourselves using the history api for page transitions because the client foesn t like hard refreshes but still wants ‘real’ pages for search engine friendlyness… So yeah, I definitely see myself using this. The usability aspect is a very good reason to implement this, though…never thought of it that way…
Good one!
Tested on iPhone, hitting the back button seems to work
Keep up the good work!
What minimum jquery version is needed? I tried implementing this on a Drupal 7 install but it didn’t add the animation class.
1.9 is the basic requirement. I haven’t done the research to see if it’s compatible with an even earlier version, but it might be. Feel free to reach out to me if you run into any issues trying to implement smoothState.
Oh wait, same issues with reinitializing javascript… probably because it uses the history api………….
Do you ever find a solution to reinitialising the JS? Having the same problem here.
Is the code will work as it is which is given? or we have to do some modification on it?
This is a great article. It reminds me of the 90’s when FrontPage had page transitions, except these one’s don’t make me want to end my life.
Thanks for another awesome article!
That had been a perfect article if there would have been real page reloading. Otherwise there is no problems of doing that. What if you wan’t to do fade-in/fade-out with real redirects. Not ajax content loading. I haven’t found the way of doing that.
It works perfect and superfast! I have a question, some pages of me use jquery functions, and after a pageswitch they aren’t working anymore. How can I reinitialize them?
I have the same problem.
Anyone found a solution ?
I love it. Really neat.
Definitely cool post, but I question if this is actually good for the user experience. I get the idea of the tele-transportation from one room to the other, but unless you preload an entire site, you could be blocking the navigation just for the sake of transitions.
Hey Joe, thanks for the thoughts.
Could you elaborate a little on what you mean by “blocking the navigation”?
Great post!
Huuuh, why is this not working on Chrome?
on my windows tablet the first transition to a page takes over ten seconds.
Tested this with css3 background animation… It works smooth just like it’s name however some jQuery functions like a simple countdown is not working after you navigate to otehr page and come back to the page which has js functions. any one found a workaround for this ?