DEV Community

Cover image for Creating a Preloader for Gatsby site
bartosjiri ⚡
bartosjiri ⚡

Posted on

Creating a Preloader for Gatsby site

Introduction

Gatsby sites are fast by design (as the official website claims) and personally I can only agree with that statement. However, you might have encountered a situation before where you wanted to briefly display a page loader (or as I call it, preloader) while some parts of the website are still loading, whatever the reason may be.

Preloader preview

In this post we will go through steps to create a simple customizable preloader that will overlay the page content and fade out once the document is ready. In the end we will also add fallback support for users that have JS disabled in their browsers. The complete code can be found in the summary section. Let's get started!

Implementation

Creating preloader component

The visual portion of the preloader consists of a parent element and custom inner content. For an inspiration, I will be providing a simple logo placeholder and animation, however feel free to customize or replace it to better fit your own needs.

Since Gatsby's provides a set of server rendering APIs, we will utilize the gatsby-ssr.js file and its functions. First we create the JSX component with a parent div element and give it an ID of preloader. Inside of it, we define our custom content, in this case a logo image and a animation element.

In order to add this component to the final HTML, we pass it through the setPreBodyComponents function on the Gatsby's onRenderBody event, which is called during the server side rendering. As the last step we import React for it to be complete:

gatsby-ssr.js
const React = require("react")

exports.onRenderBody = ({
  setPreBodyComponents
}) => {
  setPreBodyComponents([
    <div id="preloader">
      {/* Optional: */}
      <img src="/images/logo.png" alt="logo" style={{"height": "calc(3.23625vw + 77.86408px)"}} />
      <div className="preloader_animation"></div>
    </div>
  ])
}
Enter fullscreen mode Exit fullscreen mode

Next, we will be adding styles for this component. Again, the inner content styling is up to you and my demo styles can be found at the bottom of the code block:

src/styles/preloader.scss
body {
  #preloader {
    position: fixed;
    display: none;
    top: 0;
    left: -10%;
    right: -10%;
    height: 0;
    margin-left: calc(100vw - 100%);
    overflow-x: hidden;
  }

  &.preloader_active {
    height: 100vh;
    overflow-y: hidden;

    #preloader {
      height: auto;
      bottom: 0;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      background-color: #27135c;
      z-index: 9999;
      display: flex;
      overflow: hidden;
    }
  }

  &.preloader_ready {
    height: auto;
    overflow-y: auto;

    #preloader {
      animation: preloaderLeaving 0.5s forwards;

      @keyframes preloaderLeaving {
        0% {
          opacity: 1;
        }
        100% {
          opacity: 0;
        }
      }
    }
  }
}

