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:
<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>
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 DOMException: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.