Go to homepage

Projects /

Animated Page Transition

A CSS powered animation, that replaces the refresh of the page while the content is updated using Ajax.

Animated Page Transition
Check our new component library →

We’ve been playing around the idea of replacing the refresh of a web page with an animation, that takes place while the new page content is loaded using Ajax. We used the pushState method to manipulate the browser history.

Inspiration came from this beautiful website: jardins-poudriere.ch.

Creating the structure

The HTML structure is composed of a <main> element, wrapping the page content, a div.cd-cover-layer which is used to create the layer covering the content during the page transition, and a div.cd-loading-bar to create the loading bar animation.

   <div class="cd-index cd-main-content">
         <h1>Page Transition</h1>
         <!-- your content here -->

<div class="cd-cover-layer"></div> <!-- this is the cover layer -->

<div class="cd-loading-bar"></div> <!-- this is the loading bar -->

Adding style

We used the body::before and body::after pseudo-elements to create the 2 blocks that cover the page content during the page transition: these elements are in fixed position, with height equal to 50vh and width equal to 100% of the viewport. By default, they are hidden outside the viewport using the CSS transform property (translateY(-100%)/translateY(100%)). When the user triggers a page transition, these elements are moved back into the viewport (using the .page-is-changing class added to the <body> element).

Here is a quick animation that shows the starting position of the body::before, body::after and the div.cd-loading-bar elements (gif created in After Effects):

page transition

body::after, body::before {
  /* these are the 2 half blocks which cover the content once the animation is triggered */
  height: 50vh;
  width: 100%;
  position: fixed;
  left: 0;
body::before {
  top: 0;
  transform: translateY(-100%);
body::after {
  bottom: 0;
  transform: translateY(100%);
body.page-is-changing::after, body.page-is-changing::before {
  transform: translateY(0);

The fade-out effect of the page content during the page transition is achieved animating the opacity of the div.cd-cover-layer. It covers the entire .cd-main-content element, has the same background-color, and its opacity is animated from 0 to 1 when the .page-is-changing class is assigned to the <body>.

The progress bar animation is created using the .cd-loading-bar::before pseudo-element: by default, it is scaled down (scaleX(0) and transform-origin: left center), while it is scaled back up when the page transition is triggered (scaleX(1)).

.cd-loading-bar {
  /* this is the loading bar - visible while switching from one page to the following one */
  position: fixed;
  height: 2px;
  width: 90%;
.cd-loading-bar::before {
  /* this is the progress bar inside the loading bar */
  position: absolute;
  left: 0;
  top: 0;
  height: 100%;
  width: 100%;
  transform: scaleX(0);
  transform-origin: left center;
.page-is-changing .cd-loading-bar::before {
  transform: scaleX(1);

Smooth animations are achieved using CSS Transitions. We have been using a different transition-delay value for each animated element in order to perform the different animations in the right order.

Events handling

We have been using the data-type="page-transition" to target the links triggering the action. When a click event is detected, the changePage() function is executed:

$('main').on('click', '[data-type="page-transition"]', function(event){
   //detect which page has been selected
   var newPage = $(this).attr('href');
   //if the page is not animating - trigger animation
   if( !isAnimating ) changePage(newPage, true);

This function triggers the page animation and loads the new content (loadNewContent() function):

function changePage(url, bool) {
   isAnimating = true;
   // trigger page animation
   loadNewContent(url, bool);

When the new content is loaded, it replaces the old content inside the <main> element, the .page-is-changing class is removed from the body (to reverse the  page animation) and the new loaded page is added to the window.history (using the pushState() method).

function loadNewContent(url, bool) {
   var newSectionName = 'cd-'+url.replace('.html', ''),
   section = $('<div class="cd-main-content '+newSectionName+'"></div>');
   section.load(url+' .cd-main-content > *', function(event){
      // load new content and replace <main> content with the new one

      if(url != window.location){
         //add the new page to the window.history
         window.history.pushState({path: url},'',url);

In order to trigger the same page animation when user clicks the browser back button, we listen for the popstate event, and execute the changePage() function when it is fired:

$(window).on('popstate', function() {
   var newPageArray = location.pathname.split('/'),
   //this is the url of the page to be loaded 
   newPage = newPageArray[newPageArray.length - 1];
   if( !isAnimating ) changePage(newPage);

You can read more about the popstate event and how browsers handle it here.

Note: we implemented a basic load() function to upload new content, but you may want to replace it with, for example, a $.ajax call in order to handle errors, etc.

Project duplicated

Project created

Globals imported

There was an error while trying to export your project. Please try again or contact us.