Techniek

Per effect staat hier hoe het werkt, met de echte waarden uit de code en wat er gebeurt als je Beweging uitzet.

Naar Toegankelijkheid Naar Prestatie

Jij kunt het uitzetten.

De schakelaar bouwt deze pagina live af: Lenis stopt, reveals en scrubs verdwijnen, tekst en bediening blijven. Je keuze blijft op dit apparaat bewaard.

De twaalf effecten

Elk effect heeft een doel, een aanwijzing hoe vaak je het inzet en een stille variant. De waarden hieronder staan zoals ze in de code staan.

Effect 01

Soepel scrollen

Categorie
Scroll
Doel
rust en continuïteit
Hoe vaak
zeldzaam

Hoe het werkt

  • Lenis dempt de echte documentscroll; toetsenbord, scrollbalk en zoeken op de pagina blijven gewoon werken.
  • Eén klok drijft alles aan: de GSAP-ticker geeft Lenis zijn tijd en ScrollTrigger krijgt elke scrollstap direct door.
  • Bij Beweging uit bestaat Lenis niet en scrolt de browser zelf.
Waarden van Soepel scrollen
InstellingWaarde
lerp0,1
autoRafuit
klokgsap.ticker, lagSmoothing 0
ankerklikscrollTo in 1,2 s
scrubtrue
snapuit

Bij Beweging uit

Gewone browserscroll, geen demping.

Effect 02

Regelreveal

Categorie
Tekst
Doel
leesrichting en ritme
Hoe vaak
zeldzaam

Hoe het werkt

  • SplitText knipt de kop in regels, elke regel krijgt een masker.
  • De regels schuiven van 125 procent naar 0, een voor een, en spelen één keer.
  • Na een formaatwissel splitst de tekst opnieuw zonder de animatie te herstarten.
Waarden van Regelreveal
InstellingWaarde
duur1,0 s
easeexpo.out
stagger0,08 s
starttop 85%
herhalingeenmalig
maskeroverflow clip, marge 0,14 em
alinea’s (data-split-lines)0,9 s, stagger 0,07 s, daarna weer gewone tekst

Bij Beweging uit

De tekst staat er meteen, zonder fade.

Effect 03

Woordscrub

Categorie
Tekst
Doel
het leestempo aan de scroll koppelen
Hoe vaak
zeldzaam

Hoe het werkt

  • De zin wordt in woorden gesplitst; de scrollpositie bepaalt hoeveel woorden al helder zijn, met vijf woorden tegelijk in overgang.
  • De beginkleur is --gedempt (6,08:1 op papier) en elke tussentint naar inkt of accent haalt minstens 3,4:1, zodat niets onleesbaar is. De eerste regel steekt onder de vouw van de hero uit en licht op vanaf de eerste scrollstap.
  • Lenis dempt al, dus de scrub volgt de scroll exact.
Waarden van Woordscrub
InstellingWaarde
kleurgedempt naar inkt, de sleutelwoorden naar accent
scrubtrue
easenone
staggerper woord, vijf woorden tegelijk in overgang
begineerste scrollstap
klaarhele zin in beeld, onder de header
splitsingwoorden, zonder masker

Bij Beweging uit

De hele zin staat op volle sterkte.

Effect 04

Lopende strook

Categorie
Scroll
Doel
status: je ziet hoe snel je scrolt
Hoe vaak
zeldzaam

Hoe het werkt

  • De strook loopt als CSS-animatie (lineair, translateX naar min 50 procent), dus buiten de hoofdthread.
  • De scrollsnelheid, gedempt met gsap.quickTo, zet het tempo van de animatie en een lichte kanteling (skewX) die weer terugveert.
  • Een pauzeknop stopt de strook; buiten beeld loopt hij niet.
Waarden van Lopende strook
InstellingWaarde
snelheid64 en 48 px/s, lineair
tempo bij scrollen1 tot 5 keer
skewXhooguit 8 graden, veert terug
demping0,6 s power3.out
pauzeknopja, minstens 44 bij 44 px
buiten beeldgepauzeerd

Bij Beweging uit

Geen animatie: de namen staan als omlopende regels, afwisselend massief en omlijnd.

Effect 05

