Voortgang: balk, stappen, laden, cirkel New
Four progress forms in one set: steps (2 of 4), linear bar with label and amount, an upload that ends in success or error, a ring and an indeterminate loading bar. Animates to its value when in view (IntersectionObserver + CSS transitions), with role="progressbar" and aria-valuenow. With reduced motion values are set directly and the loader stays still.
Voortgang · vier vormen
Offerte aanvragenStap 2 van 4 · Woning
- Gegevens
- Woning
- Planning
- Bevestigen
Speeltuin Kralingen0%
€ 6.840 van € 9.000 opgehaald
plattegrond.pdf · 3,2 MB0%
Uploaden
0%
Profiel compleet
Agenda ophalen
How to use it
- Paste the HTML where the numbers & charts should appear.
- Add the CSS to your stylesheet. All classes are prefixed, so nothing clashes with your own styles.
- Add the JavaScript and call init() with the element that contains the HTML.
<!-- Voortgang: balk, stappen, laden, cirkel · NedDev · eigen werk. -->
<div class="jouw-container">
<div class="prs">
<div class="prs__head"><span class="prs__eyebrow">Voortgang · vier vormen</span><span class="prs__btns"><button class="prs__btn prs__replay" type="button">Opnieuw</button><button class="prs__btn prs__fail" type="button">Toon fout</button></span></div>
<div class="prs__grid">
<div class="prs__col">
<div class="prs__item">
<div class="prs__lbl"><span>Offerte aanvragen</span><b>Stap 2 van 4 · Woning</b></div>
<ol class="prs__steps" aria-label="Offerte aanvragen, stap 2 van 4"><li class="is-done">Gegevens</li><li class="is-cur" aria-current="step">Woning</li><li>Planning</li><li>Bevestigen</li></ol>
</div>
<div class="prs__item">
<div class="prs__lbl"><span id="prs-l1">Speeltuin Kralingen</span><b class="prs__num" data-to="76">0%</b></div>
<div class="prs__bar" role="progressbar" aria-labelledby="prs-l1" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" data-to="76"><span></span></div>
<small class="prs__sub">€ 6.840 van € 9.000 opgehaald</small>
</div>
<div class="prs__item prs__up">
<div class="prs__lbl"><span id="prs-l2">plattegrond.pdf · 3,2 MB</span><b class="prs__unum">0%</b></div>
<div class="prs__bar prs__ubar" role="progressbar" aria-labelledby="prs-l2" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"><span></span></div>
<small class="prs__msg" aria-live="polite">Uploaden</small>
</div>
</div>
<div class="prs__col prs__col--r">
<div class="prs__ring" role="progressbar" aria-label="Profiel compleet" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" data-to="80"><svg viewBox="0 0 44 44" aria-hidden="true"><circle class="prs__rbg" cx="22" cy="22" r="19"/><circle class="prs__rfg" cx="22" cy="22" r="19"/></svg><span class="prs__rnum">0%</span></div>
<small class="prs__cap">Profiel compleet</small>
<div class="prs__ind" role="progressbar" aria-label="Beschikbaarheid ophalen" aria-busy="true"><span></span></div>
<small class="prs__cap">Agenda ophalen</small>
</div>
</div>
</div>
</div>/* Voortgang: balk, stappen, laden, cirkel · NedDev · eigen werk. */
.prs{--ink:#0a0a0a;--pop:#F5480F;--pd:#b83a0b;--ok:#16833f;--err:#b3261e;position:absolute;inset:0;display:flex;flex-direction:column;gap:10px;padding:14px 16px;font-family:'Switzer',system-ui,sans-serif;color:var(--ink);overflow:auto}
.prs__head{display:flex;align-items:center;justify-content:space-between;gap:8px}
.prs__eyebrow{font:600 10px/1 'JetBrains Mono',monospace;letter-spacing:.12em;text-transform:uppercase;color:#5b5b5b}
.prs__btns{display:flex;gap:6px}
.prs__btn{white-space:nowrap;font:600 10px/1 'JetBrains Mono',monospace;letter-spacing:.06em;text-transform:uppercase;padding:6px 8px;border:1px solid var(--ink);border-radius:100px;background:#fff;color:var(--ink);cursor:pointer;transition:background .15s,color .15s}
.prs__btn:hover{background:var(--ink);color:#fff}
.prs__btn:focus-visible{outline:2px solid var(--pd);outline-offset:2px}
.prs__grid{flex:1;display:grid;grid-template-columns:1fr 108px;gap:16px;align-items:center}
.prs__col{display:flex;flex-direction:column;gap:11px;min-width:0}
.prs__col--r{align-items:center;gap:5px}
.prs__lbl{display:flex;justify-content:space-between;gap:8px;font-size:12px;margin-bottom:5px}
.prs__lbl span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.prs__lbl b{font-weight:600;font-variant-numeric:tabular-nums;white-space:nowrap}
.prs__steps{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(4,1fr);gap:4px;font-size:0}
.prs__steps li{height:6px;border-radius:3px;background:#e1ded8;position:relative;overflow:hidden}
.prs__steps li::after{content:'';position:absolute;inset:0;background:var(--ink);transform:scaleX(0);transform-origin:left;transition:transform .45s cubic-bezier(.2,.7,.2,1)}
.prs.is-run .prs__steps li.is-done::after{transform:scaleX(1)}
.prs.is-run .prs__steps li.is-cur::after{background:var(--pop);transform:scaleX(.5);transition-delay:.4s}
.prs__bar{height:8px;border-radius:4px;background:#e1ded8;overflow:hidden}
.prs__bar span{display:block;height:100%;width:0;background:var(--ink);border-radius:4px;transition:width 1.1s cubic-bezier(.2,.7,.2,1)}
.prs__ubar span{background:var(--pop);transition:width .15s linear}
.prs__sub,.prs__msg,.prs__cap{display:block;margin-top:4px;font-size:11px;color:#5b5b5b}
.prs__cap{margin:0 0 6px;text-align:center}
.prs__up.is-ok .prs__ubar span{background:var(--ok)}
.prs__up.is-ok .prs__msg{color:var(--ok);font-weight:600}
.prs__up.is-err .prs__ubar span{background:var(--err)}
.prs__up.is-err .prs__msg,.prs__up.is-err .prs__unum{color:var(--err);font-weight:600}
.prs__ring{position:relative;width:84px;height:84px}
.prs__ring svg{width:100%;height:100%;transform:rotate(-90deg)}
.prs__ring circle{fill:none;stroke-width:4.5}
.prs__rbg{stroke:#e1ded8}
.prs__rfg{stroke:var(--ink);stroke-linecap:round;stroke-dasharray:119.4;stroke-dashoffset:119.4;transition:stroke-dashoffset 1.2s cubic-bezier(.2,.7,.2,1)}
.prs__rnum{position:absolute;inset:0;display:grid;place-items:center;font:700 17px/1 'Switzer',system-ui,sans-serif;font-variant-numeric:tabular-nums}
.prs__ind{width:100%;height:6px;border-radius:3px;background:#e1ded8;overflow:hidden;position:relative}
.prs__ind span{position:absolute;top:0;bottom:0;width:40%;border-radius:3px;background:var(--ink);animation:prs-ind 1.3s cubic-bezier(.4,0,.2,1) infinite}
@keyframes prs-ind{from{left:-40%}to{left:100%}}
@media (max-width:420px){.prs{padding:12px;gap:8px}.prs__grid{grid-template-columns:1fr 84px;gap:12px}.prs__ring{width:68px;height:68px}.prs__rnum{font-size:14px}.prs__col{gap:8px}.prs__sub{display:none}}
@media (prefers-reduced-motion:reduce){.prs__bar span,.prs__rfg,.prs__steps li::after{transition:none}.prs__ind span{animation:none;left:0;width:100%;background:repeating-linear-gradient(90deg,#0a0a0a 0 8px,#5b5b5b 8px 16px)}}// Voortgang: balk, stappen, laden, cirkel · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
var w=root.querySelector('.prs');if(!w)return;
var U='-'+Math.random().toString(36).slice(2,7);w.querySelectorAll('[id]').forEach(function(el){var o=el.id;el.id=o+U;w.querySelectorAll('[aria-labelledby="'+o+'"]').forEach(function(x){x.setAttribute('aria-labelledby',o+U);});});
var reduce=matchMedia('(prefers-reduced-motion: reduce)').matches,raf=0,up=w.querySelector('.prs__up'),ub=up.querySelector('.prs__ubar'),un=up.querySelector('.prs__unum'),msg=up.querySelector('.prs__msg');
function count(el,to,dur){var s=performance.now();if(reduce){el.textContent=to+'%';return;}(function f(n){var p=Math.min(1,(n-s)/dur),e=1-Math.pow(1-p,3);el.textContent=Math.round(to*e)+'%';if(p<1)requestAnimationFrame(f);})(s);}
function setBar(b,v){b.setAttribute('aria-valuenow',Math.round(v));b.firstElementChild.style.width=v+'%';}
function upload(failAt){cancelAnimationFrame(raf);up.classList.remove('is-ok','is-err');msg.textContent='Uploaden';var v=0,last=performance.now();
(function f(n){v+=Math.min(4,(n-last)/22)*(.6+Math.random()*.8);last=n;if(failAt&&v>=failAt){v=failAt;setBar(ub,v);un.textContent=Math.round(v)+'%';up.classList.add('is-err');msg.textContent='Upload mislukt bij '+failAt+'%. Controleer je verbinding en probeer opnieuw.';return;}
if(v>=100){setBar(ub,100);un.textContent='100%';up.classList.add('is-ok');msg.textContent='Geüpload, 3,2 MB';return;}setBar(ub,v);un.textContent=Math.round(v)+'%';raf=requestAnimationFrame(f);})(last);}
function run(failAt){w.classList.remove('is-run');void w.offsetWidth;w.querySelectorAll('.prs__bar[data-to]').forEach(function(b){setBar(b,0);});
var rf=w.querySelector('.prs__rfg');rf.style.strokeDashoffset=119.4;
requestAnimationFrame(function(){requestAnimationFrame(function(){w.classList.add('is-run');
w.querySelectorAll('.prs__bar[data-to]').forEach(function(b){setBar(b,+b.dataset.to);});w.querySelectorAll('.prs__num').forEach(function(n){count(n,+n.dataset.to,1100);});
var r=w.querySelector('.prs__ring'),to=+r.dataset.to;rf.style.strokeDashoffset=119.4*(1-to/100);r.setAttribute('aria-valuenow',to);count(w.querySelector('.prs__rnum'),to,1200);
upload(failAt);});});}
w.querySelector('.prs__replay').addEventListener('click',function(){run(0);});
w.querySelector('.prs__fail').addEventListener('click',function(){run(64);});
var done=false;if('IntersectionObserver' in window){var io=new IntersectionObserver(function(es){if(es[0].isIntersecting&&!done){done=true;run(0);io.disconnect();}},{threshold:.35});io.observe(w);}else run(0);
}
init(document.querySelector('.jouw-container'));
More numbers & charts
Seen something you like?
Add it to your selection and send it over. We build your site around it, in your brand.
Get in touch →