The loader runs until the page content is fully loaded. It is then removed from the DOM.
- If user-preferences allow animations:
- Display loading animation.
- Else:
- Display static text: "Loading...".
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>
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.
Refresh the page to trigger the loader.
- Open inspector,
Ctrl+Shift+P
,Run>
. Type 'reduce',- Click 'Rendering' on 'Emulate CSS prefers-reduced-motion reduce',
- Refresh the page.
- 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.
<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>
: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);
}
}
}
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")
})
})
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.