Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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: 2 additions & 2 deletions packages/interact-validate/src/semantic/fouc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ export function checkHitAreaShift(
const isPointer = owner.trigger === 'pointerMove';
if (!isDiscrete && !isPointer) return [];
// `hitArea: 'root'` tracks the viewport, so a transform on the source cannot
// shift the hit area. Default (`'self'`) and explicit `'self'` are at risk.
if (isPointer && owner.params?.hitArea === 'root') return [];
// shift the hit area. Only explicit `'self'` are at risk.
if (isPointer && owner.params?.hitArea !== 'self') return [];
if (!targetsSameElementAsSource(owner, effect)) return [];
const keyframes = effect.keyframeEffect?.keyframes;
if (!Array.isArray(keyframes)) return [];
Expand Down
26 changes: 18 additions & 8 deletions packages/interact/rules/full-lean.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ For most use cases, `key` alone is sufficient for both source and target resolut
- `effectId`: string of the effect to wait for completion
- Usage: Fire when the specified effect (by `effectId`) on the source element finishes, useful for chaining sequences.
- pointerMove: `PointerMoveParams`
- `hitArea?`: `'root' | 'self'` (default `'self'`)
- `hitArea?`: `'root' | 'self'` (default `'root'` — an omitted `hitArea` tracks the viewport)
- `axis?`: `'x' | 'y'` - when using `keyframeEffect` with `pointerMove`, selects which pointer coordinate maps to linear 0-1 progress; defaults to `'y'`. Ignored for `namedEffect` and `customEffect`.
- Usage:
- `'self'`: Track pointer within the source element’s bounds.
Expand Down Expand Up @@ -283,15 +283,23 @@ For `TimeEffect` (keyframe/named/custom effects), set `triggerType` on the effec

```ts
params: {
threshold?: number; // 0–1, IntersectionObserver threshold
inset?: string; // like view-timeline-inset, e.g. '-100px' or '-50px 0px'
threshold?: number; // 0–1, IntersectionObserver threshold (default 0.2)
inset?: string; // like view-timeline-inset, e.g. '-100px' or '-50px 0px'
useSafeViewEnter?: boolean; // default false; see below
}
// Playback behavior is set on each effect:
effect.triggerType: 'once' | 'repeat' | 'alternate' | 'state'; // default: 'once'
```

**CRITICAL:** When source and target are the **same element**, MUST use `triggerType: 'once'`. For `'repeat'` / `'alternate'` / `'state'`, ALWAYS use **separate** source and target elements — animating the observed element can cause it to leave/re-enter the viewport, causing rapid re-triggers.

**`useSafeViewEnter`** — guards against a `threshold` that can never be met. A `threshold` is a fraction of the **source's own box**, so when `sourceHeight × threshold` exceeds the viewport height the ratio is unreachable and the animation never fires. With this flag set, the first non-intersecting observer callback measures the source and, if the threshold is unreachable, swaps to a fallback observer (`threshold: 0`, `rootMargin: '0px 0px -10% 0px'`).

Two constraints that follow from the implementation:

- It only helps alongside an **explicit** `threshold`. The check reads the authored value, not the `0.2` default, so `useSafeViewEnter: true` on its own does nothing.
- The fallback observer uses a fixed config, so a configured `inset` is discarded once it kicks in.

### viewProgress

Scroll-driven animations using native `ViewTimeline`, with polyfill where not supported. Progress is driven by scroll position. Control the range via `rangeStart`/`rangeEnd` on the effect (see [Scroll / Pointer-driven Effect](#scroll--pointer-driven-effect)).
Expand All @@ -317,7 +325,9 @@ params: {
- For 2D effects, use `namedEffect` mouse presets or `customEffect`. `keyframeEffect` only supports a single axis.
- For independent 2-axis control with keyframes, use two separate interactions (one `axis: 'x'`, one `axis: 'y'`) with `composite: 'add'` or `'accumulate'` on the second effect.

**`centeredToTarget`** — set `true` to remap the `0–1` progress range so that `0.5` progress corresponds to the center of the target element. Use when source and target are different elements, or when `hitArea: 'root'` is used, so that the pointer resting over the target center produces 50% progress regardless of position in viewport.
**`centeredToTarget`** — set `true` to remap the `0–1` progress range so that `0.5` progress corresponds to the center of the target element. Use when source and target are different elements, or when `hitArea: 'root'` is used, so that the pointer resting over the target center produces 50% progress regardless of position in viewport. Applies to `namedEffect` and `customEffect` only: a `keyframeEffect` scrub scene resolves no target, so centering is silently ignored there.

**`transitionDuration` / `transitionEasing`** — progress smoothing. Forwarded **only** when the payload is a `customEffect`; they are dropped for `keyframeEffect` and `namedEffect` on `pointerMove`.

**Progress object** (for `customEffect`):

Expand Down Expand Up @@ -406,10 +416,10 @@ Used with `viewProgress` and `pointerMove` triggers.
reversed?: boolean;
fill?: 'none' | 'forwards' | 'backwards' | 'both';
composite?: 'replace' | 'add' | 'accumulate';
centeredToTarget?: boolean;
transitionDuration?: number; // ms, smoothing on progress jumps (primarily for pointerMove)
centeredToTarget?: boolean; // pointerMove; namedEffect / customEffect only — ignored for keyframeEffect
transitionDuration?: number; // ms, smoothing on progress jumps; pointerMove + customEffect only
transitionDelay?: number; // ms (primarily for pointerMove)
transitionEasing?: 'linear' | 'hardBackOut' | 'easeOut' | 'elastic' | 'bounce';
transitionEasing?: 'linear' | 'hardBackOut' | 'easeOut' | 'elastic' | 'bounce'; // pointerMove + customEffect only
// + exactly one animation payload (see below)
}
```
Expand Down Expand Up @@ -450,7 +460,7 @@ Used with `hover` / `click` triggers. Set `stateAction` on the effect to control
- `transition?`: `{ duration?: number; delay?: number; easing?: string; styleProperties: { name: string; value: string }[] }`
- Applies a single transition options block to all listed style properties.
- `transitionProperties?`: `Array<{ name: string; value: string; duration?: number; delay?: number; easing?: string }>`
- Allows per-property transition options. If both `transition` and `transitionProperties` are provided, the system SHOULD apply both with per-property entries taking precedence for overlapping properties.
- Allows per-property transition options. Set one or the other: if `transition.styleProperties` is present, `transitionProperties` is ignored **entirely** — it is not merged, and per-property entries do not take precedence for overlapping properties.

```ts
// Shared timing for all properties:
Expand Down
2 changes: 1 addition & 1 deletion packages/interact/rules/integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,5 +351,5 @@ Each `Interact.create(config)` call returns an instance. Keep a reference if you
| `Interact.registerEffects(presets)` | Register named effect presets before `generate()` and `create`. Required for `namedEffect`. |
| `Interact.destroy()` | Tear down all instances. |
| `Interact.forceReducedMotion` | `boolean` — force reduced-motion behavior regardless of OS setting. Default: `false`. |
| `Interact.allowA11yTriggers` | `boolean` — enable accessibility triggers (`interest`, `activate`). Default: `false`. |
| `Interact.allowA11yTriggers` | `boolean` — enable accessibility triggers (`interest`, `activate`). Default: `true`. |
| `Interact.setup(options)` | Configure global defaults for scroll/pointer/viewEnter trigger params. Call before `create`. |
36 changes: 14 additions & 22 deletions packages/interact/rules/pointermove.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ type PointerMoveParams = {

### Properties

- `hitArea` — determines where mouse movement is tracked:
- `hitArea` — determines where mouse movement is tracked. **Omitting it behaves as `'root'`**: only an explicit `'self'` scopes tracking to the source element.
- `'self'` — tracks pointer within the source element's bounds only. Use for local pointer-tracking effects on a specific element.
- `'root'` — tracks pointer anywhere in the viewport. Use for global cursor followers, ambient effects.
- `'root'` (default) — tracks pointer anywhere in the viewport. Use for global cursor followers, ambient effects.
- `axis` — restricts pointer tracking to a single axis. Used with `keyframeEffect` to map one axis to 0–1 progress; ignored by `namedEffect` and `customEffect` which receive the full 2D progress:
- `'x'` — maps horizontal pointer position to 0–1 progress for keyframe interpolation.
- `'y'` — maps vertical pointer position to 0–1 progress for keyframe interpolation. **Default** when `keyframeEffect` is used.
Expand Down Expand Up @@ -69,7 +69,9 @@ type Progress = {
Controls which element's bounds define the 0–1 progress range.

- **`false` (default)**: Progress is calculated against the **source element's** (or viewport's) bounds. The `50%` progress of the timeline is at the center of the source element.
- **`true`**: `50%` progress of the timeline is calculated against the **target element's center**. The edges of the timeline are still calculated against the edges of the source element/viewport depending on `hitAea`.
- **`true`**: `50%` progress of the timeline is calculated against the **target element's center**. The edges of the timeline are still calculated against the edges of the source element/viewport depending on `hitArea`.

**Applies to `namedEffect` and `customEffect` only.** Centering needs a resolved target, and a `keyframeEffect` scrub scene resolves none — set it there and it is silently ignored.

---

Expand Down Expand Up @@ -118,9 +120,7 @@ Use pre-built mouse presets from `@wix/motion-presets` that handle 2D mouse trac
type: '[NAMED_EFFECT_TYPE]',
[EFFECT_PROPERTIES]
},
centeredToTarget: [CENTERED_TO_TARGET],
transitionDuration: [TRANSITION_DURATION_MS],
transitionEasing: '[TRANSITION_EASING]'
centeredToTarget: [CENTERED_TO_TARGET]
},
// additional effects targeting other elements can be added here
]
Expand All @@ -135,8 +135,8 @@ Use pre-built mouse presets from `@wix/motion-presets` that handle 2D mouse trac
- `[NAMED_EFFECT_TYPE]` — a registered effect name, or a preset from `@wix/motion-presets` `mouse` library.
- `[EFFECT_PROPERTIES]` — preset-specific options. Refer to motion-presets rules for each preset's available options and their value types. Do NOT guess preset option names or types; omit unknown options and rely on defaults.
- `[CENTERED_TO_TARGET]` — `true` or `false`. See **Centering with `centeredToTarget`** above.
- `[TRANSITION_DURATION_MS]` — optional number. Milliseconds for smoothing (interpolating) between progress updates. The animation does not jump to the new progress value instantly; instead it transitions over this duration. Use to add inertia/lag to the effect, making it feel more physical (e.g. `200`–`600`).
- `[TRANSITION_EASING]` — optional string. CSS easing or named easing from `@wix/motion`. Adds a natural deceleration feel when used with `transitionDuration`.

> `transitionDuration` / `transitionEasing` are **not** available here. They are forwarded only for a `customEffect` payload (see Rule 4).

---

Expand All @@ -160,9 +160,6 @@ Use `keyframeEffect` when the pointer position along a single axis should drive
keyframes: [KEYFRAMES]
},
fill: 'both',
centeredToTarget: [CENTERED_TO_TARGET],
transitionDuration: [TRANSITION_DURATION_MS],
transitionEasing: '[TRANSITION_EASING]',
effectId: '[UNIQUE_EFFECT_ID]'
},
// additional effects targeting other elements can be added here
Expand All @@ -177,11 +174,10 @@ Use `keyframeEffect` when the pointer position along a single axis should drive
- `[AXIS]` — `'x'` (horizontal) or `'y'` (vertical). Defaults to `'y'` when omitted.
- `[EFFECT_NAME]` — unique string name for the keyframe effect.
- `[KEYFRAMES]` — array of CSS keyframe objects (e.g. `[{ transform: 'rotate(-10deg)' }, { transform: 'rotate(0)' }, { transform: 'rotate(10deg)' }]`). Distributed evenly across 0–1 progress: first keyframe = progress 0 (left/top edge), last = progress 1 (right/bottom edge). Any number of keyframes is allowed.
- `[CENTERED_TO_TARGET]` — optional. `true` or `false`. See **Centering with `centeredToTarget`** above. Defaults to `false`.
- `[TRANSITION_DURATION_MS]` — optional. Milliseconds for smoothing between progress updates. See Rule 1 for details.
- `[TRANSITION_EASING]` — optional. CSS easing string or named easing from `@wix/motion`. See Rule 1 for supported values.
- `[UNIQUE_EFFECT_ID]` — optional string identifier.