Horizontaal pannen

Categorie
Scroll
Doel
ruimtelijke samenhang en volgorde
Hoe vaak
zeldzaam

Hoe het werkt

  • Een hoog wrapper-element bevat een sticky stage van schermhoogte, geen ScrollTrigger-pin.
  • Een gescrubde tijdlijn verplaatst de rij; de afstand wordt bij elke refresh opnieuw gemeten en de scrolllengte is gelijk aan die afstand.
  • Onder 900 px, bij te weinig ruimte in het scherm en bij Beweging uit is het een gewone horizontale scrollrij met scroll-snap. Een kaart met toetsenbordfocus brengt de pagina naar de juiste plek.
Waarden van Horizontaal pannen
InstellingWaarde
technieksticky stage, geen pin
scrubtrue
easenone
scrolllengtegelijk aan de afstand, hooguit 3 schermhoogtes
vanaf900 px breed, als de stage in het scherm past
kanteling bij hoverhooguit 5 graden, alleen fijne aanwijzer

Bij Beweging uit

Een horizontale scrollrij met scroll-snap die je met de hand, het toetsenbord of aanraking bedient.

Effect 06

Kaartenstapel

Categorie
Scroll
Doel
volgorde: wat is er geweest en wat komt erna
Hoe vaak
zeldzaam

Hoe het werkt

  • Elke kaart is position: sticky met een eigen offset: 28 px per kaart, onder de header.
  • Een gescrubde tijdlijn per kaart schaalt de vorige naar 0,94 en dimt hem met een inktlaag van 0,4 dekking (0,2 over de accentkaart), terwijl de volgende naar zijn plek schuift.
  • Er zit geen overflow hidden boven de stapel, dat zou sticky breken. Past een kaart niet onder zijn plek in het scherm (laag venster, grote tekst), dan blijft het een gewone lijst.
Waarden van Kaartenstapel
InstellingWaarde
schaal vorige kaart0,94
dimmeninktlaag, dekking 0,4 (over de accentkaart 0,2, zodat de tekst minstens 3 op 1 houdt)
scrubtrue
easenone
triggerde volgende kaart, van onderrand beeld tot zijn plek
offset per kaart28 px
scrolllengtecirca 170 procent van de schermhoogte

Bij Beweging uit

Een gewone verticale lijst kaarten.

Effect 07

Licht naar donker

Categorie
Overgang
Doel
sectiewissel zonder schok
Hoe vaak
zeldzaam

Hoe het werkt

  • Een inktlaag schuift van onder naar boven over de papieren stage; de inhoud erin schuift even ver omlaag. De titel staat dus stil en klapt op de rand om van inkt naar papier, zonder tekst op een tussentint.
  • Vier stappen wisselen in één gescrubde tijdlijn met overlap. Een rollend omlijnd cijfer en een voortgangslijn dragen het ritme; de header kleurt mee zolang de inkt eronder zit.
  • Het laatste woord schaalt rond een punt in de dikste letterstreep, tot de letter het scherm vult en het papier de volgende sectie overneemt.
Waarden van Licht naar donker
InstellingWaarde
technieksticky stage, twee transforms, geen pin
scrubtrue
easenone, zoom exponentieel
zwaai15 procent van de scrolllengte
wissel8 procent, overlap 1,5 procent
blur6 px op hooguit twee elementen (mobiel 3 px)
zoomvan 76 tot 99 procent, het woord schaalt tot het scherm vol is
scrolllengte280 procent van de schermhoogte (mobiel 230)
contrastminstens 4,5:1 in elke rusttoestand (tekst op papier of inkt)

Bij Beweging uit

De sectie is meteen donker, de vier stappen staan onder elkaar.

Effect 08

Opengaand vlak

Categorie
Beeld
Doel
ruimtelijke continuïteit en focus
Hoe vaak
zeldzaam

Hoe het werkt

  • Een hoog spoor bevat een sticky stage van schermhoogte; een clip-path inset groeit van een afgerond venster naar volledig scherm.
  • De hoekradius loopt mee van 2rem naar nul en de inhoud schaalt van 1,12 naar 1.
  • De inhoud binnen het vlak is al gewoon tekst en bediening, geen plaatje: de echte schakelaar Beweging uitzetten zit erin.
  • Het vlak is na 72 procent van de plakafstand open; daarna blijft het even staan voordat de pagina verdergaat.
