You can use a conic-gradient together with some CSS variables to get a easy loader effect.
To calculate the angles you can use the formula (percent * (max - min) / 100) + min;
Which converts the precentage loaded into the correct angle (degrees) we need.
All possible angle combinations are possible as long as the MIN
in JS matches the degrees set in CSS. (and the MAX
is higher then the MIN
).
If you replace the orange
color in CSS with transparent
you'll get a seemless loader, though for demo purposes its easier to keep it colored.
const loader = document.querySelector('.progress');
const MIN = 30; // Must match CSS degrees
const MAX = 195;
function updateProgress(percent) {
const degrees = (percent * (MAX - MIN) / 100) + MIN;
loader.style.setProperty('--progress', `${degrees}deg`);
loader.textContent = `${percent}%`;
}
// Something is loading down here ...
let percent = 0;
setInterval(() => {
percent = (percent + 1) % 100;
updateProgress(percent);
}, 1000 / 30); // 30 FPS
.progress {
width: 100px;
height: 100px;
background-image: conic-gradient(
orange 30deg,
orangered 30deg var(--progress, 0), /* Set via JS */
orange var(--progress, 0) /* Set via JS */
);
border-radius: 50%;
/* center text */
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
}
<div class="progress"></div>
EDIT:
const loader = document.querySelector('.progress');
const MIN = 30;
const MAX = 330;
// Set CSS properties
loader.style.setProperty('--min-degrees', `${MIN}deg`);
loader.style.setProperty('--max-degrees', `${MAX}deg`);
function updateProgress(percent) {
const degrees = (percent * (MAX - MIN) / 100) + MIN;
loader.style.setProperty('--progress', `${degrees}deg`);
}
// Something is loading down here ...
let percent = 0;
setInterval(() => {
percent = (percent + 1) % 100;
updateProgress(percent);
}, 1000 / 60); // 60 FPS
.progress {
width: 100px;
height: 100px;
/* Lightblue gradient */
background-image: conic-gradient(
transparent var(--min-degrees),
#E8F6FD var(--min-degrees) var(--max-degrees),
transparent var(--max-degrees)
);
border-radius: 50%;
transform: rotate(180deg); /* Rotate whole element to start from center bottom */
position: relative;
}
.progress::before {
content: "";
width: 100%;
height: 100%;
/* Darkblue gradient */
background-image: conic-gradient(
transparent var(--min-degrees),
#00ACEE var(--min-degrees) var(--progress, 0),
transparent var(--progress, 0)
);
border-radius: 50%;
position: absolute;
left: 0;
top: 0;
}
/* White circle on top to make it look like a cutout */
.progress::after {
content: "";
width: 75px;
height: 75px;
left: 12.5px;
position: absolute;
top: 50%;
transform: translateY(-50%);
background: #FFF;
border-radius: 50%;
}
<div class="progress"></div>