Candidate effects for a motion wave. Scroll, and judge.
There are two different mechanisms on this page and the difference decides what a wave costs. Scroll-driven ties an animation’s progress to scroll position through animation-timeline: pure CSS, free to author, and absent in browsers that have not shipped it — where the property is dropped and the element lands on its finished state immediately. Scroll-triggered uses an observer to add a class and then runs an ordinary timed animation: it works everywhere, runs once and stays put, and needs a runtime behavior in the renderer because templates ship no JavaScript.
Where an effect can be built both ways, both are shown. The panel below reports which mechanisms this browser actually has, and each specimen carries a live diagnostic line.
01 — Reveal — the two mechanisms side by side
animation-timeline: view() vs IntersectionObserver + transition
The same fade-and-lift built both ways. The first row is scroll-driven: pure CSS, free to author, and it reverses as you scroll back. The second is scroll-triggered: an observer adds a class and an ordinary timed transition runs, once, and stays. If only the second row moves for you, this browser has no scroll-driven animation — and the triggered route is the one a wave would have to use.
Scroll-driven — pure CSS, no code cost
Hero Feature
Full-bleed picture, copy over it.
Statement Figure
One number, one line, full width.
CTA Band
Gradient band, one dominant action.
Scroll-triggered, then timed — needs a runtime behavior
Hero Feature
Full-bleed picture, copy over it.
Statement Figure
One number, one line, full width.
CTA Band
Gradient band, one dominant action.
Applies to all 91 designs. The triggered version is the one that runs everywhere.
02 — Staggered reveal
animation-range vs transition-delay on a trigger
A stagger is not a second effect — it is how the children of one reveal are timed. Scroll-driven, the offset has to be a shifted animation-range, because a time delay means nothing on a progress-based timeline. Triggered, it is an ordinary transition-delay, which is both simpler and the thing most people expect a stagger to be.
Scroll-driven, offset by animation-range
Solo
£19 a month
Crew
£39 a month
Firm
£89 a month
Bespoke
Talk to us
Triggered, offset by transition-delay
Solo
£19 a month
Crew
£39 a month
Firm
£89 a month
Bespoke
Talk to us
49 designs have a collection to stagger.
03 — Drifting ground
keyframes, infinite alternate — back to 26s
Back to the original pace, since the speed was never the problem: the first version drifted a gradient, and a gradient has no features to judge movement by. On a photograph the same 26 seconds reads clearly.
Get a fixed price before anyone climbs a ladder
The survey fee comes off the cost of any work you go ahead with.
Fits the 4 designs with a picture ground. Plain keyframes, so it runs everywhere.
04 — Drifting gradient
animated background-position — your idea
The version that works without a picture. Nothing moves: the gradient is drawn three times the size of the box and its position travels, so the color under any given point keeps changing. That is the difference from the first attempt, which slid a gradient bodily and looked static because every part of it was identical to every other part.
Twelve years on the same roofs
No photograph, no picture to source, and it costs one keyframe.
Fits every gradient design in the showpiece wave — cta-band, hero-gradient, statement-figure. Free, and it runs everywhere.
05 — Parallax, driven by the page scroll
runtime writes --p, the sheet spends it
The runtime reads the band’s position against the viewport and writes a 0-to-1 progress as --p; the stylesheet spends it, moving the picture one way and the copy the other. The travel is the picture’s own overhang — the height it renders at for the width it is given, less the height of the frame — so --p 0 shows the very top of the image and 1 shows the very bottom, and the whole thing is seen across the crossing rather than a band through its middle. That number cannot live in a stylesheet: a 4:3 photograph has hundreds of pixels to give and a 3:1 banner has almost none, so the runtime measures it. Scroll the page.
Twelve years on the same roofs
The picture travels down, the words travel up, and the gap between them is the depth.
Heroes and full-bleed bands. Costs a behavior in pattern_render.js — the same one that serves any effect wanting scroll progress. The fitness check under the band is the second half of the job: a short, wide picture has nothing to travel through, and the designer has to be told at authoring time rather than the buyer finding out on a live page.
06 — Counters
two techniques, and only one is free
The first row is scrubbed by scroll position: pure CSS, but it runs backwards when you scroll up and never settles. The second is triggered then timed — it runs once and stays, which is what a figure on a page should do.
Scrubbed by scroll — pure CSS, free
Triggered, then timed — needs a runtime behavior
3 designs carry a stat module, plus statement-figure.
07 — Ribbon
keyframes, infinite linear — seam fixed
Three things were wrong. The halves differed by one gap, so -50% landed short; the track was translated in 2d, so the browser snapped it to whole pixels and ticked at the seam; and six names meant the duplicate copy was on screen beside the original, which reads as a jump whatever the seam is doing. Now: equal halves each carrying their own trailing gap, a compositor-friendly translate3d, and sixteen names so the second copy is always off screen. It also pauses on hover so a name can be read.
One design: logo-strip. Still not user-proof: it works because sixteen names happen to overflow this container. A buyer with four would see the seam again, which is why the duplication has to be the module’s job rather than the author’s.
08 — Scroll progress
scroll(root), with a listener where that is unsupported
The rule pinned at the top of the window. Where scroll() exists it is pure CSS; where it does not, the bar was showing full green because the dropped property left the animation on its end state. It now falls back to a scroll listener, and the panel above says which route is running.
A new design. Free in Chrome, a runtime behavior everywhere else.
09 — Sticky stack
position: sticky, no animation at all
Each card pins under the one before it and the next slides over the top. Nothing here is animated, which is why it behaves identically in every browser. The stepping you saw is your wheel scrolling by lines rather than pixels — drag the scrollbar or use a trackpad and it is smooth. That is the scroller, not the design, and it would do the same to any sticky layout.
You tell us what happened
A phone call, or a photograph sent to the office. Nine times out of ten that is enough to know what we are coming to look at.
We survey and put a price on it
Two hours on site including the loft, a drone pass over anything we cannot safely walk, and a fixed written quote inside five working days.
We do the work on the day we said
Same crew who quoted it, in most cases. If the weather moves us we call you the day before.
A new design, and the cheapest here.
10 — Ribbon that fills itself
runtime repeats the list until one cycle clears the container
Specimen 07 loops smoothly only because sixteen names happen to overflow the strip — it is not fixed, it is lucky. Here the runtime takes whatever list it is handed, repeats it until one cycle is at least as wide as the container, lays two cycles end to end and translates by exactly one, so the seam is always off screen at any count. The speed is set from the cycle width rather than fixed, so a short list does not crawl and a long one does not race. Nothing here depends on how many logos the buyer has. Tested at three, six and fifteen items — all three filled correctly and ran at the same 55px per second.
This is the shape the ribbon module would take. The duplication is the runtime’s job, not the author’s — which is the whole difference between a marquee that survives a buyer and one that does not.
What a motion wave would actually cost
| Effect | Mechanism | Designs | Runs everywhere | Code cost |
|---|---|---|---|---|
| Reveal | Triggered | 91 | Yes | Runtime behavior |
| Staggered reveal | Triggered | 49 | Yes | Same behavior |
| Drifting ground | Keyframes | 4 | Yes | None |
| Drifting gradient | Keyframes | 3+ | Yes | None |
| Parallax | Perspective | 4 | Yes | None, but needs its own scroller |
| Counter | Triggered | 4 | Yes | Same behavior |
| Ribbon | Keyframes | 1 | Yes | None |
| Scroll progress | scroll() or listener | +1 | With fallback | Runtime behavior |
| Sticky stack | position: sticky | +1 | Yes | None |
One runtime behavior — an observer that adds a class when an element comes into view — covers reveal, stagger and the counters, which is the bulk of the wave. Everything else is authoring. Nothing here is decided.