Waarden van Opengaand vlak
InstellingWaarde
techniekclip-path inset met radius, sticky stage
startvenster30 procent van de breedte (op een telefoon 66 procent)
radius2rem naar 0
inhoudsschaal1,12 naar 1
scrubtrue
easenone
plakafstand100 procent van het scherm, het vlak groeit in de eerste 72 procent
onder 40rem hooghet vlak staat meteen open

Bij Beweging uit

Het vlak staat meteen op volle grootte.

Effect 09

Halftone-shader

Categorie
Beeld
Doel
sfeer en reageren op de bezoeker
Hoe vaak
zeldzaam

Hoe het werkt

  • Eén WebGL2-fragmentshader tekent een puntenraster van 45 graden waarvan de puntgrootte uit stromende ruis komt.
  • Aanwijzer en scrollsnelheid zijn uniforms die gedempt meebewegen. Tot de eerste aanwijzer zweeft een accentvlek vanzelf door de open ruimte. Rond tekst worden de punten klein en licht, zodat de tekst leesbaar blijft.
  • Buiten beeld, onder het gordijn van de volgende sectie, onder het menu, in een verborgen tab of na een druk op de pauzeknop stopt de lus. Bij contextverlies of zonder WebGL2 valt de hero terug op CSS-punten.
Waarden van Halftone-shader
InstellingWaarde
WebGL2, geen bibliotheek
celgrootte12 tot 16 px, schaalt met de breedte
DPRhooguit 1,5 (mobiel 1, renderschaal 0,75)
dempingk 3,5 tot 4 per seconde, echte dt
frameshooguit 60 per seconde
bufferhooguit 2,4 miljoen pixels, schaal zakt tot 0,55 bij trage frames
pauzebuiten beeld (marge 250 px), onder het gordijn of het menu, verborgen tab, pauzeknop
terugvalCSS-punten

Bij Beweging uit

Een stilstaand frame.

Effect 10

Cursor en magneet

Categorie
Cursor
Doel
feedback en uitnodigen
Hoe vaak
tientallen keren

Hoe het werkt

  • De systeemcursor blijft altijd zichtbaar; de ring is een extra laag.
  • gsap.quickTo volgt de aanwijzer zonder bij elke beweging een nieuwe tween te maken.
  • Alleen bij een fijne aanwijzer en met beweging aan; op aanraking bestaat het element niet.
Waarden van Cursor en magneet
InstellingWaarde
stip0,2 s power3.out
ring0,45 s power3.out
magneet0,35 van de afstand, hooguit 12 px, 0,5 s power3.out
druk op knopschaal 0,97 in 0,14 s
alleen bijhover en fijne aanwijzer

Bij Beweging uit

Geen ring en geen magneet; de gewone cursor.

Effect 11

Hover-preview

Categorie
Cursor
Doel
een voorbeeld zonder te klikken
Hoe vaak
tientallen keren

Hoe het werkt

  • Het voorbeeldkaartje volgt de aanwijzer met demping en kantelt met de snelheid.
  • Dezelfde preview verschijnt bij toetsenbordfocus naast de rij, hover is nooit de enige route; Escape verbergt hem.
  • De rij zelf is een gewone link, en de namen van de andere rijen dimmen licht. Op aanraking is het een gewone lijst met omschrijvingen, zonder kaartje.
Waarden van Hover-preview
InstellingWaarde
volgengedempt met quickTo, 0,3 s power3.out
kantelingmet de snelheid, hooguit 8 graden
verschijnenschaal 0,92 naar 1 in 0,3 s
naamgedempt naar inkt in 0,25 s, alleen kleur
andere rijendekking 0,7
wisselen van tegelcrossfade van 0,2 s
focuszelfde preview naast de rij

Bij Beweging uit

Een gewone lijst met links; elke rij toont zijn korte omschrijving en er is geen kaartje.

Effect 12

Paginaovergang

Categorie
Overgang
Doel
ruimtelijke samenhang tussen pagina’s
Hoe vaak
af en toe

