loading

Scroll animations

There are two ways to tie motion to a scrollbar, and they answer different questions. Scroll-linked motion asks "how far along are we?" and maps that progress continuously onto a property - the reader scrubs the animation. Scroll-triggered motion asks "is this on screen yet?" and plays a normal animation once the answer changes. For the first there is a fast path - Timeline, which hands the scrubbing to the browser outright - and a general one, BmotionScrollTracker, for when you need the number itself. WhileInView covers the second.

Window scroll progress
Y: 0.0%  |  X: 0.0%

Scroll timelines: let the browser do it

A scroll handler that calls back into .NET on every scroll event is the expensive way to scrub an animation, and it is the one thing you cannot make fast from the outside. Timeline hands the whole thing to the browser instead: the keyframes are pre-sampled in C# once, and progress comes from the browser's own ScrollTimeline / ViewTimeline. After the handoff there is no scroll handler, no frame loop and no interop at all - the animation runs on the compositor. Where those APIs are missing, the bridge scrubs the same Web Animation from one passive listener, so the values are still interpolated by the browser rather than round-tripped through .NET.

BmScrollTimeline.Page() is the whole document's progress; Container(selector) is one scroller's; View() is the animated element's own journey through the viewport, and ViewOf(selector) is some other element's. Only transforms and opacity can be handed over this way - they are what the browser can interpolate for us - and while a timeline is attached it owns those properties, so Transition does not apply: scroll position is the progress.

BmScrollTimeline.Page() · scroll the page
BmScrollTimeline.View()
…the same, one row along
…and again
Razor

Tracking the scroll position

Inject BmotionScrollTracker and call ObserveAsync to receive a BmScrollInfo on every scroll: absolute offsets and normalised 0..1 progress on both axes. The tracker is transient and owned by the component that injected it, which is why the page below implements IAsyncDisposable - the browser-side listener goes away with the component rather than outliving it.

Razor

Scroll-linked motion values

A callback that calls StateHasChanged on every scroll event is a diff per frame. Motion values skip that entirely: bind them with the Values parameter and their changes are flushed straight to the DOM each frame without re-rendering the component. Transform derives new values from them - here the same progress drives a scaleX bar directly and, mapped through 0..1 โ†’ 0..360, a full rotation over the length of the page.

Razor

Motion templates

Some properties are not a number - they are a sentence made of numbers. Bm.Template composes motion values into a CSS string and StringValues binds the result to any property, so a filter built from two independently derived values updates as one. The blur below peaks at the middle of the page ([0, 0.5, 1] โ†’ [0, 4, 0]) while the hue rotates straight through.

Razor

An element's own progress

Window progress is the wrong measure for anything that is not the whole page. Pass BmScrollOptions a TargetId and a pair of Offset alignments instead, and progress is measured over that element's journey through the viewport. Each offset reads "<target edge> <container edge>", so the default ["start end", "end start"] means 0 when its top reaches the bottom of the viewport, 1 when its bottom passes the top. Scroll the striped panel past and watch the ring fill.

This card is the tracked target
Target progress: 0.0%
Razor

whileInView

WhileInView is the trigger half: an ordinary gesture state, activated by an IntersectionObserver rather than a pointer. Once="true" keeps it from replaying every time the element crosses the fold - the right default for entrance animations, since a section that re-animates on the way back up reads as a glitch. For finer control, Viewport takes a Margin and an Amount ("some", "all", or a 0..1 threshold).

Section 1
Section 2
Section 3
Section 4
Section 5
Section 6
Section 7
Section 8
Section 9
Section 10
Section 11
Section 12
Razor
Bmotion 0 el ยท 0 active
An unhandled error has occurred. Reload ๐Ÿ—™