Skip to content

CSS and JavaScript page loader which displays a loading animation or message, depending on the user's preferences.

License

Notifications You must be signed in to change notification settings

chrisnajman/loader

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Loader

The loader runs until the page content is fully loaded. It is then removed from the DOM.


Table of Contents

Description

  • If user-preferences allow animations:
    • Display loading animation.
  • Else:
    • Display static text: "Loading...".

Accessibility

Screen Reader Accessibility

The HTML contains text only accessible by screen readers to ensure that users with visual impairments are notified about the loading state of the page. This is achieved by using the aria-live="polite" attribute, which ensures that the screen reader will announce updates without interrupting the user.

<div id="loader" class="loader">
  <span class="visually-hidden">The page is loading...</span>
</div>

<p class="visually-hidden"  aria-live="polite" aria-hidden="true" id="page-loaded"></p>

After the page has loaded (and the loader has been removed from the DOM), p id="page-loaded" is rendered as:

<p class="visually-hidden" aria-hidden="false" id="page-loaded">The page has loaded.</p>

User Preference: Reduced Motion

All animation/transition CSS is wrapped inside:

@media (prefers-reduced-motion: no-preference) {
  ...
}

This ensures that if prefers-reduced-motion has not been set, the animation will run.

If it has been set, fallback CSS will display a static message.

WAVE Web Accessibility Evaluation Tools Report


Testing

Refresh the page to trigger the loader.

To Disable Animations (Chrome, Windows 10):

  1. Open inspector,
  2. Ctrl+Shift+P,
  3. Run>. Type 'reduce',
  4. Click 'Rendering' on 'Emulate CSS prefers-reduced-motion reduce',
  5. Refresh the page.

To re-enable animations:

  • Follow steps 1-3 then,
  • Click 'Rendering' on 'Emulate CSS prefers-reduced-motion',
  • Refresh the page.

Tested on Windows 10 with:

  • Chrome
  • Firefox
  • Microsoft Edge

The page has been tested in both browser and device views.


Code

HTML

<div id="loader" class="loader">
  <span class="visually-hidden">The page is loading...</span>
</div>

<p class="visually-hidden" aria-hidden="true" id="page-loaded"></p>

CSS

:root {
    --clr-lightest: white;
    --clr-green: rgb(4, 167, 167);
    --clr-dark: rgb(30, 39, 39);
}

.loader {
    position: fixed;
    z-index: 500;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--clr-dark);
}

.loader-hidden {
    opacity: 0;
    visibility: hidden;
}

.loader::after {
    content: "Loading...";
    font-size: 5rem;
}

@media (prefers-reduced-motion: no-preference) {
    .loader {
        transition:
            opacity 0.75s,
            visibility 0.75s;
    }

    .loader::after {
        content: "";
        font-size: 0rem;
        width: 10rem;
        height: 10rem;
        border: 2rem solid var(--clr-lightest);
        border-top-color: var(--clr-green);
        border-radius: 50%;
        animation: loading 0.75s ease infinite;
    }
    @keyframes loading {
        from {
            transform: rotate(0turn);
        }
        to {
            transform: rotate(1turn);
        }
    }
}

JavaScript

window.addEventListener("load", () => {
  const loader = document.getElementById("loader")
  const pageLoaded = document.getElementById("page-loaded")

  loader.classList.add("loader-hidden")

  loader.addEventListener("transitionend", () => {
    loader.remove()

    // For screen readers
    pageLoaded.textContent = "Page has loaded."
    pageLoaded.setAttribute("aria-hidden", "false")
  })
})

Source

This is a fork from CSS Only Page Loader (CodePen), with accessibility enhancements of the CSS and JavaScript.

I also made a further change to the JavaScript, replacing

loader.addEventListener("transitionend", () => {
  document.body.removeChild(loader)
})

with

loader.addEventListener("transitionend", () => {
  loader.remove()
})

as the former triggered a console error:

Uncaught DOM Exception: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.