Hoe het werkt

  • Een eigen router haalt de volgende pagina op en vervangt alleen main; header, voettekst, gordijn en Lenis blijven staan.
  • Echte links, terug en vooruit, titel en focus worden bijgewerkt en een melding gaat naar schermlezers.
  • Bij een fout of een speciale klik valt hij terug op een gewone navigatie.
Waarden van Paginaovergang
InstellingWaarde
gordijn5 kolommen, scaleY
dichtkolommen 0,28 s uiInOut, stagger 0,028 s vanuit het midden, samen 0,46 s
bestemmingsnaamyPercent 125 naar 0, 0,26 s power4.out, vanaf 0,2 s
openkolommen 0,38 s uiInOut, stagger 0,035 s vanaf de randen, vanaf 0,04 s, samen 0,56 s
klik tot klaar1,07 tot 1,19 s gemeten op deze machine (headless Chrome), budget hooguit 1,2 s
easeuiInOut en power4.out
terug en vooruitwerken

Bij Beweging uit

Een fade van 150 ms, dicht en open.

Toegankelijkheid

De pagina werkt en leest ook stil. Dit doet de demo voor toegankelijkheid en dit zijn de bestanden waar je het ziet. Wat niet is getest, staat er ook bij.

Beweging

Wat er gebeurt als de beweging uit staat of mislukt.

Systeeminstelling en schakelaar
prefers-reduced-motion: reduce en de schakelaar Beweging uit doen hetzelfde. De pagina bouwt live af, zonder herladen: Lenis stopt, de ScrollTriggers verdwijnen, de cursorring verdwijnt en alle tekst staat op volle sterkte. Zet je de beweging weer aan, dan bouwt de pagina zich weer op.
In de codejs/core.js css/tokens.css
Eindstaat zonder JavaScript
Zonder JavaScript, na een fout of met geblokkeerde scripts is alle tekst zichtbaar. Beginstaten van reveals bestaan alleen als html de klasse js heeft en motion-failed en motion-off ontbreken, en alleen binnen prefers-reduced-motion: no-preference. Komt motion-ready niet binnen 4 seconden, dan zet de pagina zichzelf op motion-failed.
In de codecss/base.css het script in de head van elke pagina
Pauzeknoppen
Wat vanzelf loopt heeft een pauzeknop met een vaste naam die zegt wat hij stopt (Achtergrond pauzeren voor de shader, Strook pauzeren voor de lopende strook) en meldt zijn stand in data-state.
In de codejs/sections/marquee.js js/shader.js
Paginawissel
Een paginawissel duurt ongeveer een seconde. Een tweede klik tijdens de wissel wordt genegeerd, Terug en Vooruit werken altijd. Bij reduce en Beweging uit is het een fade van 150 ms.
In de codejs/router.js

Bediening

Toetsenbord, aanwijzer en aanraking.

Toetsenbord en focus
De skiplink Naar de inhoud is het eerste focuspunt. Elk focuspunt krijgt een ring van 3 px die geen masker afknipt, en het hover-effect van knoppen, links en chips heeft dezelfde stand bij focus. De Tab-doorloop in check-a11y controleert dat elk focuspunt zichtbaar is, niet bedekt wordt en een ring van 2 tot 3 px heeft.
In de codecss/base.css css/components.css
Geen scroll-hijacking
Lenis scrolt de echte documentscroll. PageDown, End, Home en Spatie werken en blijven gelijk met scrollY. Er is geen eigen scroll- of wheel-listener (de scrollstand voor Terug bewaart de header met een ScrollTrigger, en bij Beweging uit het event scrollend), geen snap op de pagina en geen preventDefault op wheel of toetsen.
In de codejs/core.js js/router.js
Cursor
De systeemcursor blijft altijd zichtbaar: nergens staat cursor: none. De cursorring is een extra laag, bestaat alleen bij een fijne aanwijzer met beweging aan en is verborgen voor schermlezers.
In de codejs/chrome.js
Tikdoelen en zoom
Bediening is minstens 44 bij 44 px, gemeten op 375 px breed. De pagina blijft leesbaar op 320 px breed en bij 200 procent tekst en scrolt dan niet zijdelings. Alleen een tabel, de rij effectkaarten op Home en op een smal scherm de index scrollen binnen hun eigen kader.
In de codecss/components.css check-a11y

