Exhibit 10 of 17
Reading progress
Scroll the notice. The bar and the percentage are an ordinary CSS animation, run by the box’s scroll position instead of by time.
The exhibit
Notice to apprentices
This notice is long on purpose. Scroll it and watch the bar at the top fill up. No script is measuring anything; the browser is running an animation whose clock is this box’s scroll position instead of the time.
The lamp is to be left on. If it goes out, it needs oil, a wick and a glass, in that order. The oil is in the sardine tin, which is a sea, which is mostly oil. Take a bucket.
The wick is in the walnut, which is a theatre. Ask at the kiosk. Do not ask the Understudy, who is busy refusing to start.
The glass is in the stockroom behind the showroom. Everything there is on display and nothing is in stock, so the glass is technically on display. Bring it back anyway.
Do not open the door to the stairs while the lamp is lit. There is a draught up there, and it has opinions about lamps.
If the stapler says “Clack”, it means it. Step away from the stapler.
The photocopier at the top of the workshop is to be switched off. Nobody has said why it was on.
Hours: yes.
That is the whole notice. The bar should be full.
This browser doesn’t have scroll-driven animations yet, so there is no bar. The notice still scrolls.
How it works
animation-timeline: scroll(nearest block) ties an animation to the nearest scroll container: scrolled to the top is 0%, to the bottom is 100%. The keyframes move two registered properties, a percentage for the bar’s width and an integer for the number, which counter() prints.
The arrow pill in the corner of every page on this site (“Slop at the top”) is the same trick: it fades in once you have scrolled a screen and a half.
The key CSS
Cut from this exhibit’s stylesheet as it is served.
@property --w { syntax: "<percentage>"; inherits: true; initial-value: 0%; }
@property --pct { syntax: "<integer>"; inherits: true; initial-value: 0; }
@keyframes read { to { --w: 100%; --pct: 100; } }
.x-progress .progress {
animation: read linear both;
animation-timeline: scroll(nearest block); /* the clock is the nearest scroll box: the reader */
}
.x-progress .pct { counter-reset: pct var(--pct); }
.x-progress .pct::after { content: counter(pct) "%"; }
Accessibility
The notice is a focusable, labelled region, so the keyboard can scroll it. The bar is decorative and hidden from screen readers; it adds nothing a screen reader user lacks, since they know where they are in the text. In a browser without scroll-driven animations there is no bar and a line says so.
Browser support
scroll-driven animationsWorks in this browser.Not in this browser: No bar; a line says it isn’t available. Nothing else depends on it. Checked 2026-10-14: Chromium 149 yes, Firefox 141 no.@propertyCSS can’t test for this one. Without it: Typed values can’t animate: the hands jump instead of turning, and the numbers don’t count. Checked 2026-10-14: Chromium 149 yes, Firefox 141 yes.
