Experiment - Smooth page transitions with CSS and JavaScript

Note: There are a few performance and accessibility issues with this technique such as a page not being shown at all. This was written from experimenting with my personal site. I plan on experimenting more and improving the reliability of this code. Proceed with caution.

With this article, I'm going to show you how to use CSS and JavaScript with no frameworks (no jQuery) to make seamless page transitions. This technique could be used on custom WordPress sites, static site generator sites (11ty, Jekyl, Gatsby, etc) on web apps, and just plain HTML sites.

Ross! Just give me the code →

Quick overview

It helps to take a step back and think of how we will do the page transitions before writing code. Here is a simple overview of how the page transitions will work.

  1. Add a class to the <body> tag when page content is loaded.
  2. Set default states of CSS to elements you want animated and add a transition property.
  3. Use CSS selectors with the loaded body class and add properties on how you want the page to look after loaded
  4. Delay going to the next page when internal links are clicked so that the page out transitions can play.

Technical implementation

Add a .loaded class to the body on page load.

<script>
function loaded() {
document.body.classList.add('loaded');
}
</script>

On your HTML page add the following to your body tag.

 <body onload="loaded()">

This will execute the loaded() function once all of the content in the <body> has loaded.

Setup the default CSS and transitions

Set default states of CSS of where we want the transitions to start. For this example we want the header and content to fade in from the left and we want to delay the content to animate a little bit after the header to give it a nice staging load effect.

.header,
.content
{
opacity: 0;
transform: translateX(-40px);
transition: all 1s ease;
}

.content {
transition-delay: 0.5s;
}

opacity: 0; is giving these elements a 0% opacity so that they will be completly hidden.

translateX(-20px) is telling these elements we want to move them on the x axis -20px of where the element naturally is.

The transition property is what will occur when we add another class to overwrite the elements CSS properties. all specifies that we want to use all of the CSS properties that change (opacity and transform in this case). 0.3s is how long we want the transform to occur and ease is the timing function that the transition will use.

Setup the loaded CSS

Now we will use the .loaded body class to overwrite the default state styles and this will be how the page looks when it is loaded and used.

.loaded .header,
.loaded .content
{
opacity: 1;
transform: translateX(0);
}

This code is overwriting our first styles and telling the page to set the opacity to 100% visible and move the elements back to their natural position on the x-axis.

Now when the page is loaded, by default the .header and .content will have opacity 0 and be -20px on the x-axis. After loaded the elements will transition to what we specified in the .loaded .header CSS in the amount of time we specified on the first CSS.

Your full CSS should look like

.header,
.content
{
opacity: 0;
transform: translateX(-40px);
transition: all 1s ease;
}

.content {
transition-delay: 0.5s;
}

.loaded .header,
.loaded .content
{
opacity: 1;
transform: translateX(0);
}

Click the live example below to see what we have so far.

See the Pen Simple page transitions with CSS and JavaScript by rdallaire (@rdallaire) on CodePen.

Transitioning to the next page

If you have this setup you will notice when you leave your current page it becomes blank for the current screen and then does the transition for the next page.

We want to be able to have a transition out of the current page and have it reverse the transitions.

To fix this we will

  1. Stop the next page from loading
  2. Tell the browser when a link is clicked, remove the .loaded class
  3. Grab the href of the link that was clicked
  4. After a set amount of time (enough to play the transitions) load the URL that was clicked

The following code is used to do this. It also includes a check to ignore target="_blank" links since they open in a new tab.

The setTimeout 500 at the bottom can be adjusted depending on how long your transitions take.

document.addEventListener('click', function (e) {

var goTo = e.target.getAttribute('href') !== null ? e.target.getAttribute('href')
: e.target.parentNode.getAttribute('href');

if ((e.target.matches('a') ||
e.target.parentNode.matches('a')) &&
e.target.getAttribute('target') !== '_blank' && !(/^#/.test(goTo))) {

e.preventDefault();

document.body.classList.remove('loaded');

setTimeout(function(){
window.location = goTo;
},500);
}

}, false);

Full code

Below is the full code. You can inline the CSS and JS if you want or paste it into a seperate file.

<body onload="loaded()">
<header class="header">
<h1>This is my header</h1>
</header>
<main class="content">
<p>Some content</p>
</main>
</body>
<style>
.header,
.content
{
opacity: 0;
transform: translateX(-40px);
transition: all 1s ease;
}

.content {
transition-delay: 0.5s;
}

.loaded .header,
.loaded .content
{
opacity: 1;
transform: translateX(0);
}
</style>
<script>
document.addEventListener('click', function (e) {

var goTo = e.target.getAttribute('href') !== null ? e.target.getAttribute('href')
: e.target.parentNode.getAttribute('href');

if ((e.target.matches('a') ||
e.target.parentNode.matches('a')) &&
e.target.getAttribute('target') !== '_blank' && !(/^#/.test(goTo))) {

e.preventDefault();

document.body.classList.remove('loaded');

setTimeout(function(){
window.location = goTo;
},500);
}

}, false);
</script>

Further considerations

Lazy loading images

If you have an image-heavy page or any other type of embeds, you need to consider lazy loading those assets on scroll so that the DOM can be loaded quickly enough for a smooth experience. For my site, I setup images, videos, and iframe embed's to lazy load when they are scrolled to.

Don't transition headers for smooth experience

On my site I decided to not add transitions to the header and navigation. This gives it a feel that only the content is being refreshed similar to a web app, when in reality it is still a full page load.

Add a class on page change

Instead of reversing the transitions you could experiment with adding a class to the body on page change so that when you go to a different page it has a different effect than entering the page.

Let me know what you think on twitter @rdallaire