Lezen en luisteren

Schermlezers en kleur.

Schermlezers
Een kop die in regels of tekens wordt gesplitst krijgt zijn tekst als aria-label en de losse stukken zijn aria-hidden, zodat een schermlezer de kop als één geheel leest. Een alinea die in regels wordt gesplitst (data-split-lines, zoals de inleiding bovenaan deze pagina) gebruikt aria: none: de tekst blijft gewone tekst, een link erin blijft bereikbaar en houdt zijn focus, en na het afspelen is het weer gewone tekst. Na een paginawissel meldt een aria-live-gebied Pagina geladen met de titel en gaat de focus naar de nieuwe pagina. Niet getest met NVDA, JAWS of VoiceOver.
In de codejs/core.js js/router.js
Kleur en contrast
Elk tekstpaar uit de tokens haalt minstens AA, gemeten met contrast.mjs. De actieve pagina in het menu krijgt een punt en de actieve plek in de index een lijn met aria-current, zodat kleur nooit de enige aanwijzing is.
In de codecss/tokens.css

Prestatie

Wat het kost: alleen gemeten cijfers. Ze komen uit headless Chrome met een softwarerenderer op een pc, niet van een telefoon of een echte GPU.

Bestanden
BestandRuw, KBGzip, KB
Vendor
GSAP71,227,8
ScrollTrigger43,517,6
SplitText7,63,6
Flip24,99,5
CustomEase7,03,6
Lenis18,35,3
Lenis (css)0,50,2
Vendor samen173,067,5
Eigen code
Eigen JS, 17 bestanden228,577,7
Eigen JS geminificeerd95,7n.v.t.
Eigen CSS, 17 bestanden165,050,4
Lettertypen
Alle 8 woff2-bestanden op schijf281,6n.v.t.

Bytes gemeten met node op de bestanden in de projectmap. Gzip met node:zlib op standaardniveau. Geminificeerd gemeten met esbuild. 1 KB is 1024 bytes.

Per pagina
MetingHomeEffectenTechniek
Laden
Verzoeken, document inbegrepen464646
Gewicht, KB ongecomprimeerd833,2808,2847,5
Gewicht, KB gzip (berekend)398,8395,6403,3
Waarvan lettertypen, KB ongecomprimeerd191,5191,5191,5
ScrollTriggers
Na laden, 1440 px181822
Na laden, 375 px161822
Na scrollen tot het einde, 1440 px1322
Lange taken en CLS, 1440 px
CLS, laden en scrollen samen0,0030,0010,001
Lange taken boven 50 ms tijdens het laden, aantal200
Langste tijdens het laden, ms6400
Lange taken boven 50 ms tijdens het scrollen, aantal000
Langste tijdens het scrollen, ms000

Chrome 155.0.8059.39, headless, softwarerenderer (SwiftShader), geen echte GPU of telefoon, gemeten op 2026-10-08. Verzoeken en gewicht uit de resource-timing van de browser bij een eerste laadbeurt op 1440 bij 900 px, preloader overgeslagen. ScrollTriggers geteld met ScrollTrigger.getAll() na 2,6 s, op 1440 px en op 375 px met aanraking. CLS en lange taken (boven 50 ms) met een PerformanceObserver, vanaf het begin van het laden tot het einde van een scroll met het muiswiel door de hele pagina. Een lange taak telt als laden als hij begon vóór de scroll en als scrollen als hij erna begon.

Niet gemeten: een echte GPU, een telefoon, iOS Safari, Firefox en schermlezers.

Eén hoofdmoment per sectie.

Binnen een sectie draait het om één beweging en de rest van die sectie staat stil. Op Effecten staan alle twaalf effecten bij elkaar, op Home zie je ze in beweging.

Bekijk de effecten Naar Home

Gebouwd bovenop GSAP (met ScrollTrigger, SplitText, Flip en CustomEase) en Lenis. Lettertypen: Bricolage Grotesque, Hanken Grotesk en DM Mono, onder de SIL Open Font License. De licenties staan in de mappen vendor/licenties en fonts.

Laden…