<div class="center" id="center">
<div>
<h1>Ending loading animation</h1>
<p>Below is a loader which has an animation loop and an end animation. When the loading is finished, it waits for the current loop to be finished before triggering.</p><p>The fake request has a minimal duration of 200ms, but it will last at least 1s (animation time).</p>
<button type="button" id="toggleButton">Start random loading</button>
<div class="overlay">
<div class="loader" id="loader">
<div class="loader__circle">
</div>
<div class="loader__dot loader__dot--init"></div>
<div class="loader__dot loader__dot--finished"></div>
<div class="loader__check">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check"><polyline points="20 6 9 17 4 12"></polyline></svg>
</div>
</div>
</div>
</div>
</div>
:root {
--primary-color-rgb: 202, 42, 93;
--secondary-color-rgb: 52, 208, 179;
}
body {
-webkit-font-smoothing: antialiased;
text-rendering: geometricPrecision;
font-family: Montserrat;
line-height: 1.5;
}
.center {
display: flex;
height: 100vh;
width: 100vw;
align-items: center;
justify-content: center;
text-align: center;
> div {
max-width: 768px;
padding: 2em;
}
.loader {
display: none;
}
&.is-loading {
.overlay {
opacity: 1;
pointer-events: all;
}
.loader {
display: block;
}
}
}
.overlay {
opacity: 0;
pointer-events: none;
position: fixed;
top: 0;
left: 0;
z-index: 10;
display: flex;
height: 100vh;
width: 100vw;
align-items: center;
justify-content: center;
background: rgba(white, 0.88);
transition: opacity 0.2s linear;
.loader {
--dot-color: rgb(var(--primary-color-rgb));
--check-color: rgba(var(--secondary-color-rgb));
vertical-align: middle;
}
}
button {
cursor: pointer;
font: inherit;
appearance: none;
margin: 1em 0;
background: rgb(var(--primary-color-rgb));
color: white;
padding: 0.5em 1.5em;
border: 0;
font-weight: 600;
border-radius: 2em;
box-shadow: 0 2.8px 2.2px rgba(var(--primary-color-rgb), 0.02),
0 6.7px 5.3px rgba(var(--primary-color-rgb), 0.028),
0 12.5px 10px rgba(var(--primary-color-rgb), 0.035),
0 22.3px 17.9px rgba(var(--primary-color-rgb), 0.042),
0 41.8px 33.4px rgba(var(--primary-color-rgb), 0.05),
0 100px 80px rgba(var(--primary-color-rgb), 0.07);
}
// css loader
.loader {
--size: 60px;
--border: 0.04em;
--dot-size: 0.23em;
--dot-color: inherit;
--check-color: green;
--easing: cubic-bezier(0.74, 0.17, 0.29, 0.82);
position: relative;
display: inline-block;
font-size: var(--size);
width: 1em;
height: 1em;
transform: translateZ(0);
color: inherit;
&, * {
&,
&::before,
&::after {
box-sizing: border-box;
}
}
&__circle {
position: absolute;
--calculated-position: calc(var(--dot-size) / 2 - var(--border));
border-radius: 50%;
top: var(--calculated-position);
left: var(--calculated-position);
height: calc(100% - var(--dot-size) + var(--border) * 2);
width: calc(100% - var(--dot-size) + var(--border) * 2);
border: var(--border) solid currentColor;
opacity: 0.16;
}
&__dot {
--background: var(--dot-color);
position: absolute;
top: 0;
left: calc(50% - var(--dot-size) / 2);
width: var(--dot-size);
height: var(--dot-size);
background: var(--background);
border-radius: 50%;
&--init {
animation: spin 1s infinite var(--easing);
transform-origin: 50% 0.5em;
}
&--finished {
--background: var(--check-color);
display: none;
}
}
&__check {
--check-size: calc(var(--dot-size) * 2);
display: none;
width: var(--check-size);
height: var(--check-size);
position: absolute;
top: calc(50% - var(--check-size) / 2);
left: calc(50% - var(--check-size) / 2);
color: var(--check-color);
svg {
color: inherit;
display: block;
width: 100%;
height: 100%;
}
}
&.is-finishing {
.loader__circle {
animation: endCircle 1s both var(--easing);
}
.loader__dot {
&--init {
animation: endInit 1s both var(--easing);
}
&--finished {
display: block;
animation: end 1s both var(--easing);
}
}
.loader__check {
display: block;
animation: check 1s both var(--easing);
}
}
}
@keyframes spin {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
@keyframes endInit {
0% {
opacity: 1;
}
50%,
100% {
opacity: 0;
transform: translateY(calc(0.5em - var(--dot-size) / 2));
}
}
@keyframes end {
0% {
opacity: 0;
}
50%,
100% {
transform: translateY(calc(0.5em - var(--dot-size) / 2));
opacity: 1;
}
100% {
transform: translateY(calc(0.5em - var(--dot-size) / 2)) scale(10);
opacity: 0;
}
}
@keyframes endCircle {
0%,
50% {
opacity: 0.16;
transform: scale(1);
}
100% {
opacity: 0;
transform: scale(4);
}
}
@keyframes check {
0%,
50% {
transform: scale(0.25);
opacity: 0;
}
100% {
transform: scale(1);
opacity: 1;
}
}
View Compiled
const button = document.getElementById('toggleButton');
const loader = document.getElementById('loader');
const centerDiv = document.getElementById('center');
let isRequestFinished = false;
loader.addEventListener('animationiteration', () => {
if (isRequestFinished) {
loader.classList.add('is-finishing');
}
})
loader.addEventListener('animationend', () => {
setTimeout(() => {
centerDiv.classList.remove('is-loading');
loader.classList.remove('is-finishing');
isRequestFinished = false;
}, 1000)
})
button.addEventListener('click', () => {
isRequestFinished = false;
centerDiv.classList.add('is-loading')
setTimeout(() => {
isRequestFinished = true;
}, getRandomTime(200, 10000))
})
function getRandomTime(min, max) {
return Math.floor(Math.random() * max) + min;
}
View Compiled
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.