Skip to content
Open
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

### Changed

- **ScrollAnimation:** refactor exports ([#494](https://github.com/studiometa/ui/pull/494), [a5d0e29](https://github.com/studiometa/ui/commit/a5d0e29))

## [v1.7.0](https://github.com/studiometa/ui/compare/1.6.0..1.7.0) (2025-11-11)

### Added
Expand Down
5 changes: 5 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# @studiometa/ui packages

## Commit messages

- Use English for commit messages
- Use simple verb-first sentences (e.g., "Add...", "Fix...", "Refactor...")

## Project structure

- Monorepo managed by NPM with packages in the `./packages` folder
Expand Down
10 changes: 6 additions & 4 deletions packages/docs/components/ScrollAnimation/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ title: ScrollAnimation examples

</llm-only>

## Parent driven animation
## Timeline driven animation

Coordinate multiple animations using `ScrollAnimationTimeline` with `ScrollAnimationTarget` children.

<llm-exclude>
<PreviewPlayground
Expand Down Expand Up @@ -111,11 +113,11 @@ Here, we even use the [ImageGrid organism](/components/ImageGrid/) to quickly ha

</llm-only>

## Parallax with a parent
## Parallax with a timeline

It might be sometimes interesting to use the parent ↔ child logic of the `ScrollAnimation` component to improve performance, as only the parent progression in the viewport is watched.
It might be sometimes interesting to use the timeline ↔ target logic of the `ScrollAnimation` component to improve performance, as only the timeline progression in the viewport is watched.

The resulting effect is different as each child animation is driven by the parent one, but it is still interesting.
The resulting effect is different as each target animation is driven by the timeline, but it is still interesting.

<llm-exclude>
<PreviewPlayground
Expand Down
62 changes: 59 additions & 3 deletions packages/docs/components/ScrollAnimation/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,49 @@ The `ScrollAnimation` component creates scroll-driven animations that respond to

## Usage

Once the [package installed](/guide/installation/), simply include the component in your project:
For the best performance and flexibility, use `ScrollAnimationTimeline` with `ScrollAnimationTarget` children:

```js{2,3,9,10}
import { Base, createApp } from '@studiometa/js-toolkit';
import { ScrollAnimationTimeline, ScrollAnimationTarget } from '@studiometa/ui';

class App extends Base {
static config = {
name: 'App',
components: {
ScrollAnimationTimeline,
ScrollAnimationTarget,
},
};
}

export default createApp(App, document.body);
```

```html
<div data-component="ScrollAnimationTimeline">
<div
data-component="ScrollAnimationTarget"
data-option-from='{"opacity": 0, "translateY": "100px"}'
data-option-to='{"opacity": 1, "translateY": "0px"}'
data-option-play-range='[0, 0.5]'
>
First element
</div>
<div
data-component="ScrollAnimationTarget"
data-option-from='{"opacity": 0, "translateY": "100px"}'
data-option-to='{"opacity": 1, "translateY": "0px"}'
data-option-play-range='[0.5, 1]'
>
Second element
</div>
</div>
```

## Legacy usage

The `ScrollAnimation` component can be used standalone for simple use cases, but it is **deprecated** in favor of the timeline API.

```js{2,8}
import { Base, createApp } from '@studiometa/js-toolkit';
Expand All @@ -32,8 +74,8 @@ export default createApp(App, document.body);
```

```html
<div
data-component="ScrollAnimation"
<div
data-component="ScrollAnimation"
data-option-from='{"opacity": 0, "translateY": "100px"}'
data-option-to='{"opacity": 1, "translateY": "0px"}'
>
Expand All @@ -51,5 +93,19 @@ export default createApp(App, document.body);
- **Easing Control**: Customize animation timing with cubic-bezier easing
- **Play Range**: Control when animation starts and ends during scroll
- **Performance**: Optimized with intersection observer and RAF
- **Timeline Support**: Coordinate multiple animations with `ScrollAnimationTimeline` and `ScrollAnimationTarget`
- **Variants**: Multiple specialized classes for different use cases

## Deprecated components

:::warning Deprecated
The following components are deprecated and will be removed in a future version. Use `ScrollAnimationTimeline` and `ScrollAnimationTarget` instead:

- `ScrollAnimation` β†’ use `ScrollAnimationTimeline` and `ScrollAnimationTarget`
- `ScrollAnimationParent` β†’ use `ScrollAnimationTimeline`
- `ScrollAnimationChild` β†’ use `ScrollAnimationTarget`
- `ScrollAnimationChildWithEase` β†’ use `ScrollAnimationTarget`
- `ScrollAnimationWithEase` β†’ use `ScrollAnimationTimeline` and `ScrollAnimationTarget`
- `animationScrollWithEase` β†’ no replacement
:::

108 changes: 107 additions & 1 deletion packages/docs/components/ScrollAnimation/js-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,116 @@ Manually render the animation at a specific progress value.

- Type: `HTMLElement`

The element being animated (either the `target` ref or the component's root element).
The element being animated. For `ScrollAnimationTarget`, it defaults to the component's root element (`$el`). For the legacy `ScrollAnimation`, it uses the `target` ref if provided.

### `animation`

- Type: `Animation`

The animation instance created from the keyframes and easing options. See [`animate` documentation](https://js-toolkit.studiometa.dev/utils/css/animate.html).

---

## ScrollAnimationTimeline

A parent component that manages scroll-based animations for its children `ScrollAnimationTarget` components.

### Usage

```js
import { Base, createApp } from '@studiometa/js-toolkit';
import { ScrollAnimationTimeline, ScrollAnimationTarget } from '@studiometa/ui';

class App extends Base {
static config = {
name: 'App',
components: {
ScrollAnimationTimeline,
ScrollAnimationTarget,
},
};
}

export default createApp(App, document.body);
```

```html
<div data-component="ScrollAnimationTimeline">
<div
data-component="ScrollAnimationTarget"
data-option-from='{"opacity": 0}'
data-option-to='{"opacity": 1}'
>
Content
</div>
</div>
```

### Children Components

#### `ScrollAnimationTarget`

- Type: `ScrollAnimationTarget[]`

Array of child animation targets that will be animated based on the scroll progress of the timeline.

---

## ScrollAnimationTarget

A component that animates based on scroll progress from a parent `ScrollAnimationTimeline`. Each target can have its own animation keyframes and play range.

### Usage

```html
<div data-component="ScrollAnimationTimeline">
<div
data-component="ScrollAnimationTarget"
data-option-from='{"opacity": 0, "translateY": "100px"}'
data-option-to='{"opacity": 1, "translateY": "0px"}'
data-option-play-range='[0, 0.5]'
>
First element
</div>
<div
data-component="ScrollAnimationTarget"
data-option-from='{"opacity": 0, "translateY": "100px"}'
data-option-to='{"opacity": 1, "translateY": "0px"}'
data-option-play-range='[0.5, 1]'
>
Second element
</div>
</div>
```

### Options

`ScrollAnimationTarget` inherits all options from the base animation class and adds:

#### `dampFactor`

- Type: `number`
- Default: `0.1`

Damping factor for smooth scroll animations. Lower values create smoother, slower animations.

#### `dampPrecision`

- Type: `number`
- Default: `0.001`

Precision threshold for damping calculations. Lower values increase precision but may impact performance.

### Properties

#### `dampedCurrent`

- Type: `{ x: number, y: number }`

Current damped scroll position values for both axes.

#### `dampedProgress`

- Type: `{ x: number, y: number }`

Current damped progress values (0-1) for both axes.
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Base, createApp } from '@studiometa/js-toolkit';
import { Figure, ScrollAnimationChild, ScrollAnimationParent } from '@studiometa/ui';
import { Figure, ScrollAnimationTarget, ScrollAnimationTimeline } from '@studiometa/ui';

class ParallaxChild extends ScrollAnimationChild {
class ParallaxTarget extends ScrollAnimationTarget {
static config = {
...ScrollAnimationChild.config,
name: 'ParallaxChild',
...ScrollAnimationTarget.config,
name: 'ParallaxTarget',
components: {
Figure,
},
Expand All @@ -15,12 +15,12 @@ class ParallaxChild extends ScrollAnimationChild {
}
}

class ParallaxParent extends ScrollAnimationParent {
class ParallaxTimeline extends ScrollAnimationTimeline {
static config = {
...ScrollAnimationParent.config,
name: 'ParallaxParent',
...ScrollAnimationTimeline.config,
name: 'ParallaxTimeline',
components: {
ParallaxChild,
ParallaxTarget,
},
};

Expand All @@ -29,7 +29,7 @@ class ParallaxParent extends ScrollAnimationParent {
}

scrolledInView(props) {
this.$children.ParallaxChild.forEach((child) => {
this.$children.ParallaxTarget.forEach((child) => {
child.scrolledInView(props);
});
}
Expand All @@ -39,7 +39,7 @@ class App extends Base {
static config = {
name: 'App',
components: {
ParallaxParent,
ParallaxTimeline,
},
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@
}
] %}

<div class="max-w-2xl l:max-w-5xl mx-auto" data-component="ParallaxParent">
<div class="max-w-2xl l:max-w-5xl mx-auto" data-component="ParallaxTimeline">
{% include '@ui/ImageGrid/ImageGrid.twig' with {
images: images,
image_attr: {
data_component: 'ParallaxChild',
data_component: 'ParallaxTarget',
data_option_from: { y: [-20, '%'] },
data_option_to: { y: [20, '%'] },
class: 'h-fit overflow-hidden',
Expand Down
16 changes: 4 additions & 12 deletions packages/docs/components/ScrollAnimation/stories/parent/app.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
import { Base, createApp } from '@studiometa/js-toolkit';
import {
ScrollAnimationParent as ScrollAnimationParentCore,
ScrollAnimationChild,
ScrollAnimationTimeline,
ScrollAnimationTarget,
} from '@studiometa/ui';

class ScrollAnimationParent extends ScrollAnimationParentCore {
static config = {
name: 'ScrollAnimationParent',
components: {
ScrollAnimationChild,
},
};
}

class App extends Base {
static config = {
name: 'App',
components: {
ScrollAnimationParent,
ScrollAnimationTimeline,
ScrollAnimationTarget,
},
};
}
Expand Down
Loading
Loading