diff --git a/packages/docs/reference/items/Motion/index.md b/packages/docs/reference/items/Motion/index.md index c0a4d4b5..8b2d1ccb 100644 --- a/packages/docs/reference/items/Motion/index.md +++ b/packages/docs/reference/items/Motion/index.md @@ -14,7 +14,7 @@ The `motion` peer dependency is resolved with a lazy `import()` the first time a ## Usage -Option values are parsed as JSON, so object keys must be quoted: +Option values are parsed as JSON, so object keys must be quoted. The animation below plays once on mount, which a preview finishes booting before you look at it — hence the replay button, which calls `play()` to restart it: +```html {4} +
+ … +
+``` + + +Keep [`initial`](#initial) alongside: it paints the starting state before the component mounts, where the keyframes only apply once the animation runs. + ### `transition` - Type: `AnimationOptions` diff --git a/packages/docs/reference/items/Motion/stories/basic/app.js b/packages/docs/reference/items/Motion/stories/basic/app.js index 12443a8b..ddee54e7 100644 --- a/packages/docs/reference/items/Motion/stories/basic/app.js +++ b/packages/docs/reference/items/Motion/stories/basic/app.js @@ -1,4 +1,5 @@ import { registerComponents } from '@studiometa/js-toolkit'; +import { Action } from '@studiometa/ui'; import { Motion } from '@studiometa/ui-motion'; -registerComponents(Motion); +registerComponents(Action, Motion); diff --git a/packages/docs/reference/items/Motion/stories/basic/app.twig b/packages/docs/reference/items/Motion/stories/basic/app.twig index 42dd958a..9b874660 100644 --- a/packages/docs/reference/items/Motion/stories/basic/app.twig +++ b/packages/docs/reference/items/Motion/stories/basic/app.twig @@ -1,9 +1,25 @@ -
- Hello, I animate on mount! +{# The animation plays once, on mount. A preview boots before you look at it, + so the story pairs it with a replay button. The keyframes are written as + `[from, to]` arrays on purpose: a single target value would animate from + whatever the element currently shows, so a replay of a settled animation + would have nothing to move. #} +
+
+ Hello, I animate on mount! +
+ +
diff --git a/packages/docs/reference/items/MotionScrollTimeline/examples.md b/packages/docs/reference/items/MotionScrollTimeline/examples.md index f1cccdaa..9e166b6f 100644 --- a/packages/docs/reference/items/MotionScrollTimeline/examples.md +++ b/packages/docs/reference/items/MotionScrollTimeline/examples.md @@ -24,3 +24,45 @@ title: MotionScrollTimeline examples ::: + +## Reading progress bar + +The timeline element is the article, and the [`offset`](./js-api#offset) maps progress `0` to its top reaching the top of the viewport and progress `1` to its bottom reaching the bottom — the reading range, rather than the default entering-and-leaving range. The sticky bar holds a single `Motion` child scrubbed from `scaleX: 0` to `scaleX: 1`, which is the cheap way to draw a progress indicator: the browser animates a transform instead of a width, anchored to the left edge by `origin-left`. + + + + + + +:::code-group + +<<< ./stories/reading-progress/app.twig +<<< ./stories/reading-progress/app.js + +::: + + + +## Parallax layers + +One timeline, one scroll range, three `Motion` children: only the travelled distance changes. The far disc moves 80 pixels, the card 180 and the badge 300 over the same progress, and the speed difference alone reads as depth. The timeline scrubs every child with the same progress, so depth is authored entirely in the keyframes — there is no per-layer speed or offset option to set. + + + + + + +:::code-group + +<<< ./stories/parallax-layers/app.twig +<<< ./stories/parallax-layers/app.js + +::: + + diff --git a/packages/docs/reference/items/MotionScrollTimeline/stories/parallax-layers/app.js b/packages/docs/reference/items/MotionScrollTimeline/stories/parallax-layers/app.js new file mode 100644 index 00000000..610d29d1 --- /dev/null +++ b/packages/docs/reference/items/MotionScrollTimeline/stories/parallax-layers/app.js @@ -0,0 +1,4 @@ +import { registerComponents } from '@studiometa/js-toolkit'; +import { MotionScrollTimeline } from '@studiometa/ui-motion'; + +registerComponents(MotionScrollTimeline); diff --git a/packages/docs/reference/items/MotionScrollTimeline/stories/parallax-layers/app.twig b/packages/docs/reference/items/MotionScrollTimeline/stories/parallax-layers/app.twig new file mode 100644 index 00000000..a46a7c7a --- /dev/null +++ b/packages/docs/reference/items/MotionScrollTimeline/stories/parallax-layers/app.twig @@ -0,0 +1,36 @@ +

+ Scroll down ↓ +

+ +{# One timeline, one scroll range, three Motion children: only the travelled + distance differs. The far layer moves the least and the near one the most, + so the speed difference alone reads as depth — no per-layer wiring, the + timeline scrubs them all with the same progress. #} +
+
+
+
+

Parallax layers

+

+ Same range, three speeds. +

+
+
+ Closest +
+
+
+ +

+ Done ↑ +

diff --git a/packages/docs/reference/items/MotionScrollTimeline/stories/reading-progress/app.js b/packages/docs/reference/items/MotionScrollTimeline/stories/reading-progress/app.js new file mode 100644 index 00000000..610d29d1 --- /dev/null +++ b/packages/docs/reference/items/MotionScrollTimeline/stories/reading-progress/app.js @@ -0,0 +1,4 @@ +import { registerComponents } from '@studiometa/js-toolkit'; +import { MotionScrollTimeline } from '@studiometa/ui-motion'; + +registerComponents(MotionScrollTimeline); diff --git a/packages/docs/reference/items/MotionScrollTimeline/stories/reading-progress/app.twig b/packages/docs/reference/items/MotionScrollTimeline/stories/reading-progress/app.twig new file mode 100644 index 00000000..e6cd2c7e --- /dev/null +++ b/packages/docs/reference/items/MotionScrollTimeline/stories/reading-progress/app.twig @@ -0,0 +1,58 @@ +{# The article itself is the timeline, and the `offset` maps progress 0 to its + top reaching the top of the viewport and progress 1 to its bottom reaching + the bottom — the reading range. The single Motion child inside the sticky + bar is scrubbed by that progress, so its scaleX track reads as the share of + the article already scrolled. #} +
+
+
+
+ +
+

Reading progress

+

+ Scroll this article: the bar above sticks to the top of the section and + fills from left to right as the content goes by. Nothing listens to the + scroll event — the timeline binds the bar to the scroll position once, on + mount. +

+

+ The bar is a plain Motion child. It declares a single + scaleX track from 0 to 1, keeps its + autoplay off, and the timeline takes over its playback: the animation + never runs on its own clock, it is scrubbed. +

+

+ Scaling is the cheap way to draw a progress bar: the browser animates a + transform instead of a width, and the + origin-left class anchors the growth to the left edge. +

+

+ The default offset would start the bar as soon as the section enters + the viewport from the bottom, so the bar would already be part-filled on the + first paint. The + ["start start", "end end"] range instead follows the section's + own scroll, which is what a reader expects from a progress indicator. +

+

+ Any number of children can share the same timeline. A second + Motion could fade a "back to top" button in over the last part + of the range, without a single line of JavaScript. +

+

+ Where the browser supports ScrollTimeline, the link is + hardware-accelerated and runs off the main thread; elsewhere Motion falls + back to a scroll listener with the same result. +

+

+ The link is released when the timeline is destroyed, so a section that + leaves the page leaves nothing behind. +

+
+
diff --git a/packages/docs/reference/items/MotionSequence/examples.md b/packages/docs/reference/items/MotionSequence/examples.md index 589c04be..d6737e1d 100644 --- a/packages/docs/reference/items/MotionSequence/examples.md +++ b/packages/docs/reference/items/MotionSequence/examples.md @@ -24,3 +24,24 @@ title: MotionSequence examples ::: + +## Hero intro on mount + +A badge, a heading, a paragraph and a button entering as one choreography: `data-option-autoplay` goes on the sequence, never on the children, because the sequence owns their playback. The three positioning modes are mixed in one timeline — the badge and the heading follow one another in DOM order, the paragraph starts _with_ the heading through [`data-option-at="<"`](./js-api#at-on-the-children), and the button overlaps the end of the paragraph with the relative offset `data-option-at="-0.2"`. Each child keeps its own `initial`/`animate` keyframes and its own spring. A mount animation is over before a reader reaches the preview, so the replay button restarts the whole choreography with a single `play()` on the sequence. + + + + + + +:::code-group + +<<< ./stories/hero-intro/app.twig +<<< ./stories/hero-intro/app.js + +::: + + diff --git a/packages/docs/reference/items/MotionSequence/stories/hero-intro/app.js b/packages/docs/reference/items/MotionSequence/stories/hero-intro/app.js new file mode 100644 index 00000000..b74d14a1 --- /dev/null +++ b/packages/docs/reference/items/MotionSequence/stories/hero-intro/app.js @@ -0,0 +1,5 @@ +import { registerComponents } from '@studiometa/js-toolkit'; +import { Action } from '@studiometa/ui'; +import { MotionSequence } from '@studiometa/ui-motion'; + +registerComponents(Action, MotionSequence); diff --git a/packages/docs/reference/items/MotionSequence/stories/hero-intro/app.twig b/packages/docs/reference/items/MotionSequence/stories/hero-intro/app.twig new file mode 100644 index 00000000..fbc16e69 --- /dev/null +++ b/packages/docs/reference/items/MotionSequence/stories/hero-intro/app.twig @@ -0,0 +1,58 @@ +{# One choreography, played on mount: `data-option-autoplay` belongs to the + sequence, never to the children — the sequence owns their playback. The + badge and the heading follow one another in DOM order, the paragraph joins + the heading with `data-option-at="<"`, and the button overlaps the end of + the paragraph with the relative offset `data-option-at="-0.2"`. The replay + button below drives the whole timeline with a single `play()`. #} +
+ + New + + +

+ Animate the whole hero as one timeline +

+ +

+ This paragraph starts with the heading, not after it: "<" + positions a segment at the start of the previous one. +

+ + +
+ + diff --git a/packages/docs/reference/items/MotionView/examples.md b/packages/docs/reference/items/MotionView/examples.md index 3ef32ace..7188b767 100644 --- a/packages/docs/reference/items/MotionView/examples.md +++ b/packages/docs/reference/items/MotionView/examples.md @@ -27,7 +27,7 @@ title: MotionView examples ## Zero-wiring exit animation for `data-bind:if` -[Ambient wiring](./js-api#ambient-wiring) in action: the `MotionView` has no wiring attribute at all — it wraps the `dom-update` event that [`data-bind:if`](/reference/items/DataBind/js-api#conditional-rendering-with-data-bind-if) announces before changing the DOM, so the template content animates in **and out**, even though the removed nodes are already gone when the exit plays. The `layout` option morphs the container's size with a spring. +[Ambient wiring](./js-api#ambient-wiring) in action: the `MotionView` has no wiring attribute at all — it wraps the `dom-update` event that [`data-bind:if`](/reference/items/DataBind/js-api#conditional-rendering-with-data-bind-if) announces before changing the DOM, so the template content animates in **and out**, even though the removed nodes are already gone when the exit plays. The `layout` option morphs the container's size with a spring. Because the event bubbles, the `MotionView` only needs to sit just outside the `