> A `keyframeEffect` scrub scene resolves no target, so `centeredToTarget` is silently ignored here, and `transitionDuration` / `transitionEasing` are not forwarded either. All three apply only to the payloads noted in Rules 1 and 4. For smoothed, centered pointer motion use a mouse `namedEffect` or a `customEffect`.

---

## Rule 3: Two keyframeEffects with Two Axes and `composite`
Expand Down Expand Up @@ -211,19 +207,15 @@ Use two separate interactions on the same source/target pair — one for `axis:
keyframes: [X_KEYFRAMES]
},
fill: '[FILL_MODE]', // usually 'both'
composite: '[COMPOSITE_OPERATION]',
transitionDuration: [TRANSITION_DURATION_MS],
transitionEasing: '[TRANSITION_EASING]'
composite: '[COMPOSITE_OPERATION]'
},
'[Y_EFFECT_ID]': {
keyframeEffect: {
name: '[Y_EFFECT_NAME]',
keyframes: [Y_KEYFRAMES]
},
fill: '[FILL_MODE]', // usually 'both'
composite: '[COMPOSITE_OPERATION]',
transitionDuration: [TRANSITION_DURATION_MS],
transitionEasing: '[TRANSITION_EASING]'
composite: '[COMPOSITE_OPERATION]'
}
}
}
Expand All @@ -238,8 +230,8 @@ Use two separate interactions on the same source/target pair — one for `axis:
- `[X_KEYFRAMES]` / `[Y_KEYFRAMES]` — arrays of WAAPI keyframe objects for the X-axis and Y-axis effects respectively. Each effect can vary in propertise and keyframes.
- `[COMPOSITE_OPERATION]` — `'add'` or `'accumulate'`. Required when both effects animate `transform` and/or both animate `filter`, so their values combine rather than override. `'add'`: composited transform functions are appended. `'accumulate'`: matching function arguments are summed.
- `[FILL_MODE]` — typically `'both'` to ensure the effect keeps applying after exiting the effect's active range.
- `[TRANSITION_DURATION_MS]` — optional. Milliseconds for smoothing between progress updates. See Rule 1 for details.
- `[TRANSITION_EASING]` — optional. CSS easing function for the smoothing transition. See Rule 1 for supported values.

