Carrousel basis (snap, autoplay met pauze) Nieuw
Projectcarrousel op scroll-snap met pijlen, bolletjes, teller 01/05, voortgangsbalk (scroll-driven animation), swipe, slepen met de muis, pijltjestoetsen en autoplay met zichtbare pauzeknop (WCAG 2.2.2). Pauzeert ook bij hover, focus en buiten beeld; start niet bij reduced-motion. Waar de browser ::scroll-button en ::scroll-marker kent, nemen de native knoppen en bolletjes het over; elders doen kleine JS-knoppen hetzelfde.





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.
<!-- Carrousel basis (snap, autoplay met pauze) · NedDev · eigen werk. -->
<div class="jouw-container">
<div class="crb" role="region" aria-roledescription="carrousel" aria-label="Recente projecten">
<div class="crb__track" tabindex="0" aria-label="Projecten, gebruik de pijltjestoetsen">
<figure class="crb__slide" role="group" aria-roledescription="dia" aria-label="1 van 5"><img src="https://neddev.com/lab-media/carousel-basis-1.webp" alt="Gerenoveerde woonkamer met brede eiken vloer in een grachtenpand" width="800" height="436" draggable="false"><figcaption class="crb__cap"><span class="crb__tag">Renovatie · 2026</span><strong>Grachtenpand, Amsterdam</strong></figcaption></figure>
<figure class="crb__slide" role="group" aria-roledescription="dia" aria-label="2 van 5"><img src="https://neddev.com/lab-media/carousel-basis-2.webp" alt="Nieuwe antraciet dakkapel op een bakstenen rijtjeshuis" width="800" height="436" loading="lazy" draggable="false"><figcaption class="crb__cap"><span class="crb__tag">Dakkapel · 2026</span><strong>Rijtjeshuis, Utrecht</strong></figcaption></figure>
<figure class="crb__slide" role="group" aria-roledescription="dia" aria-label="3 van 5"><img src="https://neddev.com/lab-media/carousel-basis-3.webp" alt="Badkamer met grijze tegels en inloopdouche met zwart kozijn" width="800" height="436" loading="lazy" draggable="false"><figcaption class="crb__cap"><span class="crb__tag">Badkamer · 2025</span><strong>Jaren-dertigwoning, Den Haag</strong></figcaption></figure>
<figure class="crb__slide" role="group" aria-roledescription="dia" aria-label="4 van 5"><img src="https://neddev.com/lab-media/carousel-basis-4.webp" alt="Keuken met lichte houten fronten en betonlook werkblad" width="800" height="436" loading="lazy" draggable="false"><figcaption class="crb__cap"><span class="crb__tag">Keuken · 2025</span><strong>Tussenwoning, Haarlem</strong></figcaption></figure>
<figure class="crb__slide" role="group" aria-roledescription="dia" aria-label="5 van 5"><img src="https://neddev.com/lab-media/carousel-basis-5.webp" alt="Gerestaureerde bakstenen gevel met nieuwe witte houten kozijnen" width="800" height="436" loading="lazy" draggable="false"><figcaption class="crb__cap"><span class="crb__tag">Gevel · 2025</span><strong>Monumentaal pand, Leiden</strong></figcaption></figure>
</div>
<div class="crb__dots" role="group" aria-label="Kies een project"></div>
<div class="crb__bar">
<button class="crb__play" type="button" aria-label="Pauzeer automatisch afspelen"><svg class="crb__ring" viewBox="0 0 36 36" aria-hidden="true"><circle cx="18" cy="18" r="16"/></svg><svg class="crb__ic crb__ic--pause" viewBox="0 0 24 24" aria-hidden="true"><path d="M9 6v12M15 6v12"/></svg><svg class="crb__ic crb__ic--play" viewBox="0 0 24 24" aria-hidden="true"><path d="M8 5v14l11-7z"/></svg></button>
<span class="crb__count" aria-hidden="true"><b>01</b> / 05</span>
<span class="crb__prog" aria-hidden="true"><span class="crb__fill"></span></span>
<button class="crb__nav crb__prev" type="button" aria-label="Vorig project"><span aria-hidden="true">←</span></button>
<button class="crb__nav crb__next" type="button" aria-label="Volgend project"><span aria-hidden="true">→</span></button>
</div>
<p class="crb__sr" aria-live="off"></p>
</div>
</div>/* Carrousel basis (snap, autoplay met pauze) · NedDev · eigen werk. */
.crb{--pop:#F5480F;position:absolute;inset:0;display:flex;flex-direction:column;background:#0a0a0a;color:#fff;font-family:'Switzer',system-ui,sans-serif;timeline-scope:--crbx;overflow:hidden}
.crb__track{position:relative;flex:1;min-height:0;display:flex;gap:8px;overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain;scroll-snap-type:x mandatory;scrollbar-width:none;scroll-timeline:--crbx x;outline:none}
.crb__track::-webkit-scrollbar{display:none}
.crb__track:focus-visible{box-shadow:inset 0 0 0 2px var(--pop)}
.crb.is-drag .crb__track{scroll-snap-type:none;cursor:grabbing}
.crb__slide{position:relative;flex:0 0 86%;margin:0;scroll-snap-align:start;overflow:hidden;background:#1b1b1b}
.crb__slide:last-child{scroll-snap-align:end}
.crb__slide img{display:block;width:100%;height:100%;object-fit:cover;user-select:none}
.crb__cap{position:absolute;left:0;right:0;bottom:0;padding:30px 14px 12px;background:linear-gradient(rgba(10,10,10,0),rgba(10,10,10,.8));display:flex;flex-direction:column;gap:3px}
.crb__tag{font:600 9.5px/1 'JetBrains Mono',monospace;letter-spacing:.12em;text-transform:uppercase;color:#ffd9c9}
.crb__cap strong{font-size:15px;font-weight:600;line-height:1.2}
.crb__dots{position:absolute;right:10px;bottom:52px;display:flex;z-index:2}
.crb__dot{width:22px;height:24px;padding:0;border:0;background:none;cursor:pointer;display:grid;place-items:center}
.crb__dot::before{content:'';width:8px;height:8px;border-radius:4px;background:rgba(255,255,255,.55);transition:width .2s,background .2s}
.crb__dot:hover::before{background:#fff}
.crb__dot[aria-current=true]::before{width:18px;background:#fff}
.crb__bar{flex:none;height:46px;display:flex;align-items:center;gap:10px;padding:0 10px}
.crb__count{font:600 12px/1 'JetBrains Mono',monospace;font-variant-numeric:tabular-nums;color:#9a9a9a;white-space:nowrap}
.crb__count b{color:#fff}
.crb__prog{flex:1;height:2px;background:rgba(255,255,255,.2);position:relative;overflow:hidden}
.crb__fill{position:absolute;inset:0;background:var(--pop);transform-origin:left;transform:scaleX(.2)}
@supports (animation-timeline:scroll()){.crb__fill{animation:crb-fill linear both;animation-timeline:--crbx}}
@keyframes crb-fill{from{transform:scaleX(.2)}to{transform:scaleX(1)}}
.crb__nav,.crb__play{position:relative;flex:none;width:32px;height:32px;border-radius:50%;border:1px solid rgba(255,255,255,.3);background:transparent;color:#fff;cursor:pointer;display:grid;place-items:center;padding:0;font:500 15px/1 system-ui,sans-serif;transition:background .15s,border-color .15s}
.crb__nav:hover,.crb__play:hover{border-color:#fff;background:rgba(255,255,255,.08)}
.crb__nav:active,.crb__play:active{background:rgba(255,255,255,.16)}
.crb__nav:disabled{opacity:.35;cursor:default;background:transparent;border-color:rgba(255,255,255,.3)}
.crb__nav:focus-visible,.crb__play:focus-visible,.crb__dot:focus-visible{outline:2px solid var(--pop);outline-offset:2px}
.crb__play{border-color:transparent}
.crb__ring{position:absolute;inset:-1px;width:32px;height:32px;transform:rotate(-90deg)}
.crb__ring circle{fill:none;stroke:rgba(255,255,255,.3);stroke-width:2;stroke-dasharray:100.6;stroke-dashoffset:0}
.crb.is-playing .crb__ring circle{stroke:var(--pop);animation:crb-ring var(--crb-d,5s) linear forwards}
.crb.is-held .crb__ring circle{animation-play-state:paused}
@keyframes crb-ring{from{stroke-dashoffset:100.6}to{stroke-dashoffset:0}}
.crb__ic{width:14px;height:14px;fill:none;stroke:#fff;stroke-width:2.4;stroke-linecap:round}
.crb__ic--play{fill:#fff;stroke:none;display:none}
.crb.is-paused .crb__ic--play{display:block}.crb.is-paused .crb__ic--pause{display:none}
.crb__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;margin:0}
@supports selector(::scroll-marker){
.crb--native .crb__dots,.crb--native .crb__nav{display:none}
.crb--native .crb__track{scroll-marker-group:after}
.crb--native .crb__track::scroll-marker-group{position:absolute;right:14px;bottom:60px;display:flex;gap:8px;z-index:2}
.crb--native .crb__slide::scroll-marker{content:'';width:8px;height:8px;border-radius:4px;background:rgba(255,255,255,.55);transition:width .2s,background .2s}
.crb--native .crb__slide::scroll-marker:target-current{width:18px;background:#fff}
.crb--native .crb__slide::scroll-marker:focus-visible{outline:2px solid var(--pop);outline-offset:3px}
.crb--native .crb__track::scroll-button(*){position:absolute;bottom:7px;width:32px;height:32px;border-radius:50%;border:1px solid rgba(255,255,255,.3);background:#0a0a0a;color:#fff;cursor:pointer;font:500 15px/1 system-ui,sans-serif;z-index:3}
.crb--native .crb__track::scroll-button(*):hover{border-color:#fff}
.crb--native .crb__track::scroll-button(*):focus-visible{outline:2px solid var(--pop);outline-offset:2px}
.crb--native .crb__track::scroll-button(*):disabled{opacity:.35;cursor:default}
.crb--native .crb__track::scroll-button(inline-start){content:'←' / 'Vorig project';right:52px}
.crb--native .crb__track::scroll-button(inline-end){content:'→' / 'Volgend project';right:10px}
.crb--native .crb__bar{padding-right:96px}
}
@media (max-width:420px){.crb__cap strong{font-size:13.5px}.crb__cap{padding:24px 12px 10px}}
@media (prefers-reduced-motion:reduce){.crb__track{scroll-behavior:auto}.crb__dot::before{transition:none}}// Carrousel basis (snap, autoplay met pauze) · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
var w=root.querySelector('.crb');if(!w)return;
var tr=w.querySelector('.crb__track'),sl=[].slice.call(w.querySelectorAll('.crb__slide')),N=sl.length,dots=w.querySelector('.crb__dots'),cnt=w.querySelector('.crb__count b'),sr=w.querySelector('.crb__sr'),play=w.querySelector('.crb__play'),prev=w.querySelector('.crb__prev'),next=w.querySelector('.crb__next'),fill=w.querySelector('.crb__fill');
var reduce=matchMedia('(prefers-reduced-motion: reduce)').matches,DUR=5000,idx=0;
if(window.CSS&&CSS.supports('selector(::scroll-marker)'))w.classList.add('crb--native');
var pad=function(n){return (n<10?'0':'')+n;};
sl.forEach(function(s,i){var b=document.createElement('button');b.type='button';b.className='crb__dot';b.setAttribute('aria-label','Project '+(i+1)+': '+s.querySelector('strong').textContent);b.addEventListener('click',function(){go(i);hold('user');});dots.appendChild(b);});
var db=[].slice.call(dots.children);
function maxX(){return tr.scrollWidth-tr.clientWidth;}
function go(i){i=(i+N)%N;var x=Math.min(sl[i].offsetLeft-sl[0].offsetLeft,maxX());tr.scrollTo({left:x,behavior:reduce?'auto':'smooth'});}
function sync(){var x=tr.scrollLeft,st=sl.length>1?sl[1].offsetLeft-sl[0].offsetLeft:1,i=x>=maxX()-2?N-1:Math.round(x/st);if(i===idx&&cnt.textContent===pad(i+1))return;idx=i;cnt.textContent=pad(i+1);
db.forEach(function(d,j){d.setAttribute('aria-current',j===i?'true':'false');});prev.disabled=false;next.disabled=false;
sr.textContent='Project '+(i+1)+' van '+N+': '+sl[i].querySelector('strong').textContent;
if(!(window.CSS&&CSS.supports('animation-timeline: scroll()')))fill.style.transform='scaleX('+(.2+.8*(maxX()?x/maxX():0))+')';}
var raf=0;tr.addEventListener('scroll',function(){cancelAnimationFrame(raf);raf=requestAnimationFrame(sync);},{passive:true});
prev.addEventListener('click',function(){go(idx-1);hold('user');});next.addEventListener('click',function(){go(idx+1);hold('user');});
w.addEventListener('keydown',function(e){if(e.target.closest('.crb__bar'))return;var k=e.key;if(k==='ArrowRight'){e.preventDefault();go(idx+1);}else if(k==='ArrowLeft'){e.preventDefault();go(idx-1);}else if(k==='Home'){e.preventDefault();go(0);}else if(k==='End'){e.preventDefault();go(N-1);}});
// autoplay: timer met resterende tijd, zodat hover/focus echt pauzeert
var userPaused=reduce,holds={},tm=0,left=DUR,t0=0,inView=true;
function running(){return !userPaused&&!Object.keys(holds).length&&inView&&!document.hidden;}
function restartRing(){w.classList.remove('is-playing');void w.offsetWidth;if(!userPaused)w.classList.add('is-playing');}
function arm(){clearTimeout(tm);if(!running())return;t0=performance.now();tm=setTimeout(function(){go(idx+1);left=DUR;restartRing();arm();},left);}
function stop(){if(tm){clearTimeout(tm);tm=0;left=Math.max(200,left-(performance.now()-t0));}}
function update(){w.classList.toggle('is-held',!running()&&!userPaused);w.classList.toggle('is-paused',userPaused);
play.setAttribute('aria-label',userPaused?'Start automatisch afspelen':'Pauzeer automatisch afspelen');tr.setAttribute('aria-live',userPaused?'polite':'off');sr.setAttribute('aria-live',userPaused?'polite':'off');
if(running())arm();else stop();}
function hold(k,on){if(on===false)delete holds[k];else holds[k]=1;if(k==='user'){delete holds.user;left=DUR;restartRing();}update();}
play.addEventListener('click',function(){userPaused=!userPaused;left=DUR;restartRing();if(!userPaused){delete holds.focus;}update();});
w.addEventListener('pointerenter',function(e){if(e.pointerType==='mouse')hold('hover');});w.addEventListener('pointerleave',function(){hold('hover',false);});
w.addEventListener('focusin',function(e){if(e.target!==play)hold('focus');});w.addEventListener('focusout',function(e){if(!w.contains(e.relatedTarget))hold('focus',false);});
document.addEventListener('visibilitychange',update);
if('IntersectionObserver' in window)new IntersectionObserver(function(es){inView=es[0].isIntersecting;update();},{threshold:.4}).observe(w);
// slepen met de muis (touch swipe is native scroll)
var dx=0,sx=0,si=0,drag=false;
tr.addEventListener('pointerdown',function(e){if(e.pointerType!=='mouse'||e.button)return;drag=true;dx=e.clientX;sx=tr.scrollLeft;si=idx;w.classList.add('is-drag');tr.setPointerCapture(e.pointerId);});
tr.addEventListener('pointermove',function(e){if(drag)tr.scrollLeft=sx-(e.clientX-dx);});
function endDrag(e){if(!drag)return;drag=false;w.classList.remove('is-drag');var d=e.clientX-dx;go(Math.max(0,Math.min(N-1,Math.abs(d)>40?si+(d<0?1:-1):si)));hold('user');}
tr.addEventListener('pointerup',endDrag);tr.addEventListener('pointercancel',endDrag);
idx=-1;sync();restartRing();update();
}
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 →