Beeldsequentie op scroll Nieuw
Een reeks van 24 frames op een canvas die met de scroll meeloopt, zoals productpagina's een draaiend product tonen. Hier gegenereerd uit drie foto's van een zolderverbouwing (crossfade en lichte zoom); in productie laad je een geëxporteerde reeks (frame-01.webp tot frame-24.webp) vooraf. Canvas 2D + scroll-event met requestAnimationFrame; bij minder beweging geen zoom en geen automatisch voorbeeldscrollen.



Week 1: kale zolder van 22 m²
Week 2: isoleren en aftimmeren
Week 3: een slaapkamer erbij
Daarna loopt de pagina door met de offerte en de planning.
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.
<!-- Beeldsequentie op scroll · NedDev · eigen werk. -->
<div class="jouw-container">
<div class="sbs">
<div class="sbs__src" hidden><img src="https://neddev.com/lab-media/scroll-beeldsequentie-1.webp" alt=""><img src="https://neddev.com/lab-media/scroll-beeldsequentie-2.webp" alt=""><img src="https://neddev.com/lab-media/scroll-beeldsequentie-3.webp" alt=""></div>
<div class="sbs__sc" tabindex="0" aria-label="Voorbeeld: zolderverbouwing in Haarlem, scroll om de verbouwing te volgen">
<div class="sbs__track"><div class="sbs__pin">
<canvas class="sbs__cv" role="img" aria-label="Zolder in Haarlem, van kale ruimte tot slaapkamer"></canvas>
<div class="sbs__cap"><p class="sbs__t is-on">Week 1: kale zolder van 22 m²</p><p class="sbs__t">Week 2: isoleren en aftimmeren</p><p class="sbs__t">Week 3: een slaapkamer erbij</p></div>
<div class="sbs__bar" aria-hidden="true"><i class="sbs__fill"></i></div>
</div></div>
<div class="sbs__na"><p>Daarna loopt de pagina door met de offerte en de planning.</p></div>
</div>
</div>
</div>/* Beeldsequentie op scroll · NedDev · eigen werk. */
.sbs{position:absolute;inset:0;background:#0a0a0a;color:#fff;font-family:'Switzer',sans-serif;container-type:size;text-align:left}
.sbs__sc{position:absolute;inset:0;overflow-y:auto;overflow-x:hidden;scrollbar-width:thin;scrollbar-color:#5c5a57 transparent}
.sbs__sc:focus-visible{outline:2px solid #F5480F;outline-offset:-2px}
.sbs__track{height:420cqh;position:relative}
.sbs__pin{position:sticky;top:0;height:100cqh;overflow:hidden}
.sbs__cv{display:block;width:100%;height:100%;background:#222}
.sbs__cap{position:absolute;left:16px;right:16px;bottom:20px;height:1.4em;font:600 clamp(14px,4.4cqi,24px)/1.2 'Switzer',sans-serif;letter-spacing:-.01em;text-shadow:0 1px 12px rgba(0,0,0,.55)}
.sbs__t{position:absolute;left:0;bottom:0;margin:0;opacity:0;transform:translateY(6px);transition:opacity .25s,transform .25s}
.sbs__t.is-on{opacity:1;transform:none}
.sbs__bar{position:absolute;left:16px;right:16px;bottom:10px;height:2px;background:rgba(255,255,255,.3)}
.sbs__fill{display:block;height:100%;width:100%;background:#F5480F;transform-origin:left;transform:scaleX(0)}
.sbs__na{padding:18px 16px 28px;font:400 14px/1.5 'Switzer',sans-serif;color:#d6d3ce}
.sbs__na p{margin:0}
@media (prefers-reduced-motion:reduce){.sbs__t{transition:none;transform:none}}// Beeldsequentie op scroll · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
var w=root.querySelector('.sbs');if(!w)return;
var sc=w.querySelector('.sbs__sc'),cv=w.querySelector('.sbs__cv'),ctx=cv.getContext('2d');
var imgs=[].slice.call(w.querySelectorAll('.sbs__src img')),caps=[].slice.call(w.querySelectorAll('.sbs__t')),fill=w.querySelector('.sbs__fill');
var rm=matchMedia('(prefers-reduced-motion: reduce)').matches,F=24,frames=[],cur=-1,W=0,H=0;
function cover(c,im,z,a){if(!im.naturalWidth)return;var s=Math.max(W/im.naturalWidth,H/im.naturalHeight)*z,dw=im.naturalWidth*s,dh=im.naturalHeight*s;c.globalAlpha=a;c.drawImage(im,(W-dw)/2,(H-dh)/2,dw,dh);c.globalAlpha=1;}
function build(){var d=Math.min(2,window.devicePixelRatio||1);W=Math.round(cv.clientWidth*d);H=Math.round(cv.clientHeight*d);if(!W||!H)return;cv.width=W;cv.height=H;frames=[];
for(var f=0;f<F;f++){var t=f/(F-1)*(imgs.length-1),k=Math.min(imgs.length-2,Math.floor(t)),a=t-k,e=a*a*(3-2*a);
var oc=document.createElement('canvas');oc.width=W;oc.height=H;var c=oc.getContext('2d');c.fillStyle='#222';c.fillRect(0,0,W,H);
var z1=rm?1:1+.07*a,z2=rm?1:1.07-.07*a;cover(c,imgs[k],z1,1);cover(c,imgs[k+1],z2,e);frames.push(oc);}cur=-1;draw();}
function draw(){var max=sc.scrollHeight-sc.clientHeight,tr=w.querySelector('.sbs__track').offsetHeight-sc.clientHeight;var p=tr>0?Math.min(1,sc.scrollTop/tr):0;
var f=Math.round(p*(F-1));fill.style.transform='scaleX('+p.toFixed(3)+')';
if(f!==cur&&frames[f]){cur=f;ctx.clearRect(0,0,W,H);ctx.drawImage(frames[f],0,0);}
var ci=p<.36?0:(p<.72?1:2);caps.forEach(function(el,j){el.classList.toggle('is-on',j===ci);});}
var tick=false;sc.addEventListener('scroll',function(){if(!tick){tick=true;requestAnimationFrame(function(){tick=false;draw();});}},{passive:true});
var left=imgs.length;function ready(){if(--left<=0)build();}
imgs.forEach(function(im){if(im.complete&&im.naturalWidth)ready();else{im.addEventListener('load',ready);im.addEventListener('error',ready);}});
var rt;if('ResizeObserver' in window)new ResizeObserver(function(){clearTimeout(rt);rt=setTimeout(build,120);}).observe(cv);
if(!rm){var on=true,t0=0;['wheel','touchstart','pointerdown','keydown','focusin'].forEach(function(ev){w.addEventListener(ev,function(){on=false;},{passive:true});});
var tr=w.querySelector('.sbs__track');function drift(now){if(!on)return;if(!t0)t0=now;var m=tr.offsetHeight-sc.clientHeight;sc.scrollTop=m*(.5-.5*Math.cos((now-t0)/1000*.4));requestAnimationFrame(drift);}
setTimeout(function(){if(on)requestAnimationFrame(drift);},900);}
}
init(document.querySelector('.jouw-container'));
Meer uit scroll-effecten
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 →