> As in Rule 2, `transitionDuration` / `transitionEasing` / `centeredToTarget` do not apply to a `keyframeEffect` payload.

---

Expand Down
6 changes: 3 additions & 3 deletions packages/interact/rules/viewprogress.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ For static sites, pre-render CSS via `generate()` at build time — see
- `'cover'` — full visibility span from first pixel entering to last pixel leaving.
- `'entry'` — the phase while the element is entering the viewport.
- `'exit'` — the phase while the element is exiting the viewport.
- `'contain'` — while the element is fully contained in the viewport. Typically used with a `position: sticky` container.
- `'entry-crossing'` — from the element's leading edge entering to its leading edge reaching the opposite side.
- `'exit-crossing'` — from the element's trailing edge reaching the start to its trailing edge leaving.
- `'contain'` — while the element is fully contained by the viewport, or — for an element taller than the viewport — while it fully covers it. This is the phase a `position: sticky` child stays pinned, which is why it pairs with a sticky container.
- `'entry-crossing'` — from the element's leading edge entering to its trailing edge entering.
- `'exit-crossing'` — from the element's leading edge exiting to its trailing edge exiting.
- `[START_PERCENTAGE]` — 0–100, starting point within the named range.
- `[END_PERCENTAGE]` — 0–100, end point within the named range.
- `[EASING_FUNCTION]` - CSS easing string or named easing from `@wix/motion`. Typically `'linear'` for scrolling effects.
Expand Down
Loading