Stoom uit een kop koffie Nieuw
Uit een getekende kop stijgt stoom op die langzaam krult en uitwaaiert; beweeg je muis of vinger erlangs en je blaast de stoom opzij. Voor de hero of de menukaart van een koffiebar, lunchroom, bakkerij, theewinkel of koffiebranderij. Canvas 2D met één voorgetekende rookwolk die honderden keren hergebruikt wordt; met minder beweging hangt er een stille sliert boven de kop.
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.
<!-- Stoom uit een kop koffie · © 2026 NedDev BV · NedDev Lab-licentie (gratis gebruik, niet doorverkopen) · neddev.com/nl/lab/gebruik/#licentie -->
<div class="jouw-container">
<div class="skf">
<canvas class="skf__cv" aria-hidden="true"></canvas>
<svg class="skf__kop" viewBox="0 0 120 86" aria-hidden="true"><path d="M92 26h6a12 12 0 0 1 0 24h-8" fill="none" stroke="#F5480F" stroke-width="6"/><path d="M14 18h80v28a34 34 0 0 1-34 34H48a34 34 0 0 1-34-34z" fill="#F5480F"/><path d="M2 82h116" stroke="#fff" stroke-width="4" stroke-linecap="round"/></svg>
<div class="skf__txt"><p class="skf__h">Koffiebar De Kade, Leiden</p><p class="skf__p">Flat white € 3,90, havermelk zonder meerprijs</p><a class="skf__btn" href="#">Bekijk de kaart</a></div>
</div>
</div>/* Stoom uit een kop koffie · © 2026 NedDev BV · NedDev Lab-licentie (gratis gebruik, niet doorverkopen) · neddev.com/nl/lab/gebruik/#licentie */
.skf{position:absolute;inset:0;background:#0a0a0a;font-family:Switzer,sans-serif;color:#fff;overflow:hidden}
.skf__cv{position:absolute;inset:0;width:100%;height:100%;display:block;touch-action:pan-y}
.skf__kop{position:absolute;right:12%;bottom:12px;width:min(120px,28%);height:auto;pointer-events:none}
.skf__txt{position:absolute;left:16px;top:14px;max-width:52%;pointer-events:none}
.skf__h{margin:0;font:600 18px/1.2 Switzer,sans-serif}
.skf__p{margin:4px 0 10px;font:400 14px/1.35 Switzer,sans-serif;color:#d6d4d0}
.skf__btn{pointer-events:auto}
.skf__btn{display:inline-flex;align-items:center;min-height:36px;padding:0 14px;border:0;border-radius:4px;font:600 14px/1 Switzer,sans-serif;cursor:pointer;text-decoration:none;background:#fff;color:#0a0a0a;transition:background-color .18s,color .18s}
.skf__btn:hover{background:#F5480F;color:#0a0a0a}
.skf__btn:focus-visible{outline:2px solid #fff;outline-offset:3px}
@media (prefers-reduced-motion: reduce){.skf__btn{transition:none}}// Stoom uit een kop koffie · © 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 reduce=!!(window.matchMedia&&matchMedia('(prefers-reduced-motion: reduce)').matches);
function fit(cv,ctx,cb){var dpr=Math.min(window.devicePixelRatio||1,2);function f(){var r=cv.getBoundingClientRect();var w=Math.max(1,Math.round(r.width)),h=Math.max(1,Math.round(r.height));if(cv.width===Math.round(w*dpr)&&cv.height===Math.round(h*dpr))return;cv.width=Math.round(w*dpr);cv.height=Math.round(h*dpr);if(ctx)ctx.setTransform(dpr,0,0,dpr,0,0);if(cb)cb(w,h,dpr);}if(window.ResizeObserver)new ResizeObserver(f).observe(cv);f();}
function loop(el,step){var on=false,raf=0,last=0;function fr(t){raf=requestAnimationFrame(fr);var dt=last?Math.min(.05,(t-last)/1000):.016;last=t;step(dt,t/1000);}function go(v){if(v&&!on){on=true;last=0;raf=requestAnimationFrame(fr);}else if(!v&&on){on=false;cancelAnimationFrame(raf);}}if(window.IntersectionObserver)new IntersectionObserver(function(es){go(es[0].isIntersecting);}).observe(el);else go(true);}
var w = root.querySelector('.skf'), cv = w.querySelector('.skf__cv'), ctx = cv.getContext('2d'), kop = w.querySelector('.skf__kop');
var W = 1, H = 1, ox = 0, oy = 0, ow = 40, ps = [], t = 0, acc = 0, mx = -999, my = -999, pvx = 0, pvy = 0, lx = null, ly = null, lt = 0;
var spr = document.createElement('canvas'); spr.width = spr.height = 64; var sc = spr.getContext('2d');
var g = sc.createRadialGradient(32, 32, 0, 32, 32, 32); g.addColorStop(0, 'rgba(255,255,255,1)'); g.addColorStop(.5, 'rgba(255,255,255,.45)'); g.addColorStop(1, 'rgba(255,255,255,0)');
sc.fillStyle = g; sc.fillRect(0, 0, 64, 64);
function origin() { var r = w.getBoundingClientRect(), k = kop.getBoundingClientRect(); ox = k.left - r.left + k.width * .45; oy = k.top - r.top + k.height * .18; ow = k.width * .5; }
function spawn() { ps.push({ x: ox + (Math.random() - .5) * ow, y: oy, vx: (Math.random() - .5) * 4, vy: -(22 + Math.random() * 16), s: 8 + Math.random() * 6, life: 0, max: 2.6 + Math.random() * 1.6, seed: Math.random() * 20 }); }
function draw() {
ctx.clearRect(0, 0, W, H);
for (var i = 0; i < ps.length; i++) {
var p = ps[i], k = p.life / p.max, a = Math.sin(Math.PI * k) * .3, s = p.s + k * 50;
ctx.globalAlpha = a; ctx.drawImage(spr, p.x - s / 2, p.y - s / 2, s, s);
}
ctx.globalAlpha = 1;
}
function upd(dt) {
t += dt; acc += dt * 30; while (acc > 1) { spawn(); acc--; }
pvx *= Math.exp(-4 * dt); pvy *= Math.exp(-4 * dt);
for (var i = ps.length - 1; i >= 0; i--) {
var p = ps[i]; p.life += dt; if (p.life > p.max) { ps.splice(i, 1); continue; }
var d = Math.hypot(p.x - mx, p.y - my);
if (d < 110) { var f = (1 - d / 110) * 1.6 * dt; p.vx += pvx * f; p.vy += pvy * f; }
p.vx += Math.sin(t * 1.7 + p.y * .045 + p.seed) * 16 * dt; p.vx *= Math.exp(-.8 * dt);
p.x += p.vx * dt; p.y += p.vy * dt;
}
}
fit(cv, ctx, function (a, b) { W = a; H = b; origin(); if (reduce) { ps = []; for (var i = 0; i < 90; i++) upd(1 / 30); draw(); } });
cv.addEventListener('pointermove', function (e) {
var r = cv.getBoundingClientRect(), x = e.clientX - r.left, y = e.clientY - r.top, tn = performance.now();
if (lx != null) { var d = Math.max(8, tn - lt) / 1000; pvx = pvx * .5 + (x - lx) / d * .5; pvy = pvy * .5 + (y - ly) / d * .5; }
lx = mx = x; ly = my = y; lt = tn;
});
cv.addEventListener('pointerleave', function () { mx = my = -999; lx = ly = null; });
w.querySelector('.skf__btn').addEventListener('click', function (e) { e.preventDefault(); });
loop(w, function (dt) { if (reduce) return; upd(dt); draw(); });
}
init(document.querySelector('.jouw-container'));
Meer uit achtergronden & webgl
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 →