Verhalen met voortgangsbalkjes Nieuw
Verhalen zoals op Instagram, maar op de eigen site: staande beelden met balkjes bovenin die vollopen, tikken links of rechts voor vorige of volgende, ingedrukt houden pauzeert, en een zichtbare pauzeknop. Rondjes ernaast springen naar een verhaal en worden grijs als het gezien is. Pijltjes, spatie en Home werken; bij minder beweging start hij gepauzeerd en buiten beeld staat hij stil. Voor bakkers, restaurants en winkels die dagelijks iets nieuws laten zien.
Bakkerij De Korenaar
Twijnstraat, Utrecht
De Korenaar2 u
Zo gebruik je het
- Plak de HTML op de plek waar het onderdeel moet komen.
- Zet de CSS in je stylesheet. Alle klassen hebben een eigen voorvoegsel, dus niets botst met je eigen opmaak.
- Voeg de JavaScript toe en roep init() aan met het element waar de HTML in staat.
<!-- Verhalen met voortgangsbalkjes · NedDev · eigen werk. -->
<div class="jouw-container">
<div class="vst">
<div class="vst__side">
<b class="vst__nm">Bakkerij De Korenaar</b>
<span class="vst__pl">Twijnstraat, Utrecht</span>
<div class="vst__hl" role="group" aria-label="Verhalen">
<button class="vst__h" type="button" data-i="0"><span class="vst__r"><img src="https://neddev.com/lab-media/verhalen-stories-1.webp" alt="" width="768" height="1376" loading="lazy"></span>Oven</button>
<button class="vst__h" type="button" data-i="1"><span class="vst__r"><img src="https://neddev.com/lab-media/verhalen-stories-2.webp" alt="" width="768" height="1376" loading="lazy"></span>Croissants</button>
<button class="vst__h" type="button" data-i="2"><span class="vst__r"><img src="https://neddev.com/lab-media/verhalen-stories-3.webp" alt="" width="768" height="1376" loading="lazy"></span>Winkel</button>
<button class="vst__h" type="button" data-i="3"><span class="vst__r"><img src="https://neddev.com/lab-media/verhalen-stories-4.webp" alt="" width="768" height="1376" loading="lazy"></span>Bezorgen</button>
</div>
</div>
<div class="vst__ph" role="region" aria-roledescription="verhaal" aria-label="Verhalen van Bakkerij De Korenaar">
<div class="vst__imgs">
<img class="vst__im is-on" src="https://neddev.com/lab-media/verhalen-stories-1.webp" alt="Bakker haalt zuurdesembroden uit de steenoven" width="768" height="1376">
<img class="vst__im" src="https://neddev.com/lab-media/verhalen-stories-2.webp" alt="Verse croissants op een rooster" width="768" height="1376" loading="lazy">
<img class="vst__im" src="https://neddev.com/lab-media/verhalen-stories-3.webp" alt="Etalage met brood op houten planken" width="768" height="1376" loading="lazy">
<img class="vst__im" src="https://neddev.com/lab-media/verhalen-stories-4.webp" alt="Bakfiets met broodkratten aan de gracht" width="768" height="1376" loading="lazy">
</div>
<div class="vst__bars" aria-hidden="true"><span><i></i></span><span><i></i></span><span><i></i></span><span><i></i></span></div>
<div class="vst__hd"><span class="vst__who">De Korenaar</span><span class="vst__ago">2 u</span><button class="vst__pause" type="button" aria-label="Pauzeren"><svg class="vst__i-pa" viewBox="0 0 24 24" aria-hidden="true"><path d="M7 5h3.5v14H7zM13.5 5H17v14h-3.5z"/></svg><svg class="vst__i-pl" viewBox="0 0 24 24" aria-hidden="true"><path d="M8 5.5v13l10.5-6.5z"/></svg></button></div>
<button class="vst__tap vst__tap--l" type="button" aria-label="Vorig verhaal"></button>
<button class="vst__tap vst__tap--r" type="button" aria-label="Volgend verhaal"></button>
<p class="vst__cap" aria-live="polite"></p>
</div>
</div>
</div>/* Verhalen met voortgangsbalkjes · NedDev · eigen werk. */
.vst{position:absolute;inset:0;display:grid;grid-template-columns:1fr auto;gap:14px;padding:10px 14px 10px 16px;background:#f4f3f1;color:#0a0a0a;font-family:'Switzer',sans-serif;text-align:left;container-type:size}
.vst__side{display:flex;flex-direction:column;justify-content:center;min-width:0}
.vst__nm{font:600 15px/1.2 'Switzer',sans-serif;letter-spacing:-.01em}
.vst__pl{font:400 12px/1.3 'Switzer',sans-serif;color:#5c5a57;margin-bottom:10px}
.vst__hl{display:grid;grid-template-columns:repeat(2,minmax(0,64px));gap:8px 10px}
.vst__h{display:flex;flex-direction:column;align-items:center;gap:4px;padding:2px 0;border:0;background:none;color:#0a0a0a;font:500 12px/1.1 'Switzer',sans-serif;cursor:pointer;border-radius:4px}
.vst__r{display:block;width:44px;height:44px;padding:2px;border-radius:50%;border:2px solid #F5480F;box-sizing:border-box}
.vst__r img{display:block;width:100%;height:100%;border-radius:50%;object-fit:cover}
.vst__h.is-seen .vst__r{border-color:#c9c5bf}
.vst__h[aria-current="true"] .vst__r{border-color:#0a0a0a}
.vst__h:focus-visible{outline:2px solid #F5480F;outline-offset:2px}
.vst__ph{position:relative;height:calc(100cqh - 20px);aspect-ratio:9/16;border-radius:8px;overflow:hidden;background:#0a0a0a;color:#fff;user-select:none;-webkit-user-select:none;touch-action:manipulation}
.vst__imgs,.vst__im{position:absolute;inset:0}
.vst__im{width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity .25s}
.vst__im.is-on{opacity:1}
.vst__ph.is-run .vst__im.is-on{animation:vst-zoom 5s linear both}
.vst__ph.is-hold .vst__im.is-on,.vst__ph.is-paused .vst__im.is-on{animation-play-state:paused}
@keyframes vst-zoom{from{transform:scale(1)}to{transform:scale(1.06)}}
.vst__ph::before,.vst__ph::after{content:"";position:absolute;left:0;right:0;z-index:1;pointer-events:none}
.vst__ph::before{top:0;height:60px;background:linear-gradient(rgba(10,10,10,.55),rgba(10,10,10,0))}
.vst__ph::after{bottom:0;height:90px;background:linear-gradient(rgba(10,10,10,0),rgba(10,10,10,.7))}
.vst__bars{position:absolute;left:6px;right:6px;top:6px;z-index:2;display:flex;gap:3px}
.vst__bars span{flex:1;height:3px;border-radius:2px;background:rgba(255,255,255,.4);overflow:hidden}
.vst__bars i{display:block;height:100%;background:#fff;transform-origin:left;transform:scaleX(0)}
.vst__hd{position:absolute;left:8px;right:4px;top:12px;z-index:3;display:flex;align-items:center;gap:6px;font:600 12px/1 'Switzer',sans-serif}
.vst__ago{font-weight:400;color:#d6d3ce}
.vst__who{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
@container (max-width:360px){.vst__ago{display:none}}
.vst__pause{flex:none;margin-left:auto;width:26px;height:26px;padding:0;border:0;border-radius:4px;background:transparent;color:#fff;display:grid;place-items:center;cursor:pointer}
.vst__pause svg{width:16px;height:16px;fill:currentColor}
.vst__i-pl{display:none}.vst__ph.is-paused .vst__i-pl{display:block}.vst__ph.is-paused .vst__i-pa{display:none}
.vst__tap{position:absolute;top:44px;bottom:0;z-index:2;border:0;background:transparent;padding:0;cursor:pointer}
.vst__tap--l{left:0;width:35%}.vst__tap--r{right:0;width:65%}
.vst__tap:focus-visible,.vst__pause:focus-visible{outline:2px solid #F5480F;outline-offset:-2px}
.vst__cap{position:absolute;left:10px;right:10px;bottom:10px;z-index:3;margin:0;font:600 13px/1.3 'Switzer',sans-serif;pointer-events:none}
@container (max-height:225px){.vst__hl{gap:4px 8px}.vst__r{width:38px;height:38px}.vst__pl{margin-bottom:6px}.vst__cap{font-size:12px}}
@media (prefers-reduced-motion:reduce){.vst__im{transition:none}.vst__ph.is-run .vst__im.is-on{animation:none}}// Verhalen met voortgangsbalkjes · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
var w=root.querySelector('.vst');if(!w)return;
var ph=w.querySelector('.vst__ph'),ims=[].slice.call(w.querySelectorAll('.vst__im')),bars=[].slice.call(w.querySelectorAll('.vst__bars i')),hs=[].slice.call(w.querySelectorAll('.vst__h')),cap=w.querySelector('.vst__cap'),pb=w.querySelector('.vst__pause');
var T=['Om half zes gaat de steenoven aan.','Croissants met roomboter, vanaf 7 uur in de winkel.','Vandaag extra: speltbrood met walnoot, € 4,95.','Bezorging in Utrecht-Oost. Bestel voor 10 uur, morgen bij je thuis.'];
var rm=matchMedia('(prefers-reduced-motion: reduce)').matches,N=ims.length,cur=0,el=0,DUR=5000,paused=rm,hold=false,vis=false,last=0,raf=0;
function show(i){cur=(i+N)%N;el=0;ims.forEach(function(m,k){m.classList.toggle('is-on',k===cur);});ph.classList.remove('is-run');void ph.offsetWidth;if(!paused&&!rm)ph.classList.add('is-run');
bars.forEach(function(b,k){b.style.transform='scaleX('+(k<cur?1:0)+')';});cap.textContent=T[cur];
hs.forEach(function(h,k){if(k===cur){h.setAttribute('aria-current','true');h.classList.add('is-seen');}else h.removeAttribute('aria-current');});}
function setPause(p){paused=p;ph.classList.toggle('is-paused',p);pb.setAttribute('aria-label',p?'Afspelen':'Pauzeren');if(!p&&!rm&&!ph.classList.contains('is-run'))ph.classList.add('is-run');}
function tick(now){raf=requestAnimationFrame(tick);var dt=last?Math.min(100,now-last):0;last=now;if(paused||hold||!vis||document.hidden)return;el+=dt;bars[cur].style.transform='scaleX('+Math.min(1,el/DUR).toFixed(4)+')';if(el>=DUR){if(cur===N-1){bars[cur].style.transform='scaleX(1)';setPause(true);}else show(cur+1);}}
w.querySelector('.vst__tap--l').addEventListener('click',function(){show(cur-1);});
w.querySelector('.vst__tap--r').addEventListener('click',function(){if(cur===N-1&&paused){show(0);setPause(false);}else show(cur+1);});
pb.addEventListener('click',function(){if(paused&&cur===N-1&&el>=DUR)show(0);setPause(!paused);});
hs.forEach(function(h,i){h.addEventListener('click',function(){show(i);if(!rm)setPause(false);});});
var ht=0;ph.addEventListener('pointerdown',function(e){if(e.target.closest('.vst__pause'))return;ht=setTimeout(function(){hold=true;ph.classList.add('is-hold');},180);});
function rel(e){clearTimeout(ht);if(hold){hold=false;ph.classList.remove('is-hold');if(e&&e.type==='pointerup'){var stop=function(ev){ev.stopPropagation();ev.preventDefault();ph.removeEventListener('click',stop,true);};ph.addEventListener('click',stop,true);setTimeout(function(){ph.removeEventListener('click',stop,true);},50);}}}
ph.addEventListener('pointerup',rel);ph.addEventListener('pointerleave',rel);ph.addEventListener('pointercancel',rel);
ph.addEventListener('keydown',function(e){var k=e.key;if(k==='ArrowRight'){show(cur+1);}else if(k==='ArrowLeft'){show(cur-1);}else if(k==='Home'){show(0);}else if(k===' '&&e.target.classList.contains('vst__tap')){e.preventDefault();setPause(!paused);}else return;if(k!==' ')e.preventDefault();});
new IntersectionObserver(function(es){es.forEach(function(e){vis=e.isIntersecting;});},{threshold:.4}).observe(ph);
setPause(rm);show(0);raf=requestAnimationFrame(tick);
}
init(document.querySelector('.jouw-container'));
Meer uit componenten
Iets gezien dat je mooi vindt?
Zet het in je selectie en stuur hem op. Dan bouwen we je site eromheen, in jouw huisstijl.
Neem contact op →