Boom door de seizoenen New
Scroll from March to February and a tree goes through the year: blossom, full green leaves, autumn colours and falling leaves, then bare branches with snow. A card names the month and what the gardener does then. For the services page or hero of a gardener, nursery or garden centre. Own SVG and own scroll box with keyboard support.
Maart
Lente Hagen snoeien, borders planten en het gazon bijzaaien.
How to use it
- Paste the HTML where the scroll effects 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.
<!-- Boom door de seizoenen · © 2026 NedDev BV · NedDev Lab-licentie (gratis gebruik, niet doorverkopen) · neddev.com/nl/lab/gebruik/#licentie -->
<div class="jouw-container">
<div class="bs">
<div class="bs__vak" tabindex="0" role="region" aria-label="Een boom door het jaar, van maart tot februari. Scroll in dit vak.">
<div class="bs__baan">
<div class="bs__beeld">
<svg class="bs__svg" viewBox="0 0 240 150" preserveAspectRatio="xMidYMax slice" aria-hidden="true">
<rect class="bs__grond" x="0" y="128" width="240" height="22" fill="#9cc06a"/>
<path d="M121 130C120 112 118 100 119 86M119 100C110 92 100 86 92 72M119 96C128 88 138 82 146 68M119 86C118 74 112 62 106 50M119 86C124 74 130 60 134 48M97 78C88 74 82 70 78 62M141 74C150 70 156 66 160 58" fill="none" stroke="#5b4636" stroke-width="4" stroke-linecap="round"/>
<path d="M115 130L127 130L123 100L118 100Z" fill="#5b4636"/>
<path class="bs__sneeuwtak" d="M92 71C100 84 110 91 119 98M146 67C138 81 128 88 119 95M106 49C112 60 117 72 119 84M134 47C130 59 124 72 119 84M78 61C82 69 88 73 97 77M160 57C156 65 150 69 141 73" fill="none" stroke="#fff" stroke-width="1.6" stroke-linecap="round" opacity="0"/>
<g class="bs__blad"></g>
<g class="bs__vlok"></g>
</svg>
<div class="bs__kaart" aria-live="polite"><p class="bs__maand">Maart</p><p class="bs__taak"><strong>Lente</strong> Hagen snoeien, borders planten en het gazon bijzaaien.</p></div>
</div>
</div>
</div>
<p class="bs__hint" aria-hidden="true">Scroll door het jaar</p>
</div>
</div>/* Boom door de seizoenen · © 2026 NedDev BV · NedDev Lab-licentie (gratis gebruik, niet doorverkopen) · neddev.com/nl/lab/gebruik/#licentie */
.bs{position:absolute;inset:0;container-type:size;font-family:'Switzer',system-ui,sans-serif;color:#0a0a0a;background:#dfeaf0}
.bs__vak{position:absolute;inset:0;overflow-y:auto;scrollbar-width:none}
.bs__vak::-webkit-scrollbar{display:none}
.bs__vak:focus-visible{outline:2px solid #F5480F;outline-offset:-2px}
.bs__baan{height:600cqh}
.bs__beeld{position:sticky;top:0;height:100cqh;overflow:hidden;background:#dfeaf0}
.bs__svg{position:absolute;inset:0;width:100%;height:100%;display:block}
.bs__kaart{position:absolute;left:10px;top:10px;max-width:min(46%,210px);background:#fff;border-radius:4px;padding:7px 10px}
.bs__maand{margin:0;font-size:clamp(17px,5.4cqw,22px);font-weight:700;line-height:1.1}
.bs__taak{margin:3px 0 0;font-size:12px;line-height:1.35;color:#333}
.bs__taak strong{display:block;color:#b83a0b;font-weight:600}
.bs__hint{position:absolute;right:10px;bottom:8px;margin:0;font-size:12px;background:#fff;padding:3px 8px;border-radius:4px;pointer-events:none;transition:opacity .3s}
.bs.is-weg .bs__hint{opacity:0}// Boom door de seizoenen · © 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){
const w = root.querySelector('.bs'); if (!w) return;
const vak = w.querySelector('.bs__vak'), beeld = w.querySelector('.bs__beeld'), grond = w.querySelector('.bs__grond'), tak = w.querySelector('.bs__sneeuwtak');
const gB = w.querySelector('.bs__blad'), gV = w.querySelector('.bs__vlok'), maand = w.querySelector('.bs__maand'), taak = w.querySelector('.bs__taak');
const rm = matchMedia('(prefers-reduced-motion: reduce)').matches;
const NS = 'http://www.w3.org/2000/svg';
let seed = 7; const rnd = () => (seed = (seed * 16807) % 2147483647) / 2147483647;
const BL = ['#ffffff', '#f4c6cc'], GR = ['#6f9e3f', '#5d8a35', '#86b24f'], HF = ['#F5480F', '#e8a33d', '#c4512a', '#d9822b'];
const blad = [];
for (let i = 0; i < 52; i++) {
const a = rnd() * Math.PI * 2, r = Math.sqrt(rnd());
const x = 119 + Math.cos(a) * r * 50, y = 66 + Math.sin(a) * r * 30;
const c = document.createElementNS(NS, 'circle'); c.setAttribute('r', (5 + rnd() * 3).toFixed(1)); gB.appendChild(c);
blad.push({ c, x, y, bl: BL[i % 2], gr: GR[i % 3], hf: HF[i % 4], tg: .12 + rnd() * .12, th: .48 + rnd() * .1, val: .6 + rnd() * .14, gx: x + (rnd() - .5) * 60, gy: 131 + rnd() * 12 });
}
const vlok = [];
for (let i = 0; i < 16; i++) { const c = document.createElementNS(NS, 'circle'); c.setAttribute('r', 1.4); c.setAttribute('fill', '#fff'); gV.appendChild(c); vlok.push({ c, x: rnd() * 240, y: rnd() * 150 }); }
const hex = h => [1, 3, 5].map(i => parseInt(h.slice(i, i + 2), 16));
const mix = (a, b, t) => { const x = hex(a), y = hex(b); return 'rgb(' + x.map((v, i) => Math.round(v + (y[i] - v) * t)).join(',') + ')'; };
const cl = v => v < 0 ? 0 : v > 1 ? 1 : v;
const M = ['Maart', 'April', 'Mei', 'Juni', 'Juli', 'Augustus', 'September', 'Oktober', 'November', 'December', 'Januari', 'Februari'];
const SZ = [['Lente', 'Hagen snoeien, borders planten en het gazon bijzaaien.'], ['Zomer', 'Maaien, water geven en de vijver bijhouden.'], ['Herfst', 'Blad ruimen en bollen planten voor het voorjaar.'], ['Winter', 'Bomen snoeien en kappen nu de sap stilstaat.']];
const LU = ['#dfeaf0', '#cfe3f2', '#efe2d0', '#e3e7ea', '#e3e7ea'], GD = ['#9cc06a', '#7fae4e', '#a99b5a', '#fbfbfb', '#fbfbfb'];
let raf = 0, nu = -1;
function teken() {
raf = 0; const m = vak.scrollHeight - vak.clientHeight, p = m > 0 ? vak.scrollTop / m : 0;
const s = Math.min(3.999, p * 4), k = Math.floor(s), f = cl((s - k - .6) / .4);
beeld.style.background = mix(LU[k], LU[k + 1], f); grond.setAttribute('fill', mix(GD[k], GD[k + 1], f));
const winter = cl((p - .76) / .06);
tak.setAttribute('opacity', winter.toFixed(2));
blad.forEach(b => {
let kl, sc = 1, x = b.x, y = b.y, op = 1;
const g = cl((p - b.tg) / .025), h = cl((p - b.th) / .025);
sc = (.35 + .45 * cl(p / .1)) * (1 - g) + g;
kl = h > 0 ? mix(b.gr, b.hf, h) : g > 0 ? mix(b.bl, b.gr, g) : b.bl;
if (p > b.val) {
const t = cl((p - b.val) / .06);
if (rm) op = 1 - t; else { x = b.x + (b.gx - b.x) * t; y = b.y + (b.gy - b.y) * t * t; }
}
if (p > .78) op = Math.min(op, 1 - cl((p - .78) / .06));
b.c.setAttribute('cx', x.toFixed(1)); b.c.setAttribute('cy', y.toFixed(1)); b.c.setAttribute('fill', kl);
b.c.style.transform = 'scale(' + sc.toFixed(3) + ')'; b.c.style.transformOrigin = x.toFixed(1) + 'px ' + y.toFixed(1) + 'px';
b.c.setAttribute('opacity', op.toFixed(2));
});
vlok.forEach(v => { const y = rm ? v.y : (v.y + (p - .76) * 1400) % 150; v.c.setAttribute('cx', v.x.toFixed(1)); v.c.setAttribute('cy', y.toFixed(1)); v.c.setAttribute('opacity', winter.toFixed(2)); });
const mi = Math.min(11, Math.floor(p * 12));
if (mi !== nu) { nu = mi; const sz = SZ[Math.floor(mi / 3)]; maand.textContent = M[mi]; taak.innerHTML = '<strong>' + sz[0] + '</strong> ' + sz[1]; }
w.classList.toggle('is-weg', vak.scrollTop > 8);
}
vak.addEventListener('scroll', () => { if (!raf) raf = requestAnimationFrame(teken); }, { passive: true });
teken();
}
init(document.querySelector('.jouw-container'));
More scroll effects
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 →