Ballenbak met klussen Nieuw
Klussen als ballen in een bak: gooi ze met muis of vinger in de mand, of tik er een aan (ook met Enter) en hij vliegt er vanzelf in of weer uit. De knop telt mee. Leuk als speelse intake op de homepage van een klusbedrijf of schilder, in plaats van een saaie lijst met vinkjes. Bij minder beweging leggen de ballen zich zonder vlucht in of naast de mand.
Wat moet er gebeuren?
Offerte voor 0 klussenGooi je klussen in de mand. Tik of sleep.
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.
<!-- Ballenbak met klussen · © 2026 NedDev BV · NedDev Lab-licentie (gratis gebruik, niet doorverkopen) · neddev.com/nl/lab/gebruik/#licentie -->
<div class="jouw-container">
<div class="fyb">
<div class="fyb__tekst"><div class="fyb__rij"><h3 class="fyb__kop">Wat moet er gebeuren?</h3><a class="fyb__cta" href="#fy-ballenbak">Offerte voor 0 klussen</a></div>
<p class="fyb__st">Gooi je klussen in de mand. Tik of sleep.</p></div>
<div class="fyb__mand" aria-hidden="true"></div>
<div class="fyb__ballen" role="group" aria-label="Klussen, tik om in of uit de mand te gooien">
<button class="fyb__bal" type="button" aria-pressed="false">verven</button>
<button class="fyb__bal" type="button" aria-pressed="false">goot</button>
<button class="fyb__bal" type="button" aria-pressed="false">tegels</button>
<button class="fyb__bal" type="button" aria-pressed="false">kozijn</button>
<button class="fyb__bal" type="button" aria-pressed="false">lekkage</button>
<button class="fyb__bal" type="button" aria-pressed="false">behang</button>
</div>
</div>
</div>/* Ballenbak met klussen · © 2026 NedDev BV · NedDev Lab-licentie (gratis gebruik, niet doorverkopen) · neddev.com/nl/lab/gebruik/#licentie */
.fyb{position:absolute;inset:0;overflow:hidden;background:#f4f3f1;color:#0a0a0a;font-family:'Switzer',sans-serif;text-align:left}
.fyb *{box-sizing:border-box}
.fyb__tekst{position:absolute;left:14px;top:12px;right:12px;display:flex;flex-direction:column;gap:4px;z-index:3;pointer-events:none}
.fyb__rij{display:flex;align-items:center;justify-content:space-between;gap:10px}
.fyb__kop{margin:0;font:700 17px/1.15 'Switzer',sans-serif;letter-spacing:-.01em;z-index:2;pointer-events:none}
.fyb__st{margin:0;font:400 13px/1.35 'Switzer',sans-serif;color:#3d3b37;z-index:2;pointer-events:none}
.fyb__cta{flex:0 0 auto;pointer-events:auto;padding:9px 12px;background:#0a0a0a;color:#fff;border-radius:4px;font:600 13px/1 'Switzer',sans-serif;text-decoration:none;transition:background-color .16s}
.fyb__cta:hover{background:#F5480F;color:#0a0a0a}
.fyb__cta:focus-visible{outline:2px solid #F5480F;outline-offset:2px}
.fyb__mand{position:absolute;right:0;bottom:0;left:52%;top:50%;border-left:4px solid #0a0a0a;background:repeating-linear-gradient(90deg,rgba(10,10,10,.06) 0 2px,transparent 2px 14px)}
.fyb__ballen{position:absolute;inset:0}
.fyb__bal{position:absolute;left:0;top:0;width:56px;height:56px;margin:0;padding:0;border:0;border-radius:50%;background:#fff;color:#0a0a0a;font:600 12px/1 'Switzer',sans-serif;cursor:grab;touch-action:none;user-select:none;-webkit-user-select:none;box-shadow:inset 0 -4px 0 rgba(10,10,10,.12);will-change:transform;z-index:1}
.fyb__bal:nth-child(3n+1){background:#F5480F;color:#0a0a0a}
.fyb__bal:nth-child(3n+2){background:#0a0a0a;color:#fff;box-shadow:inset 0 -4px 0 rgba(255,255,255,.14)}
.fyb__bal:active{cursor:grabbing}
.fyb__bal:focus-visible{outline:3px solid #0a0a0a;outline-offset:2px}
.fyb__bal:nth-child(3n+2):focus-visible{outline-color:#F5480F}// Ballenbak met klussen · © 2026 NedDev BV · NedDev Lab-licentie (gratis gebruik, niet doorverkopen) · neddev.com/nl/lab/gebruik/#licentie
// root = het element waarin de HTML staat
function init(root){
var el=root.querySelector('.fyb');if(!el)return;
var RM=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var mand=el.querySelector('.fyb__mand'),st=el.querySelector('.fyb__st'),cta=el.querySelector('.fyb__cta');
var B=[].slice.call(el.querySelectorAll('.fyb__bal')).map(function(n){return{n:n,x:0,y:0,vx:0,vy:0,m:false,drag:false,moved:0,dragged:false,lt:0,ox:0,oy:0};});
var W=1,H=1,R=28,wx=0,rim=0,WT=4,G=1600,raf=0,vis=true,last=0,klaar=false;
function size(){var r=el.getBoundingClientRect();if(!r.width)return;W=r.width;H=r.height;R=Math.max(25,Math.min(36,W*.075));wx=Math.round(W*.52);rim=Math.round(H*.5);mand.style.left=wx+'px';mand.style.top=rim+'px';
B.forEach(function(b){b.n.style.width=b.n.style.height=(2*R)+'px';b.x=Math.min(Math.max(b.x,R),W-R);b.y=Math.min(Math.max(b.y,R),H-R);});}
function draw(){B.forEach(function(b){b.n.style.transform='translate('+(b.x-R).toFixed(1)+'px,'+(b.y-R).toFixed(1)+'px)';});}
function upd(){var nm=[];B.forEach(function(b){var m=b.x>wx+WT&&b.y>rim-R*.2;if(m!==b.m){b.m=m;b.n.setAttribute('aria-pressed',m?'true':'false');}if(b.m)nm.push(b.n.textContent);});
var t=nm.length?'In de mand: '+nm.join(', ')+'.':'Gooi je klussen in de mand. Tik of sleep.';if(st.textContent!==t)st.textContent=t;
var c='Offerte voor '+nm.length+(nm.length===1?' klus':' klussen');if(cta.textContent!==c)cta.textContent=c;}
function layout(){var oi=0,ii=0,d=2*R+4;B.forEach(function(b){if(b.m){var p=Math.max(1,Math.floor((W-wx-WT)/d));b.x=wx+WT+R+2+(ii%p)*d;b.y=H-R-Math.floor(ii/p)*2*R;ii++;}else{var q=Math.max(1,Math.floor((wx-4)/d));b.x=R+4+(oi%q)*d;b.y=H-R-Math.floor(oi/q)*2*R;oi++;}b.vx=b.vy=0;});draw();}
function step(dt){var i,j,b;
for(i=0;i<B.length;i++){b=B[i];if(b.drag)continue;b.vy+=G*dt;b.vx*=.999;b.x+=b.vx*dt;b.y+=b.vy*dt;}
for(var it=0;it<3;it++){
for(i=0;i<B.length;i++)for(j=i+1;j<B.length;j++){var a=B[i],c=B[j],dx=c.x-a.x,dy=c.y-a.y,d2=dx*dx+dy*dy,mn=2*R;if(d2>=mn*mn||d2===0)continue;if((a.vlucht&&a.vy<0)||(c.vlucht&&c.vy<0))continue;
var d=Math.sqrt(d2),nx=dx/d,ny=dy/d,ov=mn-d,wa=a.drag?0:(c.drag?1:.5),wc=c.drag?0:(a.drag?1:.5);
a.x-=nx*ov*wa;a.y-=ny*ov*wa;c.x+=nx*ov*wc;c.y+=ny*ov*wc;var rv=(c.vx-a.vx)*nx+(c.vy-a.vy)*ny;if(rv<0){var jj=-1.3*rv;a.vx-=nx*jj*wa;a.vy-=ny*jj*wa;c.vx+=nx*jj*wc;c.vy+=ny*jj*wc;}}
for(i=0;i<B.length;i++){b=B[i];if(b.drag)continue;
if(b.x<R){b.x=R;b.vx=Math.abs(b.vx)*.5;}if(b.x>W-R){b.x=W-R;b.vx=-Math.abs(b.vx)*.5;}if(b.y<R){b.y=R;b.vy=Math.abs(b.vy)*.4;}
if(b.y>H-R){b.y=H-R;if(b.vy>0)b.vy=-b.vy*.32;if(Math.abs(b.vy)<45)b.vy=0;b.vx*=.97;if(Math.abs(b.vx)<6)b.vx=0;}
if(b.vlucht&&(performance.now()>b.vlucht||b.y>=H-R-1))b.vlucht=0;
if(!(b.vlucht&&b.y<rim+R*.6)){var cx=Math.max(wx,Math.min(b.x,wx+WT)),cy=Math.max(rim,Math.min(b.y,H)),ex=b.x-cx,ey=b.y-cy,e2=ex*ex+ey*ey;
if(e2<R*R){var nx2,ny2,pen;if(e2<1e-6){nx2=b.x<wx+WT/2?-1:1;ny2=0;pen=R;}else{var e=Math.sqrt(e2);nx2=ex/e;ny2=ey/e;pen=R-e;}b.x+=nx2*pen;b.y+=ny2*pen;var vn=b.vx*nx2+b.vy*ny2;if(vn<0){b.vx-=1.45*vn*nx2;b.vy-=1.45*vn*ny2;}}}}}}
function tick(t){if(!vis){raf=0;return;}var dt=Math.min(.033,(t-last)/1000);last=t;for(var k=0;k<3;k++)step(dt/3);draw();upd();raf=requestAnimationFrame(tick);}
function kick(){if(RM||raf)return;last=performance.now();raf=requestAnimationFrame(tick);}
function toss(b){if(RM){b.m=!b.m;layout();upd();return;}
var top=Math.max(R+2,rim-R-16),h=Math.max(30,b.y-top),vy0=-Math.sqrt(2*G*h),ta=-vy0/G,
tx=b.m?wx-R*.6-Math.random()*R:wx+WT+R*.6+Math.random()*R*.8;
b.vy=vy0;b.vx=(tx-b.x)/ta;b.vlucht=performance.now()+1600;kick();}
function pos(e,b){var r=el.getBoundingClientRect();return{x:Math.min(Math.max(e.clientX-r.left-b.ox,R),W-R),y:Math.min(Math.max(e.clientY-r.top-b.oy,R),H-R)};}
B.forEach(function(b){var n=b.n;
n.addEventListener('pointerdown',function(e){if(RM||e.button>0)return;var r=el.getBoundingClientRect();b.ox=e.clientX-r.left-b.x;b.oy=e.clientY-r.top-b.y;b.drag=true;b.moved=0;b.vx=b.vy=0;b.lt=performance.now();try{n.setPointerCapture(e.pointerId);}catch(_){}n.style.zIndex=2;kick();});
n.addEventListener('pointermove',function(e){if(!b.drag)return;var p=pos(e,b),now=performance.now(),dt=Math.max(.008,(now-b.lt)/1000);b.moved+=Math.abs(p.x-b.x)+Math.abs(p.y-b.y);b.vx=b.vx*.4+(p.x-b.x)/dt*.6;b.vy=b.vy*.4+(p.y-b.y)/dt*.6;b.x=p.x;b.y=p.y;b.lt=now;});
function up(){if(!b.drag)return;b.drag=false;n.style.zIndex='';if(b.moved>6){b.dragged=true;if(performance.now()-b.lt>90){b.vx=b.vy=0;}var s=Math.hypot(b.vx,b.vy);if(s>2400){b.vx*=2400/s;b.vy*=2400/s;}}else{b.vx=b.vy=0;}}
n.addEventListener('pointerup',up);n.addEventListener('pointercancel',up);
n.addEventListener('click',function(){if(b.dragged){b.dragged=false;return;}toss(b);});});
function start(){size();if(klaar)return;klaar=true;
if(RM){layout();upd();return;}
B.forEach(function(b,i){b.x=R+8+(i%3)*(2*R+12);b.y=R+8+Math.floor(i/3)*(2*R+4);b.vx=(Math.random()-.5)*120;b.vy=0;});draw();kick();}
if('ResizeObserver' in window)new ResizeObserver(function(){var w=W;size();if(RM&&klaar)layout();else if(w!==W)draw();}).observe(el);
if('IntersectionObserver' in window)new IntersectionObserver(function(es){vis=es[0].isIntersecting;if(vis)kick();}).observe(el);
start();
}
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 →