Zon komt op en gaat onder New
Scroll and a day passes over the sea: the sun rises, climbs and sets, the sky goes from night via dawn to blue and back, and stars and a moon appear. A card shows the clock and what is on at that hour. For the hero or opening hours of a beach pavilion, campsite or hotel. Own scroll box with keyboard support; only colour and position, no heavy effects.
05:00
Nog even stil Om 08:00 gaan we open.
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.
<!-- Zon komt op en gaat onder · © 2026 NedDev BV · NedDev Lab-licentie (gratis gebruik, niet doorverkopen) · neddev.com/nl/lab/gebruik/#licentie -->
<div class="jouw-container">
<div class="zd">
<div class="zd__vak" tabindex="0" role="region" aria-label="Een dag bij Paviljoen Noordstrand in Zandvoort. Scroll in dit vak om de tijd te laten lopen.">
<div class="zd__baan">
<div class="zd__beeld">
<div class="zd__sterren" aria-hidden="true"></div>
<div class="zd__maan" aria-hidden="true"></div>
<div class="zd__zon" aria-hidden="true"></div>
<div class="zd__zee" aria-hidden="true"><i class="zd__glans"></i></div>
<svg class="zd__duin" viewBox="0 0 400 60" preserveAspectRatio="none" aria-hidden="true"><path d="M0 60V26C40 10 80 8 120 22S190 40 230 30 320 6 400 20V60Z"/></svg>
<div class="zd__kaart"><p class="zd__tijd">05:00</p><p class="zd__wat" aria-live="polite"><strong>Nog even stil</strong> <span>Om 08:00 gaan we open.</span></p></div>
</div>
</div>
</div>
<p class="zd__hint" aria-hidden="true">Scroll door de dag</p>
</div>
</div>/* Zon komt op en gaat onder · © 2026 NedDev BV · NedDev Lab-licentie (gratis gebruik, niet doorverkopen) · neddev.com/nl/lab/gebruik/#licentie */
.zd{position:absolute;inset:0;container-type:size;font-family:'Switzer',system-ui,sans-serif;color:#0a0a0a;background:#0b0f16}
.zd__vak{position:absolute;inset:0;overflow-y:auto;scrollbar-width:none}
.zd__vak::-webkit-scrollbar{display:none}
.zd__vak:focus-visible{outline:2px solid #F5480F;outline-offset:-2px}
.zd__baan{height:600cqh}
.zd__beeld{position:sticky;top:0;height:100cqh;overflow:hidden;background:linear-gradient(#0b0f16,#1b2330)}
.zd__sterren{position:absolute;inset:0 0 40% 0;background:radial-gradient(circle,#fff 1px,transparent 1.4px) 0 0/37px 29px,radial-gradient(circle,#fff .8px,transparent 1.2px) 13px 9px/53px 41px}
.zd__zon,.zd__maan{position:absolute;left:0;top:0;width:14cqh;height:14cqh;margin:-7cqh 0 0 -7cqh;border-radius:50%}
.zd__zon{background:#ffd27a;box-shadow:0 0 0 6px rgba(255,210,122,.25)}
.zd__maan{background:#f1efe8;box-shadow:inset -5px -3px 0 #c9c5ba;left:22cqw;top:20cqh;width:9cqh;height:9cqh}
.zd__zee{position:absolute;left:0;right:0;top:66%;bottom:0;background:#1b2330;overflow:hidden}
.zd__glans{position:absolute;top:0;bottom:0;width:10cqh;margin-left:-5cqh;background:repeating-linear-gradient(#ffd27a 0 2px,transparent 2px 6px);opacity:0}
.zd__duin{position:absolute;left:0;bottom:0;width:100%;height:22%;fill:#1a1a1a}
.zd__kaart{position:absolute;left:12px;bottom:12px;max-width:62%;background:#fff;border-radius:4px;padding:7px 10px}
.zd__tijd{margin:0;font-size:clamp(18px,6cqw,24px);font-weight:700;font-variant-numeric:tabular-nums;line-height:1.1}
.zd__wat{margin:2px 0 0;font-size:12px;line-height:1.35}
.zd__wat strong{font-weight:600}
.zd__hint{position:absolute;right:10px;top:8px;margin:0;font-size:12px;background:#fff;padding:3px 8px;border-radius:4px;pointer-events:none;transition:opacity .3s}
.zd.is-weg .zd__hint{opacity:0}// Zon komt op en gaat onder · © 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('.zd'); if (!w) return;
const vak = w.querySelector('.zd__vak'), beeld = w.querySelector('.zd__beeld'), zon = w.querySelector('.zd__zon'), maan = w.querySelector('.zd__maan');
const sterren = w.querySelector('.zd__sterren'), zee = w.querySelector('.zd__zee'), glans = w.querySelector('.zd__glans'), duin = w.querySelector('.zd__duin');
const tijd = w.querySelector('.zd__tijd'), wat = w.querySelector('.zd__wat');
const K = [[5, '#0b0f16', '#1b2330'], [6.5, '#2b3550', '#f0a070'], [8, '#8fc3e6', '#e6f1f7'], [13, '#5aa9e0', '#cfe6f5'], [18, '#79b4dc', '#f6d7a8'], [20.5, '#2f3d5c', '#f5803f'], [21.6, '#141b29', '#5a3a2e'], [23.5, '#0a0d13', '#141a24']];
const P = [[0, 'Nog even stil', 'Om 08:00 gaan we open.'], [8, 'Ontbijt aan zee', 'vanaf € 8,50, met vers brood uit Haarlem.'], [11.5, 'Lunch op het terras', 'uitsmijter met drie eieren € 11,50.'], [16, 'Borrel met zicht op zee', 'bittergarnituur € 9,75.'], [19, 'Diner bij zonsondergang', 'de keuken is open tot 21:30.'], [21.5, 'We zijn dicht', 'morgen weer vanaf 08:00.']];
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;
let raf = 0, nu = -1;
function teken() {
raf = 0; const m = vak.scrollHeight - vak.clientHeight, p = m > 0 ? vak.scrollTop / m : 0;
const u = 5 + p * 18.5;
let k = 0; while (k < K.length - 2 && u > K[k + 1][0]) k++;
const f = cl((u - K[k][0]) / (K[k + 1][0] - K[k][0]));
const boven = mix(K[k][1], K[k + 1][1], f), onder = mix(K[k][2], K[k + 1][2], f);
beeld.style.background = 'linear-gradient(' + boven + ',' + onder + ' 66%)';
zee.style.background = 'linear-gradient(' + mix(K[k][1], K[k + 1][1], f) + ',#0a0a0a 220%)';
const op = 6.5, neer = 21.25, s = (u - op) / (neer - op);
const hoog = s > 0 && s < 1 ? Math.sin(Math.PI * s) : 0;
const licht = cl(hoog * 3);
const x = 8 + 84 * cl(s), y = 66 - hoog * 54;
zon.style.transform = 'translate(' + x + 'cqw,' + (s <= 0 || s >= 1 ? 76 : y) + 'cqh)';
zon.style.background = mix('#f5803f', '#ffe7a8', cl(hoog * 2));
glans.style.left = x + '%'; glans.style.opacity = (cl(hoog * 4) * .5).toFixed(2);
sterren.style.opacity = (1 - cl((u - 5.6) / 1.4) + cl((u - 21.3) / 1.2)).toFixed(2);
maan.style.opacity = (u > 21.6 ? cl((u - 21.6) / 1) : u < 6 ? 1 - cl((u - 5.2) / .8) : 0).toFixed(2);
duin.style.fill = mix('#1a1a1a', '#d9c8a2', licht);
const hh = Math.floor(u), mm = Math.floor((u - hh) * 4) * 15;
tijd.textContent = String(hh).padStart(2, '0') + ':' + String(mm).padStart(2, '0');
let i = 0; P.forEach((q, j) => { if (u >= q[0]) i = j; });
if (i !== nu) { nu = i; wat.innerHTML = '<strong>' + P[i][1] + '</strong> <span>' + P[i][2] + '</span>'; }
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 →