Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/docs/reference/items/Motion/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ title: Motion examples

## Action-driven playback

Autoplay is disabled, so the card only moves when asked to. The buttons are [`Action`](/reference/items/Action/) components targeting the `Motion` instance with the arrow syntax, the hover pattern combines `play()` and `reverse()` for a symmetric in/out animation, and the last button runs a one-off spin with `animate()` — clicking Play afterwards returns to the animation declared by the options.
Autoplay is off by default, so the card only moves when asked to. The buttons are [`Action`](/reference/items/Action/) components targeting the `Motion` instance with the arrow syntax, the hover pattern combines `play()` and `reverse()` for a symmetric in/out animation, and the last button runs a one-off spin with `animate()` — clicking Play afterwards returns to the animation declared by the options.

<llm-exclude>
<PreviewPlayground
Expand Down
7 changes: 3 additions & 4 deletions packages/docs/reference/items/Motion/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ badges: [JS]

# Motion <Badges :texts="$frontmatter.badges" />

The `Motion` component animates its root element declaratively with the [Motion](https://motion.dev) library. Describe the animation with the `initial`, `animate` and `transition` options: the `initial` styles are applied on mount, then the `animate` keyframes play automatically — or on demand when autoplay is disabled.
The `Motion` component animates its root element declaratively with the [Motion](https://motion.dev) library. Describe the animation with the `initial`, `animate` and `transition` options: the `initial` styles are applied on mount, then the `animate` keyframes play on demand — or automatically when autoplay is enabled with `data-option-autoplay`.

The component is a thin, headless playback surface exposing imperative methods that an [`Action`](/reference/items/Action/) can call from any interaction: `play()` and `reverse()` always drive the animation declared by the options, while `animate()` runs a one-off to arbitrary keyframes. All its events bubble, so an ancestor `Action` can catch and route them; use the `.stop` event modifier to contain them in nested setups.

Expand Down Expand Up @@ -35,14 +35,13 @@ Option values are parsed as JSON, so object keys must be quoted:

## Driving the animation with `Action`

Disable autoplay with `data-option-no-autoplay` and control the playback from any interaction — see the [examples](./examples.html) for a complete demo:
Autoplay is off by default, so the playback is yours to control from any interaction — see the [examples](./examples.html) for a complete demo:

<!-- prettier-ignore-start -->
```html
<div
data-component="Motion"
data-option-animate='{ "x": 100 }'
data-option-no-autoplay>
data-option-animate='{ "x": 100 }'>
</div>

Expand Down
10 changes: 5 additions & 5 deletions packages/docs/reference/items/Motion/js-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Styles applied to the element on mount, before anything plays. Use it to define
- Type: `DOMKeyframesDefinition`
- Default: `{}`

The target keyframes of the animation. They play automatically on mount unless [`autoplay`](#autoplay) is disabled.
The target keyframes of the animation. They play on mount when [`autoplay`](#autoplay) is enabled.

### `transition`

Expand All @@ -56,16 +56,16 @@ Motion's animation options: `duration`, `delay`, `ease`, `type` (`"tween"`, `"sp
### `autoplay`

- Type: `boolean`
- Default: `true`
- Default: `false`

Whether the `animate` keyframes play automatically on mount. Since the default is `true`, use the negated `data-option-no-autoplay` attribute to require an explicit `play()`.
Whether the `animate` keyframes play automatically on mount. Enable it with the `data-option-autoplay` attribute; without it, playback waits for an explicit `play()`.

<!-- prettier-ignore-start -->
```html {4}
<div
data-component="Motion"
data-option-animate='{ "x": 100 }'
data-option-no-autoplay>
data-option-autoplay>
</div>
```
Expand Down Expand Up @@ -183,7 +183,7 @@ Methods are callable from an `Action` effect — on the same element or through
```html {2,3}
<div data-component="Action" data-on:mouseenter="Motion(#logo)->target.play()"
data-on:mouseleave="Motion(#logo)->target.reverse()">
<div id="logo" data-component="Motion" data-option-animate='{ "scale": 1.2 }' data-option-no-autoplay>…</div>
<div id="logo" data-component="Motion" data-option-animate='{ "scale": 1.2 }'>…</div>
</div>
```
<!-- prettier-ignore-end -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
data-component="Motion"
data-option-animate='{ "x": 160, "rotate": 10 }'
data-option-transition='{ "type": "spring", "bounce": 0.4 }'
data-option-no-autoplay
class="px-6 py-4 rounded-lg bg-blue-400 dark:bg-blue-600 text-white font-bold">
Drive me!
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
data-option-initial='{ "opacity": 0, "y": 24, "scale": 0.9 }'
data-option-animate='{ "opacity": 1, "y": 0, "scale": 1 }'
data-option-transition='{ "type": "spring", "bounce": 0.3, "duration": 0.8 }'
data-option-autoplay
class="px-6 py-4 rounded-lg bg-blue-400 dark:bg-blue-600 text-white font-bold">
Hello, I animate on mount!
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
data-component="Motion"
data-option-animate='{ "x": 160 }'
data-option-transition='{ "duration": 1.5 }'
data-option-no-autoplay
class="px-6 py-4 rounded-lg bg-blue-400 dark:bg-blue-600 text-white font-bold">
Watch my status
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
data-component="Motion"
data-option-animate='{ "x": 200, "rotate": 180 }'
data-option-transition='{ "duration": 2, "ease": "easeInOut" }'
data-option-no-autoplay
class="size-16 rounded-lg bg-blue-400 dark:bg-blue-600"></div>

<div class="flex flex-wrap gap-2">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
data-option-initial='{ "opacity": 0, "scale": 0.8, "y": 40 }'
data-option-animate='{ "opacity": 1, "scale": 1, "y": 0 }'
data-option-transition='{ "type": "spring", "bounce": 0.4 }'
data-option-no-autoplay
class="pointer-events-auto relative w-full max-w-lg p-8 rounded-lg bg-white text-black shadow-2xl">
<h2 class="text-xl font-bold mb-3">Spring entrance and exit</h2>
<p class="mb-6 text-black/60">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
data-component="Action Motion TimerProgress"
data-option-delay="8"
data-option-repeat
data-option-no-autoplay
data-option-animate='{ "x": 200, "rotate": 360, "borderRadius": "50%" }'
data-on:timer-progress="Motion.seek(1 - Math.abs(2 * event.detail[0] - 1))"
class="size-16 rounded-lg bg-blue-400 dark:bg-blue-600"></div>
Expand Down
5 changes: 2 additions & 3 deletions packages/docs/reference/items/MotionScrollTimeline/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ badges: [JS]

The `MotionScrollTimeline` component is the scroll driver for a group of animations: the element's traversal of the viewport defines the timeline, and every [`Motion`](/reference/items/Motion/) child it contains is bound to that progress with Motion's [`scroll()`](https://motion.dev/docs/scroll) — hardware-accelerated where the browser supports `ScrollTimeline`.

The children declare their keyframes as usual (arrays give multi-step tracks) and keep their whole playback surface; add `data-option-no-autoplay` so they do not play before the scroll link takes over. Registering the timeline is enough: it mounts its `Motion` children itself.
The children declare their keyframes as usual (arrays give multi-step tracks) and keep their whole playback surface; leave their `autoplay` off (its default) so they do not play before the scroll link takes over. Registering the timeline is enough: it mounts its `Motion` children itself.

`MotionScrollTimeline` is part of `@studiometa/ui-motion`, alongside [`Motion`](/reference/items/Motion/), [`MotionSequence`](/reference/items/MotionSequence/) and [`MotionView`](/reference/items/MotionView/).

Expand All @@ -19,8 +19,7 @@ The children declare their keyframes as usual (arrays give multi-step tracks) an
class="h-[300vh]">
<div
data-component="Motion"
data-option-animate='{ "opacity": [0, 1, 0], "y": [80, 0, -80] }'
data-option-no-autoplay>
data-option-animate='{ "opacity": [0, 1, 0], "y": [80, 0, -80] }'>
</div>
</section>
Expand Down
5 changes: 2 additions & 3 deletions packages/docs/reference/items/MotionScrollTimeline/js-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ title: MotionScrollTimeline JS API

# JS API

The scroll driver for a group of animations: the element's traversal of the viewport defines the timeline, and every [`Motion`](/reference/items/Motion/) child it contains is bound to that progress with Motion's [`scroll()`](https://motion.dev/docs/scroll) — hardware-accelerated where the browser supports `ScrollTimeline`. The children declare their keyframes as usual (arrays give multi-step tracks) and keep their whole playback surface; add `data-option-no-autoplay` so they do not play before the scroll link takes over.
The scroll driver for a group of animations: the element's traversal of the viewport defines the timeline, and every [`Motion`](/reference/items/Motion/) child it contains is bound to that progress with Motion's [`scroll()`](https://motion.dev/docs/scroll) — hardware-accelerated where the browser supports `ScrollTimeline`. The children declare their keyframes as usual (arrays give multi-step tracks) and keep their whole playback surface; leave their `autoplay` off (its default) so they do not play before the scroll link takes over.

<!-- prettier-ignore-start -->
```html {2,6}
Expand All @@ -13,8 +13,7 @@ The scroll driver for a group of animations: the element's traversal of the view
class="h-[300vh]">
<div
data-component="Motion"
data-option-animate='{ "opacity": [0, 1, 0], "y": [80, 0, -80] }'
data-option-no-autoplay>
data-option-animate='{ "opacity": [0, 1, 0], "y": [80, 0, -80] }'>
</div>
</section>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,12 @@
<h2
data-component="Motion"
data-option-animate='{ "opacity": [0, 1, 1, 0], "y": [80, 0, 0, -80] }'
data-option-no-autoplay
class="text-3xl font-bold">
Driven by scroll
</h2>
<div
data-component="Motion"
data-option-animate='{ "rotate": [0, 360], "scale": [0.5, 1.5], "borderRadius": ["10%", "50%"] }'
data-option-no-autoplay
class="size-24 bg-blue-400 dark:bg-blue-600"></div>
</div>
</section>
Expand Down
8 changes: 4 additions & 4 deletions packages/docs/reference/items/MotionSequence/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ badges: [JS]

The `MotionSequence` component orchestrates its [`Motion`](/reference/items/Motion/) children as one animation sequence: each child declares its keyframes as usual, and the sequence composes them — in DOM order — into a single timeline with Motion's [sequencing](https://motion.dev/docs/animate#timeline-sequencing).

The whole playback surface applies to the sequence: an [`Action`](/reference/items/Action/) can `play()`, `reverse()` or `seek()` the entire choreography, and a [`MotionScrollTimeline`](/reference/items/MotionScrollTimeline/) can scrub it. Give the children `data-option-no-autoplay` the sequence owns their playback.
The whole playback surface applies to the sequence: an [`Action`](/reference/items/Action/) can `play()`, `reverse()` or `seek()` the entire choreography, and a [`MotionScrollTimeline`](/reference/items/MotionScrollTimeline/) can scrub it. The sequence owns the children's playback — leave their `autoplay` off (its default) and enable it on the sequence itself with `data-option-autoplay` to play on mount.

`MotionSequence` is part of `@studiometa/ui-motion`, alongside [`Motion`](/reference/items/Motion/), [`MotionScrollTimeline`](/reference/items/MotionScrollTimeline/) and [`MotionView`](/reference/items/MotionView/).

## Usage

<!-- prettier-ignore-start -->
```html {1}
<ul data-component="MotionSequence" data-option-stagger="0.1">
<li data-component="Motion" data-option-initial='{ "opacity": 0, "y": 16 }' data-option-animate='{ "opacity": 1, "y": 0 }' data-option-no-autoplay>One</li>
<li data-component="Motion" data-option-initial='{ "opacity": 0, "y": 16 }' data-option-animate='{ "opacity": 1, "y": 0 }' data-option-no-autoplay>Two</li>
<ul data-component="MotionSequence" data-option-stagger="0.1" data-option-autoplay>
<li data-component="Motion" data-option-initial='{ "opacity": 0, "y": 16 }' data-option-animate='{ "opacity": 1, "y": 0 }'>One</li>
<li data-component="Motion" data-option-initial='{ "opacity": 0, "y": 16 }' data-option-animate='{ "opacity": 1, "y": 0 }'>Two</li>
</ul>
```
<!-- prettier-ignore-end -->
Expand Down
8 changes: 4 additions & 4 deletions packages/docs/reference/items/MotionSequence/js-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ title: MotionSequence JS API

# JS API

Orchestrate the [`Motion`](/reference/items/Motion/) children as one animation sequence: each child declares its keyframes as usual, and the sequence composes them — in DOM order — into a single timeline with Motion's [sequencing](https://motion.dev/docs/animate#timeline-sequencing). The whole playback surface applies to the sequence: an [`Action`](/reference/items/Action/) can `play()`, `reverse()` or `seek()` the entire choreography, and a [`MotionScrollTimeline`](/reference/items/MotionScrollTimeline/) can scrub it. Give the children `data-option-no-autoplay` the sequence owns their playback.
Orchestrate the [`Motion`](/reference/items/Motion/) children as one animation sequence: each child declares its keyframes as usual, and the sequence composes them — in DOM order — into a single timeline with Motion's [sequencing](https://motion.dev/docs/animate#timeline-sequencing). The whole playback surface applies to the sequence: an [`Action`](/reference/items/Action/) can `play()`, `reverse()` or `seek()` the entire choreography, and a [`MotionScrollTimeline`](/reference/items/MotionScrollTimeline/) can scrub it. The sequence owns the children's playback — leave their `autoplay` off (its default) and enable it on the sequence itself with `data-option-autoplay` to play on mount.

<!-- prettier-ignore-start -->
```html {1}
<ul data-component="MotionSequence" data-option-stagger="0.1">
<li data-component="Motion" data-option-initial='{ "opacity": 0, "y": 16 }' data-option-animate='{ "opacity": 1, "y": 0 }' data-option-no-autoplay>One</li>
<li data-component="Motion" data-option-initial='{ "opacity": 0, "y": 16 }' data-option-animate='{ "opacity": 1, "y": 0 }' data-option-no-autoplay>Two</li>
<ul data-component="MotionSequence" data-option-stagger="0.1" data-option-autoplay>
<li data-component="Motion" data-option-initial='{ "opacity": 0, "y": 16 }' data-option-animate='{ "opacity": 1, "y": 0 }'>One</li>
<li data-component="Motion" data-option-initial='{ "opacity": 0, "y": 16 }' data-option-animate='{ "opacity": 1, "y": 0 }'>Two</li>
</ul>
```
<!-- prettier-ignore-end -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,25 @@
<ul
data-component="MotionSequence"
data-option-stagger="0.15"
data-option-no-autoplay
class="grid gap-2 list-none p-0">
<li
data-component="Motion"
data-option-initial='{ "opacity": 0, "x": -32 }'
data-option-animate='{ "opacity": 1, "x": 0 }'
data-option-no-autoplay
class="px-4 py-2 rounded bg-blue-400 dark:bg-blue-600 text-white font-bold">
One
</li>
<li
data-component="Motion"
data-option-initial='{ "opacity": 0, "x": -32 }'
data-option-animate='{ "opacity": 1, "x": 0 }'
data-option-no-autoplay
class="px-4 py-2 rounded bg-blue-400 dark:bg-blue-600 text-white font-bold">
Two
</li>
<li
data-component="Motion"
data-option-initial='{ "opacity": 0, "x": -32 }'
data-option-animate='{ "opacity": 1, "x": 0 }'
data-option-no-autoplay
class="px-4 py-2 rounded bg-blue-400 dark:bg-blue-600 text-white font-bold">
Three
</li>
Expand All @@ -34,7 +30,6 @@
data-option-animate='{ "opacity": 1, "scale": 1 }'
data-option-transition='{ "type": "spring", "bounce": 0.5 }'
data-option-at="+0.2"
data-option-no-autoplay
class="px-4 py-2 rounded bg-emerald-400 dark:bg-emerald-600 text-white font-bold">
Finale, 0.2s later
</li>
Expand Down
2 changes: 1 addition & 1 deletion packages/tests/Motion/Motion.gestures.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe('Motion gesture options', () => {
});

it('should bind nothing without gesture options', async () => {
await mountMotion({ dataOptionAnimate: '{ "x": 100 }', dataOptionNoAutoplay: '' });
await mountMotion({ dataOptionAnimate: '{ "x": 100 }' });

expect(mockHover.fn).not.toHaveBeenCalled();
expect(mockPress.fn).not.toHaveBeenCalled();
Expand Down
Loading
Loading