// Optional:
body {
  #preloader {
    img {
      z-index: 120;
    }

    .preloader_animation {
      position: absolute;
      width: calc(3.23625vw + 77.86408px);
      height: calc(3.23625vw + 77.86408px);
      border: 5px solid #ffffff;
      border-radius: 50%;
      opacity: 0;
      z-index: 110;
      animation: preloaderAnimation 1.5s ease-out infinite 0s;

      @keyframes preloaderAnimation {
        0% {
          transform: scale(.1);
          opacity: 0.0;
        }
        50% {
          opacity: 1;
        }
        100% {
          transform: scale(1.2);
          opacity: 0;
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Let's not forget importing this file into the final HTML through the gatsby-browser.js file:

gatsby-browser.js
import "./src/styles/preloader.scss"
Enter fullscreen mode Exit fullscreen mode

Adding functionality

As you might have noticed in the stylesheet, we are defining a few currently unused classes such as preloader_active and preloader_ready. Each of these is bound to a stage in the preloader life cycle:

preloader_active

This is the starting class/stage. If the body element contains this class, the preloader is visible and overlays the page content. To add this class, we will return to the gatsby-ssr.js and add it like this:

gatsby-ssr.js
// ...
exports.onRenderBody = ({
  // ...
  setBodyAttributes
}) => {
  // ...
  setBodyAttributes({
    className: "preloader_active"
  })
}
Enter fullscreen mode Exit fullscreen mode

preloader_ready

Once the document is ready, we can start fading out the preloader, thanks to the CSS animation in this class. We add the class to the body with a script that is waiting for the document state change. First, we have to create the given script:

static/scripts/preloader.js
var body = document.querySelector("body");
document.onreadystatechange = function () {
  if (document.readyState === "complete") {
    body.classList.add("preloader_ready");
    setTimeout(function () {
      body.classList.remove("preloader_active");
      body.classList.remove("preloader_ready");
    }, 500);
  }
};
Enter fullscreen mode Exit fullscreen mode

Then we insert the script into the final HTML, using the gatsby-ssr.js file and the available API yet again:

gatsby-ssr.js
// ...
exports.onRenderBody = ({
  // ...
  setHeadComponents,
  setPostBodyComponents
}) => {
  setHeadComponents([
    <link as="script" rel="preload" href="/scripts/preloader.js" />
  ])
  // ...
  setPostBodyComponents([
    <script src="/scripts/preloader.js" />
  ])
}
Enter fullscreen mode Exit fullscreen mode

We want this script to be available as soon as possible, which is the reason for pointing to it twice. You can read more about content preloading in MDN Web Docs' guide.

Finishing with noscript support

Even though we are building a website based on JavaScript, we still want to support users who prefer to have JS disabled in their browsers. Since the preloader is dependent on a script, it would stay visible forever, preventing the user to see any content. We can simply include a separate stylesheet inside a noscript tag in the head of the page (explicitly allowed in HTML5), which hides the preloader:

static/styles/noscript.css
body.preloader_active {
  height: auto;
  overflow-y: auto;
}

body.preloader_active #preloader {
  display: none;
}
Enter fullscreen mode Exit fullscreen mode
gatsby-ssr.js
// ...
exports.onRenderBody = ({
  // ..
}) => {
  setHeadComponents([
    // ...
    <noscript>
      <link rel="stylesheet" href="/styles/noscript.css" />
    </noscript>
  ])
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Summary

We have successfully added a preloader to our Gatsby site. Upon entering the site, the preloader will be overlaying the content until the document is ready, and then it will fade out. If the user has disabled JS in their browser, the preloader will remain hidden through a stylesheet.

Below you can find all files that we have added or modified:

Collapsible
gatsby-ssr.js
const React = require("react")

exports.onRenderBody = ({
  setHeadComponents,
  setPreBodyComponents,
  setBodyAttributes,
  setPostBodyComponents
}) => {
  setHeadComponents([
    <link as="script" rel="preload" href="/scripts/preloader.js" />,
    <noscript>
      <link rel="stylesheet" href="/styles/noscript.css" />
    </noscript>
  ])
  setPreBodyComponents([
    <div id="preloader">
      {/* Optional: */}
      <img src="/images/logo.png" alt="logo" style={{"height": "calc(3.23625vw + 77.86408px)"}} />
      <div className="preloader_animation"></div>
    </div>
  ])
  setBodyAttributes({
    className: "preloader_active"
  })
  setPostBodyComponents([
    <script src="/scripts/preloader.js" />
  ])
}

Enter fullscreen mode Exit fullscreen mode
gatsby-browser.js
import "./src/styles/preloader.scss"
Enter fullscreen mode Exit fullscreen mode
src/styles/preloader.scss
body {
  #preloader {
    position: fixed;
    display: none;
    top: 0;
    left: -10%;
    right: -10%;
    height: 0;
    margin-left: calc(100vw - 100%);
    overflow-x: hidden;
  }

  &.preloader_active {
    height: 100vh;
    overflow-y: hidden;

    #preloader {
      height: auto;
      bottom: 0;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      background-color: #27135c;
      z-index: 9999;
      display: flex;
      overflow: hidden;
    }
  }

  &.preloader_ready {
    height: auto;
    overflow-y: auto;

    #preloader {
      animation: preloaderLeaving 0.5s forwards;

      @keyframes preloaderLeaving {
        0% {
          opacity: 1;
        }
        100% {
          opacity: 0;
        }
      }
    }
  }
}

// Optional:
body {
  #preloader {
    img {
      z-index: 120;
    }

    .preloader_animation {
      position: absolute;
      width: calc(3.23625vw + 77.86408px);
      height: calc(3.23625vw + 77.86408px);
      border: 5px solid #ffffff;
      border-radius: 50%;
      opacity: 0;
      z-index: 110;
      animation: preloaderAnimation 1.5s ease-out infinite 0s;

      @keyframes preloaderAnimation {
        0% {
          transform: scale(.1);
          opacity: 0.0;
        }
        50% {
          opacity: 1;
        }
        100% {
          transform: scale(1.2);
          opacity: 0;
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
static/scripts/preloader.js
var body = document.querySelector("body");
document.onreadystatechange = function () {
  if (document.readyState === "complete") {
    body.classList.add("preloader_ready");
    setTimeout(function () {
      body.classList.remove("preloader_active");
      body.classList.remove("preloader_ready");
    }, 500);
  }
};
Enter fullscreen mode Exit fullscreen mode
static/styles/noscript.css
body.preloader_active {
  height: auto;
  overflow-y: auto;
}

body.preloader_active #preloader {
  display: none;
}
Enter fullscreen mode Exit fullscreen mode
static/images/logo.png

Logo placeholder

Oldest comments (1)

Collapse
 
arifsetyo21 profile image
Arif Setyo Nugroho • Edited

how can i add preloader at everytime i change page in this website?