Stoom uit een kop koffie New
Steam rises from a drawn cup, curling and fanning out slowly; move your mouse or finger past it and you blow the steam aside. For the hero or menu of a coffee bar, lunchroom, bakery, tea shop or coffee roaster. Canvas 2D with one pre-drawn puff reused hundreds of times; with reduced motion a still wisp hangs above the cup.
How to use it
- Paste the HTML where the backgrounds & webgl should appear.
- Add the CSS to your stylesheet. All classes are prefixed, so nothing clashes with your own styles.
- Add the JavaScript and call init() with the element that contains the HTML.
<!-- 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'));
More backgrounds & webgl
Seen something you like?
Add it to your selection and send it over. We build your site around it, in your brand.
Get in touch →