From dab88759b4b6d852e826430a4ea7f693bac4e831 Mon Sep 17 00:00:00 2001 From: ameerf-wix Date: Tue, 20 Oct 2026 02:04:08 +0300 Subject: [PATCH 1/4] feat(motion-presets): export param schemas for presets Port the per-preset `schema` exports from #330 onto the current preset implementations, without the base-presets refactor: - entrance, scroll and ongoing presets export #330's schemas and accept its param names; defaults are read from the schema - mouse presets export schemas for their current params (also attached to the create function, which is what gets registered) - getNames appends `options.suffix` to every keyframe name - generic contract test: implicit defaults equal schema defaults - docs/rules/skill references updated to the new param names Co-Authored-By: Claude Opus 5.5 (1M context) --- README.md | 4 +- packages/interact/README.md | 2 +- .../docs/guides/effects-and-animations.md | 2 +- packages/motion-presets/README.md | 24 +- .../rules/presets/entrance-presets.md | 128 +++++------ .../rules/presets/ongoing-presets.md | 56 ++--- .../rules/presets/presets-main.md | 43 ++-- .../rules/presets/scroll-presets.md | 78 +++---- packages/motion-presets/src/consts.ts | 6 + .../src/library/entrance/ArcIn.ts | 26 ++- .../src/library/entrance/BlurIn.ts | 10 +- .../src/library/entrance/BounceIn.ts | 37 +++- .../src/library/entrance/CurveIn.ts | 36 +-- .../src/library/entrance/DropIn.ts | 12 +- .../src/library/entrance/ExpandIn.ts | 26 ++- .../src/library/entrance/FadeIn.ts | 6 +- .../src/library/entrance/FlipIn.ts | 35 ++- .../src/library/entrance/FloatIn.ts | 14 +- .../src/library/entrance/FoldIn.ts | 22 +- .../src/library/entrance/GlideIn.ts | 25 ++- .../src/library/entrance/RevealIn.ts | 14 +- .../src/library/entrance/ShapeIn.ts | 11 +- .../src/library/entrance/ShuttersIn.ts | 20 +- .../src/library/entrance/SlideIn.ts | 19 +- .../src/library/entrance/SpinIn.ts | 18 +- .../src/library/entrance/TiltIn.ts | 25 ++- .../src/library/entrance/TurnIn.ts | 15 +- .../src/library/entrance/WinkIn.ts | 15 +- .../src/library/entrance/test/ArcIn.spec.ts | 44 ++-- .../src/library/entrance/test/BlurIn.spec.ts | 15 ++ .../library/entrance/test/BounceIn.spec.ts | 24 +- .../src/library/entrance/test/CurveIn.spec.ts | 12 +- .../src/library/entrance/test/FlipIn.spec.ts | 8 +- .../src/library/entrance/test/FloatIn.spec.ts | 4 +- .../src/library/entrance/test/FoldIn.spec.ts | 8 +- .../src/library/entrance/test/GlideIn.spec.ts | 16 +- .../library/entrance/test/RevealIn.spec.ts | 4 +- .../src/library/entrance/test/ShapeIn.spec.ts | 15 ++ .../library/entrance/test/ShuttersIn.spec.ts | 28 +-- .../src/library/entrance/test/SlideIn.spec.ts | 4 +- .../src/library/entrance/test/SpinIn.spec.ts | 4 +- .../src/library/entrance/test/TiltIn.spec.ts | 12 +- .../src/library/entrance/test/TurnIn.spec.ts | 8 +- .../src/library/mouse/AiryMouse.ts | 20 +- .../src/library/mouse/BlobMouse.ts | 12 +- .../src/library/mouse/BlurMouse.ts | 24 +- .../src/library/mouse/BounceMouse.ts | 12 +- .../src/library/mouse/ScaleMouse.ts | 19 +- .../src/library/mouse/SkewMouse.ts | 20 +- .../src/library/mouse/SpinMouse.ts | 12 +- .../src/library/mouse/SwivelMouse.ts | 19 +- .../src/library/mouse/Tilt3DMouse.ts | 14 +- .../src/library/mouse/Track3DMouse.ts | 23 +- .../src/library/mouse/TrackMouse.ts | 18 +- .../src/library/ongoing/Bounce.ts | 25 ++- .../src/library/ongoing/Breathe.ts | 27 ++- .../src/library/ongoing/Cross.ts | 17 +- .../motion-presets/src/library/ongoing/DVD.ts | 4 +- .../src/library/ongoing/Flash.ts | 12 +- .../src/library/ongoing/Flip.ts | 22 +- .../src/library/ongoing/Fold.ts | 36 +-- .../src/library/ongoing/Jello.ts | 22 +- .../src/library/ongoing/Poke.ts | 40 +++- .../src/library/ongoing/Pulse.ts | 20 +- .../src/library/ongoing/Rubber.ts | 22 +- .../src/library/ongoing/Spin.ts | 19 +- .../src/library/ongoing/Swing.ts | 22 +- .../src/library/ongoing/Wiggle.ts | 34 ++- .../src/library/ongoing/test/Bounce.spec.ts | 24 +- .../src/library/ongoing/test/Breathe.spec.ts | 8 +- .../src/library/ongoing/test/Fold.spec.ts | 36 +-- .../src/library/ongoing/test/Jello.spec.ts | 24 +- .../src/library/ongoing/test/Poke.spec.ts | 4 +- .../src/library/ongoing/test/Pulse.spec.ts | 12 +- .../src/library/ongoing/test/Rubber.spec.ts | 8 +- .../src/library/ongoing/test/Swing.spec.ts | 20 +- .../src/library/ongoing/test/Wiggle.spec.ts | 10 +- .../src/library/scroll/ArcScroll.ts | 21 +- .../src/library/scroll/BlurScroll.ts | 13 +- .../src/library/scroll/FadeScroll.ts | 13 +- .../src/library/scroll/FlipScroll.ts | 29 ++- .../src/library/scroll/GrowScroll.ts | 31 ++- .../src/library/scroll/MoveScroll.ts | 24 +- .../src/library/scroll/PanScroll.ts | 26 ++- .../src/library/scroll/ParallaxScroll.ts | 25 ++- .../src/library/scroll/RevealScroll.ts | 24 +- .../src/library/scroll/ShapeScroll.ts | 21 +- .../src/library/scroll/ShrinkScroll.ts | 31 ++- .../src/library/scroll/ShuttersScroll.ts | 28 ++- .../src/library/scroll/SkewPanScroll.ts | 24 +- .../src/library/scroll/SlideScroll.ts | 24 +- .../src/library/scroll/Spin3dScroll.ts | 24 +- .../src/library/scroll/SpinScroll.ts | 25 ++- .../src/library/scroll/StretchScroll.ts | 14 +- .../src/library/scroll/TiltScroll.ts | 35 ++- .../src/library/scroll/TurnScroll.ts | 38 +++- .../library/scroll/test/FlipScroll.spec.ts | 8 +- .../library/scroll/test/GrowScroll.spec.ts | 44 ++-- .../library/scroll/test/MoveScroll.spec.ts | 16 +- .../src/library/scroll/test/PanScroll.spec.ts | 12 +- .../scroll/test/ParallaxScroll.spec.ts | 21 +- .../library/scroll/test/RevealScroll.spec.ts | 4 +- .../library/scroll/test/ShapeScroll.spec.ts | 4 +- .../library/scroll/test/ShrinkScroll.spec.ts | 14 +- .../library/scroll/test/SkewPanScroll.spec.ts | 14 +- .../library/scroll/test/SlideScroll.spec.ts | 12 +- .../library/scroll/test/Spin3dScroll.spec.ts | 12 +- .../library/scroll/test/SpinScroll.spec.ts | 11 + .../library/scroll/test/TiltScroll.spec.ts | 24 +- .../library/scroll/test/TurnScroll.spec.ts | 8 +- .../motion-presets/src/test/presets.spec.ts | 128 +++++++++++ packages/motion-presets/src/types.ts | 206 +++++++++--------- packages/motion-presets/src/utils.ts | 9 +- skills/interactor/SKILL.md | 2 +- skills/interactor/evals/evals.json | 4 +- .../examples/text-animations/Ripple_Hover.md | 4 +- skills/interactor/references/presets.md | 142 ++++++------ skills/interactor/references/triggers.md | 6 +- 118 files changed, 1752 insertions(+), 1019 deletions(-) create mode 100644 packages/motion-presets/src/test/presets.spec.ts diff --git a/README.md b/README.md index 3c6ea777..efcfb9ea 100644 --- a/README.md +++ b/README.md @@ -221,7 +221,7 @@ const config: InteractConfig = { 'card-float': { duration: 900, easing: 'cubic-bezier(0.22, 1, 0.36, 1)', - namedEffect: { type: 'FloatIn', direction: 'bottom' }, + namedEffect: { type: 'FloatIn', from: 'bottom' }, }, }, }; @@ -269,7 +269,7 @@ const config: InteractConfig = { trigger: 'viewProgress', effects: [ { - namedEffect: { type: 'ParallaxScroll', parallaxFactor: 0.5 }, + namedEffect: { type: 'ParallaxScroll', speed: 0.7 }, rangeStart: { name: 'cover', offset: { unit: 'percentage', value: 0 } }, rangeEnd: { name: 'cover', offset: { unit: 'percentage', value: 100 } }, easing: 'linear', diff --git a/packages/interact/README.md b/packages/interact/README.md index ffe8a4dc..d04ae375 100644 --- a/packages/interact/README.md +++ b/packages/interact/README.md @@ -248,7 +248,7 @@ Each example is a complete `InteractConfig` — pass it to `Interact.create(conf 'float-in': { duration: 800, easing: 'ease-out', - namedEffect: { type: 'FloatIn', direction: 'bottom' }, + namedEffect: { type: 'FloatIn', from: 'bottom' }, }, }, } diff --git a/packages/interact/docs/guides/effects-and-animations.md b/packages/interact/docs/guides/effects-and-animations.md index 03e6208e..9df1b6d4 100644 --- a/packages/interact/docs/guides/effects-and-animations.md +++ b/packages/interact/docs/guides/effects-and-animations.md @@ -378,7 +378,7 @@ Effect applies to different elements: key: 'mobile-menu', // Different element namedEffect: { type: 'SlideIn', - direction: 'down' + from: 'top' }, duration: 300 }, diff --git a/packages/motion-presets/README.md b/packages/motion-presets/README.md index b8dbbea0..fc102d5a 100644 --- a/packages/motion-presets/README.md +++ b/packages/motion-presets/README.md @@ -174,28 +174,28 @@ For selection by tone and atmosphere (playful, elegant, bold, soft, dramatic, mo ## Parameter Conventions -### `direction` +### `from`, `direction` and `pivot` -The `direction` parameter accepts different value sets depending on the preset: +Entrance presets use `from` — the side the element comes from. All other presets use `direction` — where the motion goes. `pivot` names the edge or corner the element rotates or scales around. The accepted values depend on the preset: -| Values | Example Presets | -| ---------------------------------------- | ----------------------------------------------------------------------- | -| `'top' \| 'right' \| 'bottom' \| 'left'` | SlideIn, FloatIn, FlipIn, FoldIn | -| `'horizontal' \| 'vertical'` | WinkIn, ArcScroll, FlipScroll, Flip | -| `'clockwise' \| 'counter-clockwise'` | SpinIn, SpinScroll, Spin | -| `0–360` (degrees, number) | GlideIn, ExpandIn, MoveScroll | -| Corner values | TurnIn (`'top-left'`, `'top-right'`, `'bottom-left'`, `'bottom-right'`) | +| Values | Example Presets | +| ---------------------------------------- | -------------------------------------------------------------------------------- | +| `'top' \| 'right' \| 'bottom' \| 'left'` | `from`: SlideIn, FloatIn, RevealIn; `direction`: SlideScroll; `pivot`: FoldIn | +| `'horizontal' \| 'vertical'` | `direction`: FlipIn, WinkIn, ArcScroll, FlipScroll, Flip | +| `'clockwise' \| 'counter-clockwise'` | `direction`: SpinIn, SpinScroll, Spin | +| `0–360` (degrees, number) | `from`: GlideIn, ExpandIn; `direction`: MoveScroll | +| Corner values | `pivot`: TurnIn (`'top-left'`, `'top-right'`, `'bottom-left'`, `'bottom-right'`) | -**Angle convention:** `0° = right (east)`, angles increase counter-clockwise. +**Angle convention:** CSS convention — `0° = right`, `90° = bottom`, `180° = left`, `270° = top`. ### Distance units -Prefer the `{ value, unit }` object notation: +Movement lengths are named `travel`. Prefer the `{ value, unit }` object notation: ```typescript namedEffect: { type: 'GlideIn', - distance: { value: 120, unit: 'px' }, + travel: { value: 120, unit: 'px' }, } ``` diff --git a/packages/motion-presets/rules/presets/entrance-presets.md b/packages/motion-presets/rules/presets/entrance-presets.md index 18bffda6..2b9bc91d 100644 --- a/packages/motion-presets/rules/presets/entrance-presets.md +++ b/packages/motion-presets/rules/presets/entrance-presets.md @@ -53,12 +53,12 @@ Visual: Element enters along a 3D arc path, rotating into view with depth motion Parameters: -- `direction`: 'top' | 'right' | 'bottom' | 'left' (default: `'right'`) -- `depth`: UnitLengthPercentage — Z translation distance (default: `{ value: 200, unit: 'px' }`) +- `from`: 'top' | 'right' | 'bottom' | 'left' — the side the element comes from (default: `'right'`) +- `depth`: UnitLengthPercentage — Z translation distance (default: `{ value: 100, unit: 'px' }`) - `perspective`: number — 3D perspective in px (default: `800`) ```typescript -{ type: 'ArcIn', direction: 'bottom' } +{ type: 'ArcIn', from: 'bottom' } ``` --- @@ -83,12 +83,12 @@ Visual: Element bounces into place from a direction with an elastic multi-step c Parameters: -- `direction`: 'top' | 'right' | 'bottom' | 'left' | 'center' (default: `'bottom'`) -- `distanceFactor`: number — bounce distance multiplier (default: `1`) -- `perspective`: number — 3D perspective for center direction (default: `800`) +- `from`: 'top' | 'right' | 'bottom' | 'left' | 'back' (default: `'bottom'`) +- `travel`: UnitLengthPercentage — bounce distance (default: `{ value: 50, unit: 'px' }`) +- `perspective`: number — 3D perspective for `'back'` (default: `800`) ```typescript -{ type: 'BounceIn', direction: 'left', distanceFactor: 2 } +{ type: 'BounceIn', from: 'left', travel: { value: 100, unit: 'px' } } ``` --- @@ -99,12 +99,12 @@ Visual: Element curves in with a 180° rotation and depth motion in a 3D space, Parameters: -- `direction`: 'left' | 'right' | 'pseudoLeft' | 'pseudoRight' (default: `'right'`) -- `depth`: UnitLengthPercentage — Z translation distance (default: `{ value: 300, unit: 'px' }`) +- `from`: 'top' | 'right' | 'bottom' | 'left' (default: `'right'`) +- `depth`: UnitLengthPercentage — Z translation distance (default: `{ value: 900, unit: 'px' }`) - `perspective`: number — 3D perspective in px (default: `200`) ```typescript -{ type: 'CurveIn', direction: 'left' } +{ type: 'CurveIn', from: 'left' } ``` --- @@ -115,10 +115,10 @@ Visual: Element shrinks down from a larger size to its final scale. Parameters: -- `initialScale`: number — starting scale before settling to 1 (default: `1.6`) +- `scale`: number — starting scale before settling to 1, min 1 (default: `1.6`) ```typescript -{ type: 'DropIn', initialScale: 2 } +{ type: 'DropIn', scale: 2 } ``` --- @@ -129,12 +129,12 @@ Visual: Element expands from a point in a given direction, scaling from small to Parameters: -- `initialScale`: number — starting scale, 0 = invisible (default: `0`) -- `direction`: number | 'top' | 'right' | 'bottom' | 'left' — angle or cardinal direction (default: `90` / top). 0° = right, 90° = top, 180° = left, 270° = bottom -- `distance`: UnitLengthPercentage — how far the element travels (default: `{ value: 120, unit: 'percentage' }`) +- `scale`: number — 0 to 1, starting scale, 0 = invisible (default: `0`) +- `from`: number | 'top' | 'right' | 'bottom' | 'left' — angle or side it comes from (default: `270` / top). 0° = right, 90° = bottom, 180° = left, 270° = top +- `travel`: UnitLengthPercentage — how far the element travels (default: `{ value: 120, unit: 'percentage' }`) ```typescript -{ type: 'ExpandIn', direction: 'bottom', initialScale: 0.5 } +{ type: 'ExpandIn', from: 'bottom', scale: 0.5 } ``` --- @@ -145,12 +145,12 @@ Visual: Element flips into view with a 3D rotation around the X or Y axis. Parameters: -- `direction`: 'top' | 'right' | 'bottom' | 'left' (default: `'top'`) -- `initialRotate`: number — starting rotation in degrees (default: `90`) +- `direction`: 'horizontal' | 'vertical' — flip axis (default: `'vertical'`) +- `angle`: number — starting rotation in degrees; negative flips the other way (default: `90`) - `perspective`: number — 3D perspective in px (default: `800`) ```typescript -{ type: 'FlipIn', direction: 'right', initialRotate: 180 } +{ type: 'FlipIn', direction: 'horizontal', angle: 180 } ``` --- @@ -161,10 +161,10 @@ Visual: Element drifts gently into place from a direction with a fade-in. Parameters: -- `direction`: 'top' | 'right' | 'bottom' | 'left' (default: `'left'`) +- `from`: 'top' | 'right' | 'bottom' | 'left' (default: `'left'`) ```typescript -{ type: 'FloatIn', direction: 'bottom' } +{ type: 'FloatIn', from: 'bottom' } ``` --- @@ -175,12 +175,12 @@ Visual: Element unfolds from an edge, rotating around an axis at the edge as if Parameters: -- `direction`: 'top' | 'right' | 'bottom' | 'left' (default: `'top'`) -- `initialRotate`: number — starting fold angle in degrees (default: `90`) +- `pivot`: 'top' | 'right' | 'bottom' | 'left' — the hinged edge (default: `'top'`) +- `angle`: number — starting fold angle in degrees (default: `-90`) - `perspective`: number — 3D perspective in px (default: `800`) ```typescript -{ type: 'FoldIn', direction: 'left', initialRotate: 60 } +{ type: 'FoldIn', pivot: 'left', angle: -60 } ``` --- @@ -191,11 +191,11 @@ Visual: Element glides in smoothly from off-screen along a direction. Parameters: -- `direction`: number | 'top' | 'right' | 'bottom' | 'left' — angle or cardinal (default: `180` / left). 0° = right, 90° = top, 180° = left, 270° = bottom -- `distance`: UnitLengthPercentage | 'top' | 'right' | 'bottom' | 'left' — travel distance or edge keyword (default: `{ value: 100, unit: 'percentage' }`) +- `from`: number | 'top' | 'right' | 'bottom' | 'left' — angle or side it comes from (default: `180` / left). 0° = right, 90° = bottom, 180° = left, 270° = top +- `travel`: UnitLengthPercentage — travel distance (default: `{ value: 100, unit: 'percentage' }`) ```typescript -{ type: 'GlideIn', direction: 270, distance: { value: 200, unit: 'px' } } +{ type: 'GlideIn', from: 90, travel: { value: 200, unit: 'px' } } ``` --- @@ -206,10 +206,10 @@ Visual: Element is progressively revealed by an expanding clip-path from one edg Parameters: -- `direction`: 'top' | 'right' | 'bottom' | 'left' (default: `'left'`) +- `from`: 'top' | 'right' | 'bottom' | 'left' — the edge the reveal starts from (default: `'left'`) ```typescript -{ type: 'RevealIn', direction: 'bottom' } +{ type: 'RevealIn', from: 'bottom' } ``` --- @@ -234,12 +234,12 @@ Visual: Element is revealed through multiple shutter-like strips that open in se Parameters: -- `direction`: 'top' | 'right' | 'bottom' | 'left' (default: `'right'`) -- `shutters`: number — number of shutter segments (default: `12`) +- `from`: 'top' | 'right' | 'bottom' | 'left' — the side the shutters open from (default: `'left'`) +- `shutters`: number — number of shutter segments, min 1 (default: `12`) - `staggered`: boolean — whether shutters open in a staggered pattern (default: `true`) ```typescript -{ type: 'ShuttersIn', direction: 'top', shutters: 8 } +{ type: 'ShuttersIn', from: 'bottom', shutters: 8 } ``` --- @@ -250,11 +250,11 @@ Visual: Element slides in from one side while being revealed with a clip-path ma Parameters: -- `direction`: 'top' | 'right' | 'bottom' | 'left' (default: `'left'`) -- `initialTranslate`: number — 0 to 1, how far off-screen the element starts (default: `1`) +- `from`: 'top' | 'right' | 'bottom' | 'left' (default: `'left'`) +- `start`: number — 0 to 1, how far into the slide the element starts; 0 = fully offset (default: `0`) ```typescript -{ type: 'SlideIn', direction: 'right', initialTranslate: 0.5 } +{ type: 'SlideIn', from: 'right', start: 0.5 } ``` --- @@ -267,7 +267,7 @@ Parameters: - `spins`: number — number of full rotations (default: `0.5`) - `direction`: 'clockwise' | 'counter-clockwise' (default: `'clockwise'`) -- `initialScale`: number — starting scale, 0 = invisible (default: `0`) +- `scale`: number — starting scale, 0 = invisible (default: `0`) ```typescript { type: 'SpinIn', spins: 1, direction: 'counter-clockwise' } @@ -281,12 +281,12 @@ Visual: Element tilts in from the side with 3D rotation and a clip-path reveal. Parameters: -- `direction`: 'left' | 'right' (default: `'left'`) -- `depth`: UnitLengthPercentage — Z translation distance (default: `{ value: 200, unit: 'px' }`) +- `from`: 'left' | 'right' (default: `'left'`) +- `depth`: UnitLengthPercentage — Z translation distance (default: `{ value: 100, unit: 'px' }`) - `perspective`: number — 3D perspective in px (default: `800`) ```typescript -{ type: 'TiltIn', direction: 'right' } +{ type: 'TiltIn', from: 'right' } ``` --- @@ -297,10 +297,10 @@ Visual: Element rotates into view around a corner pivot point. Parameters: -- `direction`: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' (default: `'top-left'`) +- `pivot`: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' (default: `'top-left'`) ```typescript -{ type: 'TurnIn', direction: 'bottom-right' } +{ type: 'TurnIn', pivot: 'bottom-right' } ``` --- @@ -325,41 +325,41 @@ Some preset parameters are exposed but their defaults have been tuned for good v ### 3D Perspective -| Preset | Parameter | Default | Range | -| ----------------- | ------------- | ------- | -------- | -| ArcIn | `perspective` | 800 | 200-2000 | -| TiltIn | `perspective` | 800 | 200-2000 | -| FoldIn | `perspective` | 800 | 200-2000 | -| FlipIn | `perspective` | 800 | 200-2000 | -| CurveIn | `perspective` | 200 | 100-1000 | -| BounceIn (center) | `perspective` | 800 | 200-2000 | +| Preset | Parameter | Default | Range | +| --------------- | ------------- | ------- | -------- | +| ArcIn | `perspective` | 800 | 200-2000 | +| TiltIn | `perspective` | 800 | 200-2000 | +| FoldIn | `perspective` | 800 | 200-2000 | +| FlipIn | `perspective` | 800 | 200-2000 | +| CurveIn | `perspective` | 200 | 100-1000 | +| BounceIn (back) | `perspective` | 800 | 200-2000 | ### Depth (Z Translation) | Preset | Parameter | Default | Notes | | ------- | --------- | ------- | ---------------------- | -| ArcIn | `depth` | 200px | Z translation distance | -| CurveIn | `depth` | 300px | Z translation distance | -| TiltIn | `depth` | 200px | Z translation distance | +| ArcIn | `depth` | 100px | Z translation distance | +| CurveIn | `depth` | 900px | Z translation distance | +| TiltIn | `depth` | 100px | Z translation distance | ## Intensity Value Guide Tested values for different intensity levels. When a user asks for "soft", "subtle", "medium", or "hard"/"dramatic" motion, use these as guidelines. -| Preset | Parameter | Subtle/Soft | Medium | Dramatic/Hard | -| -------- | ---------------- | ----------- | ---------- | ------------- | -| ArcIn | easing | sineOut | cubicInOut | quintInOut | -| BlurIn | blur | 6px | 25px | 50px | -| BounceIn | distanceFactor | 1 | 2 | 3 | -| DropIn | initialScale | 1.2 | 1.6 | 2 | -| FlipIn | initialRotate | 35° | 60° | 90° | -| FoldIn | initialRotate | 35° | 60° | 90° | -| ExpandIn | initialScale | 0.8 | 0.6 | 0 | -| SlideIn | initialTranslate | 0.2 | 0.8 | 1 | -| SpinIn | initialScale | 1 | 0.6 | 0 | +| Preset | Parameter | Subtle/Soft | Medium | Dramatic/Hard | +| -------- | --------- | ----------- | ---------- | ------------- | +| ArcIn | easing | sineOut | cubicInOut | quintInOut | +| BlurIn | blur | 6px | 25px | 50px | +| BounceIn | travel | 50px | 100px | 150px | +| DropIn | scale | 1.2 | 1.6 | 2 | +| FlipIn | angle | 35° | 60° | 90° | +| FoldIn | angle | -35° | -60° | -90° | +| ExpandIn | scale | 0.8 | 0.6 | 0 | +| SlideIn | start | 0.8 | 0.2 | 0 | +| SpinIn | scale | 1 | 0.6 | 0 | ### Intensity Usage Example When a user asks: "I want a subtle flip entrance" -Suggest: `{ type: 'FlipIn', initialRotate: 35 }` +Suggest: `{ type: 'FlipIn', angle: 35 }` diff --git a/packages/motion-presets/rules/presets/ongoing-presets.md b/packages/motion-presets/rules/presets/ongoing-presets.md index 3219ba03..fbf47317 100644 --- a/packages/motion-presets/rules/presets/ongoing-presets.md +++ b/packages/motion-presets/rules/presets/ongoing-presets.md @@ -32,11 +32,11 @@ Visual: Element bounces up and down with a natural multi-step curve, like a ball Parameters: -- `intensity`: number — 0 to 1, maps to bounce height factor 1–3 (default: `0`) +- `travel`: UnitLengthPercentage — bounce height at the peak (default: `{ value: 49, unit: 'px' }`) - `iterationDelay`: number — idle time in ms after each iteration cycle (default: `0`) ```typescript -{ type: 'Bounce', intensity: 0.5, iterationDelay: 500 } +{ type: 'Bounce', travel: { value: 98, unit: 'px' }, iterationDelay: 500 } ``` --- @@ -48,12 +48,12 @@ Visual: Element gently moves back and forth along an axis, like a breathing moti Parameters: - `direction`: 'vertical' | 'horizontal' | 'center' (default: `'vertical'`) -- `distance`: UnitLengthPercentage — movement distance (default: `{ value: 25, unit: 'px' }`) +- `travel`: UnitLengthPercentage — movement distance (default: `{ value: 25, unit: 'px' }`) - `perspective`: number — 3D perspective for center direction (default: `800`) - `iterationDelay`: number — idle time in ms after each iteration cycle (default: `0`) ```typescript -{ type: 'Breathe', direction: 'horizontal', distance: { value: 15, unit: 'px' } } +{ type: 'Breathe', direction: 'horizontal', travel: { value: 15, unit: 'px' } } ``` --- @@ -109,12 +109,13 @@ Visual: Element folds at an edge using 3D rotation, like a page turning back and Parameters: -- `direction`: 'top' | 'right' | 'bottom' | 'left' (default: `'top'`) -- `angle`: number — fold angle in degrees (default: `15`) +- `pivot`: 'top' | 'right' | 'bottom' | 'left' — the folding edge (default: `'top'`) +- `angle`: number — fold angle in degrees; always folds towards the viewer first (default: `15`) +- `perspective`: number — 3D perspective in px (default: `800`) - `iterationDelay`: number — idle time in ms after each iteration cycle (default: `0`) ```typescript -{ type: 'Fold', direction: 'right', angle: 30 } +{ type: 'Fold', pivot: 'right', angle: 30 } ``` --- @@ -125,11 +126,11 @@ Visual: Element wobbles with a skew-based jello-like deformation. Parameters: -- `intensity`: number — 0 to 1, maps to skew factor 1–4 (default: `0.25`) +- `skew`: number — peak skew in degrees (default: `12.25`) - `iterationDelay`: number — idle time in ms after each iteration cycle (default: `0`) ```typescript -{ type: 'Jello', intensity: 0.5 } +{ type: 'Jello', skew: 17.5 } ``` --- @@ -141,11 +142,11 @@ Visual: Element makes two short, sharp translates in a direction back and forth, Parameters: - `direction`: 'top' | 'right' | 'bottom' | 'left' (default: `'right'`) -- `intensity`: number — 0 to 1, maps to poke strength factor 1–4 (default: `0.5`) +- `travel`: UnitLengthPercentage — poke distance at the peak (default: `{ value: 62.5, unit: 'px' }`) - `iterationDelay`: number — idle time in ms after each iteration cycle (default: `0`) ```typescript -{ type: 'Poke', direction: 'left', intensity: 0.8 } +{ type: 'Poke', direction: 'left', travel: { value: 85, unit: 'px' } } ``` --- @@ -156,11 +157,11 @@ Visual: Element pulses by subtly scaling up and down. Parameters: -- `intensity`: number — 0 to 1, adjusts the scale range (default: `0`) +- `scale`: number — scale at the deeper of the two beats (default: `0.93`) - `iterationDelay`: number — idle time in ms after each iteration cycle (default: `0`) ```typescript -{ type: 'Pulse', intensity: 0.5 } +{ type: 'Pulse', scale: 0.87 } ``` --- @@ -171,11 +172,11 @@ Visual: Element stretches non-uniformly on X and Y axes, creating a rubber-band Parameters: -- `intensity`: number — 0 to 1, adjusts the stretch amplitude (default: `0.5`) +- `stretch`: number — -1 to 1, the stretch amplitude (default: `0.1`) - `iterationDelay`: number — idle time in ms after each iteration cycle (default: `0`) ```typescript -{ type: 'Rubber', intensity: 0.8 } +{ type: 'Rubber', stretch: 0.13 } ``` --- @@ -201,12 +202,12 @@ Visual: Element swings like a pendulum from a pivot at one edge. Parameters: -- `direction`: 'top' | 'right' | 'bottom' | 'left' — swing pivot edge (default: `'top'`) -- `swing`: number — maximum swing angle in degrees (default: `20`) +- `pivot`: 'top' | 'right' | 'bottom' | 'left' — swing pivot edge (default: `'top'`) +- `angle`: number — maximum swing angle in degrees (default: `20`) - `iterationDelay`: number — idle time in ms after each iteration cycle (default: `0`) ```typescript -{ type: 'Swing', swing: 40, direction: 'right' } +{ type: 'Swing', angle: 40, pivot: 'right' } ``` --- @@ -217,11 +218,12 @@ Visual: Element shakes with combined rotation and vertical translation. Parameters: -- `intensity`: number — 0 to 1, maps to wiggle strength factor 1–4 (default: `0.5`) +- `angle`: number — rotation in degrees at the first swing (default: `25`) +- `travel`: LengthInput — how far the element lifts during the first swing (default: `25px`) - `iterationDelay`: number — idle time in ms after each iteration cycle (default: `0`) ```typescript -{ type: 'Wiggle', intensity: 0.8 } +{ type: 'Wiggle', angle: 34, travel: '34px' } ``` --- @@ -232,11 +234,11 @@ Tested values for different intensity levels. When a user asks for "soft", "subt | Preset | Parameter | Subtle/Soft | Medium | Dramatic/Hard | | ------ | --------- | ----------- | ------ | ------------- | -| Bounce | intensity | 0 | 0.5 | 1 | +| Bounce | travel | 49px | 98px | 147px | | Fold | angle | 15° | 30° | 45° | -| Jello | intensity | 0 | 0.33 | 1 | -| Poke | intensity | 0 | 0.33 | 1 | -| Pulse | intensity | 0 | 0.5 | 1 | -| Rubber | intensity | 0 | 0.5 | 1 | -| Swing | swing | 20° | 40° | 60° | -| Wiggle | intensity | 0 | 0.33 | 1 | +| Jello | skew | 7° | 14° | 28° | +| Poke | travel | 25px | 50px | 100px | +| Pulse | scale | 0.93 | 0.87 | 0.81 | +| Rubber | stretch | 0.05 | 0.1 | 0.15 | +| Swing | angle | 20° | 40° | 60° | +| Wiggle | angle | 10° | 20° | 40° | diff --git a/packages/motion-presets/rules/presets/presets-main.md b/packages/motion-presets/rules/presets/presets-main.md index b962afdb..93d8aa08 100644 --- a/packages/motion-presets/rules/presets/presets-main.md +++ b/packages/motion-presets/rules/presets/presets-main.md @@ -80,45 +80,44 @@ These are set on the effect configuration level, not on the preset itself: ### Overloaded Parameter Names -The `direction` parameter accepts different values depending on the preset: - -| Meaning | Accepted Values | Presets | -| ------------------ | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | -| Cardinal | 'top', 'right', 'bottom', 'left' | FlipIn, FoldIn, SlideIn, FloatIn, RevealIn, ShuttersIn, Poke, Swing, Fold, RevealScroll, ShuttersScroll, SlideScroll | -| Cardinal + center | 'top', 'right', 'bottom', 'left', 'center' | BounceIn | -| Two sides | 'left', 'right' | TiltIn, PanScroll, SkewPanScroll, TiltScroll, TurnScroll | -| Two sides + pseudo | 'left', 'right', 'pseudoLeft', 'pseudoRight' | CurveIn | -| Corner | 'top-left', 'top-right', 'bottom-left', 'bottom-right' | TurnIn | -| Eight directions | 4 cardinal + 4 diagonal | Cross | -| Nine directions | 4 cardinal + 4 diagonal + 'center' | GrowScroll, ShrinkScroll | -| Axis | 'horizontal', 'vertical' | WinkIn, ArcScroll, FlipScroll, Flip | -| Axis + center | 'horizontal', 'vertical', 'center' | Breathe | -| Rotation | 'clockwise', 'counter-clockwise' | SpinIn, SpinScroll, Spin | -| Angle (number) | 0–360 (0° = right, 90° = top, 180° = left, 270° = bottom) | GlideIn, ExpandIn | +Entrance presets use `from` — the side the element comes from. All other presets use `direction` — where the motion goes. `pivot` names the edge, corner or point the element rotates or scales around. Each accepts different values depending on the preset: + +| Meaning | Accepted Values | Presets | +| ---------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Cardinal | 'top', 'right', 'bottom', 'left' | `from`: ArcIn, CurveIn, FloatIn, RevealIn, ShuttersIn, SlideIn; `direction`: Poke, RevealScroll, ShuttersScroll, SlideScroll; `pivot`: FoldIn, Fold, Swing | +| Cardinal + back | 'top', 'right', 'bottom', 'left', 'back' | `from`: BounceIn | +| Two sides | 'left', 'right' | `from`: TiltIn; `direction`: PanScroll, SkewPanScroll, TurnScroll | +| Corner | 'top-left', 'top-right', 'bottom-left', 'bottom-right' | `pivot`: TurnIn | +| Eight directions | 4 cardinal + 4 diagonal | `direction`: Cross | +| Nine directions | 4 cardinal + 4 diagonal + 'center' | `pivot`: GrowScroll, ShrinkScroll | +| Axis | 'horizontal', 'vertical' | `direction`: FlipIn, WinkIn, ArcScroll, FlipScroll, Flip | +| Axis + center | 'horizontal', 'vertical', 'center' | `direction`: Breathe | +| Rotation | 'clockwise', 'counter-clockwise' | `direction`: SpinIn, SpinScroll, TiltScroll, Spin; `spin`: TurnScroll | +| Angle (number) | 0–360 (0° = right, 90° = bottom, 180° = left, 270° = top) | `from`: GlideIn, ExpandIn (also accept cardinal keywords); `direction`: MoveScroll | ### Using Units -Interact supports both a CSSUnitValue-style object (e.g., `distance: { value: 120, unit: 'px' }`, mapped to the internal type `UnitLengthPercentage`) and flat string values (e.g., `distance: '120px'`). +Entrance, scroll and ongoing movement lengths are named `travel` (PanScroll keeps `distance`; `depth` is Z translation). Interact supports both a CSSUnitValue-style object (e.g., `travel: { value: 120, unit: 'px' }`, mapped to the internal type `UnitLengthPercentage`) and flat string values (e.g., `travel: '120px'`). Prefer the object notation. Be consistent within a configuration — use one format, not both. ### Coordinate System -**Standard:** 0° = right (east), angles increase counter-clockwise +**Standard:** CSS convention — 0° = right (east), angles increase clockwise - 0° = right (east) -- 90° = top (north) +- 90° = bottom (south) - 180° = left (west) -- 270° = bottom (south) +- 270° = top (north) ### Distance Units Supported unit types: `px`, `em`, `rem`, `vh`, `vw`, `vmin`, `vmax`, `percentage` ```typescript -distance: { value: 120, unit: 'px' } // pixels -distance: { value: 50, unit: 'percentage' } // percentage -distance: { value: 10, unit: 'vh' } // viewport height +travel: { value: 120, unit: 'px' } // pixels +travel: { value: 50, unit: 'percentage' } // percentage +travel: { value: 10, unit: 'vh' } // viewport height ``` ### CSS Custom Properties diff --git a/packages/motion-presets/rules/presets/scroll-presets.md b/packages/motion-presets/rules/presets/scroll-presets.md index 5f3fcf1b..d6f2e6da 100644 --- a/packages/motion-presets/rules/presets/scroll-presets.md +++ b/packages/motion-presets/rules/presets/scroll-presets.md @@ -97,12 +97,12 @@ Visual: Element performs a 3D flip rotation as it scrolls. Parameters: - `direction`: 'vertical' | 'horizontal' (default: `'horizontal'`) -- `rotate`: number — rotation amount in degrees (default: `240`) +- `angle`: number — rotation amount in degrees (default: `240`) - `range`: 'in' | 'out' | 'continuous' (default: `'continuous'`) - `perspective`: number — 3D perspective in px (default: `800`) ```typescript -{ type: 'FlipScroll', rotate: 360, direction: 'vertical' } +{ type: 'FlipScroll', angle: 360, direction: 'vertical' } ``` --- @@ -113,13 +113,13 @@ Visual: Element scales up from a direction as it scrolls into or out of view. Parameters: -- `direction`: EffectNineDirections — origin point for scaling (default: `'center'`) +- `pivot`: EffectNineDirections — origin point for scaling (default: `'center'`) - `range`: 'in' | 'out' | 'continuous' (default: `'in'`) -- `scale`: number — starting/ending scale (default: `0` for 'in', `4` for 'out') -- `speed`: number — vertical movement factor (default: `0`) +- `scale`: number — starting scale on 'in', ending scale otherwise; inverted to below 1 for 'in' and above 1 otherwise (default: `0.25`, i.e. `4` for 'out') +- `speed`: number — approximate visual speed relative to the scroll, 1 = moves with the page (default: `1`) ```typescript -{ type: 'GrowScroll', direction: 'top-left', scale: 0.5 } +{ type: 'GrowScroll', pivot: 'top-left', scale: 0.5 } ``` --- @@ -130,12 +130,12 @@ Visual: Element translates along an angle for a given distance as it scrolls. Parameters: -- `angle`: number — movement angle in degrees, 0° = right (default: `120`) +- `direction`: number — angle in degrees the element moves towards, 0° = right, 90° = bottom, 270° = top (default: `-60`) - `range`: 'in' | 'out' | 'continuous' (default: `'in'`) -- `distance`: UnitLengthPercentage — travel distance (default: `{ value: 400, unit: 'px' }`) +- `travel`: UnitLengthPercentage — travel distance (default: `{ value: 400, unit: 'px' }`) ```typescript -{ type: 'MoveScroll', angle: 90, distance: { value: 200, unit: 'px' } } +{ type: 'MoveScroll', direction: 270, travel: { value: 200, unit: 'px' } } ``` --- @@ -146,13 +146,13 @@ Visual: Horizontal panning tied to scroll. Parameters: -- `direction`: 'left' | 'right' (default: `'left'`) +- `direction`: 'left' | 'right' (default: `'right'`) - `distance`: UnitLengthPercentage — pan distance (default: `{ value: 400, unit: 'px' }`) - `startFromOffScreen`: boolean — whether to start from off-screen (default: `true`) - `range`: 'in' | 'out' | 'continuous' (default: `'in'`) ```typescript -{ type: 'PanScroll', direction: 'right', startFromOffScreen: false } +{ type: 'PanScroll', direction: 'left', startFromOffScreen: false } ``` --- @@ -163,10 +163,11 @@ Visual: Element moves at a different speed than the scroll, creating a depth ill Parameters: -- `parallaxFactor`: number — speed multiplier relative to scroll (default: `0.5`) +- `speed`: number — approximate visual speed relative to the scroll; 1 = moves with the page, below 1 = slower (default: `0.7159`) +- `center`: number — 0 to 1, point of the scroll range where the element is at its natural position (default: `0.5`) ```typescript -{ type: 'ParallaxScroll', parallaxFactor: 0.8 } +{ type: 'ParallaxScroll', speed: 0.6 } ``` --- @@ -194,7 +195,7 @@ Parameters: - `shape`: 'circle' | 'ellipse' | 'rectangle' | 'diamond' | 'window' (default: `'circle'`) - `range`: 'in' | 'out' | 'continuous' (default: `'in'`) -- `intensity`: number — shape size factor (default: `0.5`) +- `start`: number — 0 to 1, how open the shape starts (default: `0.5`) ```typescript { type: 'ShapeScroll', shape: 'diamond', range: 'out' } @@ -208,13 +209,13 @@ Visual: Element shrinks toward a direction as it scrolls into or out of view, th Parameters: -- `direction`: EffectNineDirections — shrink origin (default: `'center'`) +- `pivot`: EffectNineDirections — shrink origin (default: `'center'`) - `range`: 'in' | 'out' | 'continuous' (default: `'in'`) -- `scale`: number — target scale (default: `1.2` for 'in', `0.8` for 'out') -- `speed`: number — vertical movement factor (default: `0`) +- `scale`: number — starting scale on 'in', ending scale otherwise; inverted to above 1 for 'in' and below 1 otherwise (default: `1.2`, i.e. `1/1.2` for 'out') +- `speed`: number — approximate visual speed relative to the scroll, 1 = moves with the page (default: `1`) ```typescript -{ type: 'ShrinkScroll', direction: 'bottom', scale: 0.5 } +{ type: 'ShrinkScroll', pivot: 'bottom', scale: 2 } ``` --- @@ -225,8 +226,8 @@ Visual: Element is revealed through staggered shutter-like strips that open on s Parameters: -- `direction`: 'top' | 'right' | 'bottom' | 'left' (default: `'right'`) -- `shutters`: number — number of shutter segments (default: `12`) +- `direction`: 'top' | 'right' | 'bottom' | 'left' — the way the shutters open (default: `'right'`) +- `shutters`: number — number of shutter segments, min 1 (default: `12`) - `staggered`: boolean — whether shutters open in a staggered pattern (default: `true`) - `range`: 'in' | 'out' | 'continuous' (default: `'in'`) @@ -242,12 +243,12 @@ Visual: Element pans horizontally with a skew distortion as it scrolls. Parameters: -- `direction`: 'left' | 'right' (default: `'right'`) +- `direction`: 'left' | 'right' (default: `'left'`) - `skew`: number — skew angle in degrees (default: `10`) - `range`: 'in' | 'out' | 'continuous' (default: `'in'`) ```typescript -{ type: 'SkewPanScroll', skew: 20, direction: 'left' } +{ type: 'SkewPanScroll', skew: 20, direction: 'right' } ``` --- @@ -258,7 +259,7 @@ Visual: Element slides in from an edge with a clip-path reveal as it scrolls. Parameters: -- `direction`: 'top' | 'right' | 'bottom' | 'left' (default: `'bottom'`) +- `direction`: 'top' | 'right' | 'bottom' | 'left' — where the element moves to (default: `'top'`) - `range`: 'in' | 'out' | 'continuous' (default: `'in'`) ```typescript @@ -273,13 +274,13 @@ Visual: Element performs a 3D spin with rotation on multiple axes as it scrolls. Parameters: -- `rotate`: number — rotation amount in degrees (default: `-100`) -- `speed`: number — vertical movement factor (default: `0`) +- `angle`: number — rotation amount in degrees, applied on all axes (default: `100`) +- `speed`: number — approximate visual speed relative to the scroll, 1 = moves with the page (default: `1`) - `range`: 'in' | 'out' | 'continuous' (default: `'in'`) - `perspective`: number — 3D perspective in px (default: `1000`) ```typescript -{ type: 'Spin3dScroll', rotate: 200, perspective: 600 } +{ type: 'Spin3dScroll', angle: 200, perspective: 600 } ``` --- @@ -307,11 +308,11 @@ Visual: Element stretches vertically with scaleY increasing while scaleX decreas Parameters: -- `stretch`: number — stretch factor (default: `0.6`) +- `stretch`: number — -1 to 1, stretch factor (scaleY = 1 + stretch, scaleX = 1 − stretch) (default: `0.6`) - `range`: 'in' | 'out' | 'continuous' (default: `'out'`) ```typescript -{ type: 'StretchScroll', stretch: 1.2, range: 'continuous' } +{ type: 'StretchScroll', stretch: 0.8, range: 'continuous' } ``` --- @@ -322,13 +323,13 @@ Visual: Element tilts in 3D and perspective, with optional parallax vertical mov Parameters: -- `direction`: 'left' | 'right' (default: `'right'`) -- `parallaxFactor`: number — vertical movement factor (default: `0`) +- `direction`: 'clockwise' | 'counter-clockwise' — z-rotation direction (default: `'counter-clockwise'`) +- `speed`: number — approximate visual speed relative to the scroll, 1 = moves with the page (default: `1`) - `perspective`: number — 3D perspective in px (default: `400`) - `range`: 'in' | 'out' | 'continuous' (default: `'in'`) ```typescript -{ type: 'TiltScroll', direction: 'left', parallaxFactor: 0.5 } +{ type: 'TiltScroll', direction: 'clockwise', speed: 1.4 } ``` --- @@ -339,13 +340,14 @@ Visual: Element pans in from off-screen while turning (rotating) as it scrolls. Parameters: -- `direction`: 'left' | 'right' (default: `'right'`) +- `direction`: 'left' | 'right' — where the element moves to (default: `'left'`) - `spin`: 'clockwise' | 'counter-clockwise' (default: `'clockwise'`) +- `angle`: number — turn angle in degrees (default: `45`) - `scale`: number — scale during turn (default: `1`) - `range`: 'in' | 'out' | 'continuous' (default: `'in'`) ```typescript -{ type: 'TurnScroll', direction: 'left', spin: 'counter-clockwise' } +{ type: 'TurnScroll', direction: 'right', spin: 'counter-clockwise' } ``` --- @@ -368,13 +370,13 @@ Tested values for different intensity levels. When a user asks for "soft", "subt | Preset | Parameter | Subtle/Soft | Medium | Dramatic/Hard | | ------------- | --------- | ----------- | ------ | ------------- | | BlurScroll | blur | 6px | 25px | 50px | -| FlipScroll | rotate | 60° | 120° | 420° | +| FlipScroll | angle | 60° | 120° | 420° | | GrowScroll | scale | 1.2 | 1.7 | 4 | -| MoveScroll | distance | 150px | 400px | 800px | +| MoveScroll | travel | 150px | 400px | 800px | | ShrinkScroll | scale | 0.8 | 0.3 | 0 | | SkewPanScroll | skew | 10° | 17° | 24° | -| Spin3dScroll | rotate | 45° | 100° | 200° | +| Spin3dScroll | angle | 45° | 100° | 200° | | SpinScroll | scale | 1 | 0.7 | 0.4 | -| StretchScroll | stretch | 1.2 | 1.5 | 2 | -| TiltScroll | distance | 0 | 0.5 | 1 | +| StretchScroll | stretch | 0.2 | 0.5 | 1 | +| TiltScroll | speed | 1 | 1.4 | 1.6 | | TurnScroll | scale | 1 | 1.3 | 1.6 | diff --git a/packages/motion-presets/src/consts.ts b/packages/motion-presets/src/consts.ts index d40b9386..e49d0cbd 100644 --- a/packages/motion-presets/src/consts.ts +++ b/packages/motion-presets/src/consts.ts @@ -1,5 +1,6 @@ const FOUR_DIRECTIONS = ['top', 'right', 'bottom', 'left'] as const; const AXIS_DIRECTIONS = ['horizontal', 'vertical'] as const; +const SCROLL_RANGES = ['in', 'out', 'continuous'] as const; const SPIN_DIRECTIONS = ['clockwise', 'counter-clockwise'] as const; const TWO_SIDES_DIRECTIONS = ['left', 'right'] as const; const EIGHT_DIRECTIONS = [ @@ -23,14 +24,19 @@ const NINE_DIRECTIONS = [ 'top-left', 'center', ] as const; +const CENTER_AND_FOUR_DIRECTIONS = ['center', 'top', 'right', 'bottom', 'left'] as const; const FOUR_CORNERS_DIRECTIONS = ['top-left', 'top-right', 'bottom-left', 'bottom-right'] as const; +const SHAPES = ['rectangle', 'ellipse', 'circle', 'diamond', 'window'] as const; export { + CENTER_AND_FOUR_DIRECTIONS, FOUR_DIRECTIONS, AXIS_DIRECTIONS, + SCROLL_RANGES, SPIN_DIRECTIONS, TWO_SIDES_DIRECTIONS, EIGHT_DIRECTIONS, NINE_DIRECTIONS, FOUR_CORNERS_DIRECTIONS, + SHAPES, }; diff --git a/packages/motion-presets/src/library/entrance/ArcIn.ts b/packages/motion-presets/src/library/entrance/ArcIn.ts index 1b0c1a46..0413133a 100644 --- a/packages/motion-presets/src/library/entrance/ArcIn.ts +++ b/packages/motion-presets/src/library/entrance/ArcIn.ts @@ -3,8 +3,12 @@ import { toKeyframeValue, parseDirection, parseLength, getEntranceFill } from '. import { FOUR_DIRECTIONS } from '../../consts'; const ROTATION_ANGLE = 80; -const DEFAULT_DIRECTION: EffectFourDirections = 'right'; -const DEFAULT_DEPTH = { value: 200, unit: 'px' }; + +export const schema = { + depth: { type: 'length', default: { value: 100, unit: 'px' } }, + from: { type: 'enum', values: FOUR_DIRECTIONS, default: 'right' }, + perspective: { type: 'number', min: 0, default: 800 }, +} as const; const DIRECTION_MAP: Record = { top: { x: 1, y: 0, sign: 1 }, @@ -17,16 +21,20 @@ export function web(options: TimeAnimationOptions, _dom?: DomApi) { return style(options, true); } -export function getNames(_: TimeAnimationOptions) { - return ['motion-fadeIn', 'motion-arcIn']; +export function getNames({ suffix = '' }: TimeAnimationOptions) { + return ['motion-fadeIn', 'motion-arcIn'].map((name) => name + suffix); } export function style(options: TimeAnimationOptions, asWeb = false) { const namedEffect = options.namedEffect as ArcIn; - const direction = parseDirection(namedEffect?.direction, FOUR_DIRECTIONS, DEFAULT_DIRECTION); + const direction = parseDirection( + namedEffect?.from, + FOUR_DIRECTIONS, + schema.from.default, + ); - const depth = parseLength(namedEffect.depth, DEFAULT_DEPTH); - const { perspective = 800 } = namedEffect; + const depth = parseLength(namedEffect.depth, schema.depth.default); + const { perspective = schema.perspective.default } = namedEffect; const [fadeIn, arcIn] = getNames(options); const easing = options.easing || 'quintInOut'; @@ -39,8 +47,8 @@ export function style(options: TimeAnimationOptions, asWeb = false) { '--motion-arc-x': `${x}`, '--motion-arc-y': `${y}`, '--motion-arc-sign': `${sign}`, - '--motion-depth-negative': `calc(-1 * ${depthValue} / 2)`, - '--motion-depth-positive': `calc(${depthValue} / 2)`, + '--motion-depth-negative': `calc(-1 * ${depthValue})`, + '--motion-depth-positive': depthValue, }; return [ diff --git a/packages/motion-presets/src/library/entrance/BlurIn.ts b/packages/motion-presets/src/library/entrance/BlurIn.ts index 1b1cbf55..5e789449 100644 --- a/packages/motion-presets/src/library/entrance/BlurIn.ts +++ b/packages/motion-presets/src/library/entrance/BlurIn.ts @@ -1,8 +1,12 @@ import type { BlurIn, TimeAnimationOptions } from '../../types'; import { toKeyframeValue, getEntranceFill } from '../../utils'; -export function getNames(_: TimeAnimationOptions) { - return ['motion-fadeIn', 'motion-blurIn']; +export const schema = { + blur: { type: 'number', min: 0, default: 6 }, +} as const; + +export function getNames({ suffix = '' }: TimeAnimationOptions) { + return ['motion-fadeIn', 'motion-blurIn'].map((name) => name + suffix); } export function web(options: TimeAnimationOptions) { @@ -10,7 +14,7 @@ export function web(options: TimeAnimationOptions) { } export function style(options: TimeAnimationOptions, asWeb = false) { - const { blur = 6 } = options.namedEffect as BlurIn; + const { blur = schema.blur.default } = options.namedEffect as BlurIn; const [fadeIn, blurIn] = getNames(options); const easing = options.easing || 'linear'; diff --git a/packages/motion-presets/src/library/entrance/BounceIn.ts b/packages/motion-presets/src/library/entrance/BounceIn.ts index 562aef99..69a232d9 100644 --- a/packages/motion-presets/src/library/entrance/BounceIn.ts +++ b/packages/motion-presets/src/library/entrance/BounceIn.ts @@ -3,16 +3,23 @@ import { getEasing, toKeyframeValue, parseDirection, + parseLength, + getCssUnits, getEntranceFill, } from '../../utils'; import type { BounceIn, TimeAnimationOptions } from '../../types'; import { FOUR_DIRECTIONS } from '../../consts'; -const DIRECTIONS = [...FOUR_DIRECTIONS, 'center'] as const; -const DEFAULT_DIRECTION: (typeof DIRECTIONS)[number] = 'bottom'; +const DIRECTIONS = [...FOUR_DIRECTIONS, 'back'] as const; -export function getNames(_: TimeAnimationOptions) { - return ['motion-fadeIn', 'motion-bounceIn']; +export const schema = { + from: { type: 'enum', values: DIRECTIONS, default: 'bottom' }, + perspective: { type: 'number', min: 0, default: 800 }, + travel: { type: 'length', default: { value: 50, unit: 'px' } }, +} as const; + +export function getNames({ suffix = '' }: TimeAnimationOptions) { + return ['motion-fadeIn', 'motion-bounceIn'].map((name) => name + suffix); } const { in: easeIn, out: easeOut } = getEasingFamily('sineIn'); @@ -34,7 +41,7 @@ const TRANSLATE_DIRECTION_MAP = { right: { y: 0, x: 1, z: 0 }, bottom: { y: 1, x: 0, z: 0 }, left: { y: 0, x: -1, z: 0 }, - center: { x: 0, y: 0, z: -1 }, + back: { x: 0, y: 0, z: -1 }, }; export function web(options: TimeAnimationOptions) { @@ -43,11 +50,17 @@ export function web(options: TimeAnimationOptions) { export function style(options: TimeAnimationOptions, asWeb = false) { const namedEffect = options.namedEffect as BounceIn; - const direction = parseDirection(namedEffect?.direction, DIRECTIONS, DEFAULT_DIRECTION); - const distanceFactor = namedEffect?.distanceFactor || 1; - const { perspective = 800 } = namedEffect || {}; + const direction = parseDirection<(typeof DIRECTIONS)[number]>( + namedEffect?.from, + DIRECTIONS, + schema.from.default, + ); + const travel = parseLength(namedEffect?.travel, schema.travel.default); + const distanceFactor = travel.value / schema.travel.default.value; + const unit = getCssUnits(travel.unit); + const { perspective = schema.perspective.default } = namedEffect || {}; const [fadeIn, bounceIn] = getNames(options); - const perspectiveValue = direction === 'center' ? `perspective(${perspective}px)` : ' '; + const perspectiveValue = direction === 'back' ? `perspective(${perspective}px)` : ' '; const { x, y, z } = TRANSLATE_DIRECTION_MAP[direction]; const custom = { @@ -75,11 +88,11 @@ export function style(options: TimeAnimationOptions, asWeb = false) { perspective_ as string ).trim()} translate3d(calc(${directionX} * ${distanceFactor_} * ${ translate / 2 - }px), calc(${directionY} * ${distanceFactor_} * ${ + }${unit}), calc(${directionY} * ${distanceFactor_} * ${ translate / 2 - }px), calc(${directionZ} * ${distanceFactor_} * ${ + }${unit}), calc(${directionZ} * ${distanceFactor_} * ${ translate / 2 - }px)) rotateZ(var(--motion-rotate, 0deg))`, + }${unit})) rotateZ(var(--motion-rotate, 0deg))`, })); return [ diff --git a/packages/motion-presets/src/library/entrance/CurveIn.ts b/packages/motion-presets/src/library/entrance/CurveIn.ts index 33b94287..c21d065a 100644 --- a/packages/motion-presets/src/library/entrance/CurveIn.ts +++ b/packages/motion-presets/src/library/entrance/CurveIn.ts @@ -1,19 +1,21 @@ -import type { CurveIn, TimeAnimationOptions, DomApi } from '../../types'; +import type { CurveIn, TimeAnimationOptions, DomApi, EffectFourDirections } from '../../types'; import { toKeyframeValue, parseDirection, parseLength, getEntranceFill } from '../../utils'; -import { TWO_SIDES_DIRECTIONS } from '../../consts'; +import { FOUR_DIRECTIONS } from '../../consts'; -const DEFAULT_DEPTH = { value: 300, unit: 'px' }; -const DIRECTIONS = [...TWO_SIDES_DIRECTIONS, 'pseudoLeft', 'pseudoRight'] as const; -const DEFAULT_DIRECTION: (typeof DIRECTIONS)[number] = 'right'; +export const schema = { + depth: { type: 'length', default: { value: 900, unit: 'px' } }, + from: { type: 'enum', values: FOUR_DIRECTIONS, default: 'right' }, + perspective: { type: 'number', min: 0, default: 200 }, +} as const; -export function getNames(_: TimeAnimationOptions) { - return ['motion-curveIn', 'motion-fadeIn']; +export function getNames({ suffix = '' }: TimeAnimationOptions) { + return ['motion-curveIn', 'motion-fadeIn'].map((name) => name + suffix); } -const PARAMS_MAP = { - pseudoRight: { rotationX: '180', rotationY: '0' }, +const PARAMS_MAP: Record = { + top: { rotationX: '180', rotationY: '0' }, right: { rotationX: '0', rotationY: '180' }, - pseudoLeft: { rotationX: '-180', rotationY: '0' }, + bottom: { rotationX: '-180', rotationY: '0' }, left: { rotationX: '0', rotationY: '-180' }, }; @@ -23,9 +25,13 @@ export function web(options: TimeAnimationOptions, _dom?: DomApi) { export function style(options: TimeAnimationOptions, asWeb = false) { const namedEffect = options.namedEffect as CurveIn; - const direction = parseDirection(namedEffect?.direction, DIRECTIONS, DEFAULT_DIRECTION); - const depth = parseLength(namedEffect.depth, DEFAULT_DEPTH); - const { perspective = 200 } = namedEffect; + const direction = parseDirection( + namedEffect?.from, + FOUR_DIRECTIONS, + schema.from.default, + ); + const depth = parseLength(namedEffect.depth, schema.depth.default); + const { perspective = schema.perspective.default } = namedEffect; const [curveIn, fadeIn] = getNames(options); const { rotationX, rotationY } = PARAMS_MAP[direction]; @@ -35,8 +41,8 @@ export function style(options: TimeAnimationOptions, asWeb = false) { '--motion-perspective': `${perspective}px`, '--motion-rotate-x': `${rotationX}deg`, '--motion-rotate-y': `${rotationY}deg`, - '--motion-depth-negative': `calc(${depthValue} * -3)`, - '--motion-depth-positive': `calc(${depthValue} * 3)`, + '--motion-depth-negative': `calc(-1 * ${depthValue})`, + '--motion-depth-positive': depthValue, }; const easing = 'quadOut'; diff --git a/packages/motion-presets/src/library/entrance/DropIn.ts b/packages/motion-presets/src/library/entrance/DropIn.ts index 3218a1fd..fb4e9ad2 100644 --- a/packages/motion-presets/src/library/entrance/DropIn.ts +++ b/packages/motion-presets/src/library/entrance/DropIn.ts @@ -1,8 +1,12 @@ import type { TimeAnimationOptions, DropIn } from '../../types'; import { toKeyframeValue, getEntranceFill } from '../../utils'; -export function getNames(_: TimeAnimationOptions) { - return ['motion-fadeIn', 'motion-dropIn']; +export const schema = { + scale: { type: 'number', min: 1, default: 1.6 }, +} as const; + +export function getNames({ suffix = '' }: TimeAnimationOptions) { + return ['motion-fadeIn', 'motion-dropIn'].map((name) => name + suffix); } export function web(options: TimeAnimationOptions) { @@ -10,13 +14,13 @@ export function web(options: TimeAnimationOptions) { } export function style(options: TimeAnimationOptions, asWeb = false) { - const { initialScale = 1.6 } = options.namedEffect as DropIn; + const { scale = schema.scale.default } = options.namedEffect as DropIn; const [fadeIn, dropIn] = getNames(options); const easing = options.easing || 'quintInOut'; const custom = { - '--motion-scale': `${initialScale}`, + '--motion-scale': `${scale}`, }; return [ diff --git a/packages/motion-presets/src/library/entrance/ExpandIn.ts b/packages/motion-presets/src/library/entrance/ExpandIn.ts index 073c97c8..a458268c 100644 --- a/packages/motion-presets/src/library/entrance/ExpandIn.ts +++ b/packages/motion-presets/src/library/entrance/ExpandIn.ts @@ -9,8 +9,12 @@ import { import type { ExpandIn } from '../../types'; import { FOUR_DIRECTIONS } from '../../consts'; -const DEFAULT_DIRECTION = 90; -const DEFAULT_DISTANCE = { value: 120, unit: 'percentage' }; +export const schema = { + from: { type: 'angle', default: 270 }, + scale: { type: 'number', min: 0, max: 1, default: 0 }, + travel: { type: 'length', default: { value: 120, unit: 'percentage' } }, +} as const; + const DIRECTION_KEYWORD_TO_ANGLE: Record = { top: 90, right: 0, @@ -18,8 +22,8 @@ const DIRECTION_KEYWORD_TO_ANGLE: Record = { left: 180, }; -export function getNames(_: TimeAnimationOptions) { - return ['motion-fadeIn', 'motion-expandIn']; +export function getNames({ suffix = '' }: TimeAnimationOptions) { + return ['motion-fadeIn', 'motion-expandIn'].map((name) => name + suffix); } export function web(options: TimeAnimationOptions) { @@ -28,20 +32,20 @@ export function web(options: TimeAnimationOptions) { export function style(options: TimeAnimationOptions, asWeb = false) { const namedEffect = options.namedEffect as ExpandIn; - const { initialScale = 0 } = namedEffect; + const { scale = schema.scale.default } = namedEffect; - const parsedDirection = parseDirection( - namedEffect?.direction, + const parsedDirection = parseDirection( + namedEffect?.from, FOUR_DIRECTIONS, - DEFAULT_DIRECTION, + schema.from.default, true, ); const direction = typeof parsedDirection === 'string' ? DIRECTION_KEYWORD_TO_ANGLE[parsedDirection] - : parsedDirection; + : 360 - parsedDirection; - const distance = parseLength(namedEffect.distance, DEFAULT_DISTANCE); + const distance = parseLength(namedEffect.travel, schema.travel.default); const [fadeIn, expandIn] = getNames(options); @@ -55,7 +59,7 @@ export function style(options: TimeAnimationOptions, asWeb = false) { const custom = { '--motion-translate-x': `${x}`, '--motion-translate-y': `${y}`, - '--motion-scale': `${initialScale}`, + '--motion-scale': `${scale}`, }; return [ diff --git a/packages/motion-presets/src/library/entrance/FadeIn.ts b/packages/motion-presets/src/library/entrance/FadeIn.ts index 4f25cd47..0660bd2f 100644 --- a/packages/motion-presets/src/library/entrance/FadeIn.ts +++ b/packages/motion-presets/src/library/entrance/FadeIn.ts @@ -2,8 +2,10 @@ import { TimeAnimationOptions } from '../../types'; import { getEntranceFill } from '../../utils'; -export function getNames(_: TimeAnimationOptions) { - return ['motion-fadeIn']; +export const schema = {} as const; + +export function getNames({ suffix = '' }: TimeAnimationOptions) { + return ['motion-fadeIn'].map((name) => name + suffix); } export function web(options: TimeAnimationOptions) { diff --git a/packages/motion-presets/src/library/entrance/FlipIn.ts b/packages/motion-presets/src/library/entrance/FlipIn.ts index 148f4adf..39a0b558 100644 --- a/packages/motion-presets/src/library/entrance/FlipIn.ts +++ b/packages/motion-presets/src/library/entrance/FlipIn.ts @@ -1,11 +1,25 @@ import { parseDirection, toKeyframeValue, getEntranceFill } from '../../utils'; -import type { EffectFourDirections, FlipIn, TimeAnimationOptions } from '../../types'; -import { FOUR_DIRECTIONS } from '../../consts'; +import type { + EffectFourDirections, + EffectTwoAxes, + FlipIn, + TimeAnimationOptions, +} from '../../types'; +import { AXIS_DIRECTIONS } from '../../consts'; -const DEFAULT_DIRECTION: EffectFourDirections = 'top'; +export const schema = { + angle: { type: 'number', default: 90 }, + direction: { type: 'enum', values: AXIS_DIRECTIONS, default: 'vertical' }, + perspective: { type: 'number', min: 0, default: 800 }, +} as const; -export function getNames(_: TimeAnimationOptions) { - return ['motion-fadeIn', 'motion-flipIn']; +const AXIS_TO_DIRECTION: Record = { + vertical: 'top', + horizontal: 'left', +}; + +export function getNames({ suffix = '' }: TimeAnimationOptions) { + return ['motion-fadeIn', 'motion-flipIn'].map((name) => name + suffix); } function getRotateFrom(direction: EffectFourDirections, rotate: number) { @@ -28,12 +42,17 @@ export function web(options: TimeAnimationOptions) { export function style(options: TimeAnimationOptions, asWeb = false) { const namedEffect = options.namedEffect as FlipIn; - const direction = parseDirection(namedEffect?.direction, FOUR_DIRECTIONS, DEFAULT_DIRECTION); - const { initialRotate = 90, perspective = 800 } = namedEffect; + const axis = parseDirection( + namedEffect?.direction, + AXIS_DIRECTIONS, + schema.direction.default, + ); + const direction = AXIS_TO_DIRECTION[axis]; + const { angle = schema.angle.default, perspective = schema.perspective.default } = namedEffect; const [fadeIn, flipIn] = getNames(options); const easing = options.easing || 'backOut'; - const from = getRotateFrom(direction, initialRotate); + const from = getRotateFrom(direction, angle); const custom = { '--motion-perspective': `${perspective}px`, diff --git a/packages/motion-presets/src/library/entrance/FloatIn.ts b/packages/motion-presets/src/library/entrance/FloatIn.ts index 97d28514..02fd6cec 100644 --- a/packages/motion-presets/src/library/entrance/FloatIn.ts +++ b/packages/motion-presets/src/library/entrance/FloatIn.ts @@ -2,10 +2,12 @@ import type { TimeAnimationOptions, FloatIn, EffectFourDirections } from '../../ import { toKeyframeValue, parseDirection, getEntranceFill } from '../../utils'; import { FOUR_DIRECTIONS } from '../../consts'; -const DEFAULT_DIRECTION: EffectFourDirections = 'left'; +export const schema = { + from: { type: 'enum', values: FOUR_DIRECTIONS, default: 'left' }, +} as const; -export function getNames(_: TimeAnimationOptions) { - return ['motion-floatIn', 'motion-fadeIn']; +export function getNames({ suffix = '' }: TimeAnimationOptions) { + return ['motion-floatIn', 'motion-fadeIn'].map((name) => name + suffix); } const DIRECTION_MAP = { @@ -21,7 +23,11 @@ export function web(options: TimeAnimationOptions) { export function style(options: TimeAnimationOptions, asWeb = false) { const namedEffect = options.namedEffect as FloatIn; - const direction = parseDirection(namedEffect?.direction, FOUR_DIRECTIONS, DEFAULT_DIRECTION); + const direction = parseDirection( + namedEffect?.from, + FOUR_DIRECTIONS, + schema.from.default, + ); const [floatIn, fadeIn] = getNames(options); const fromParams = DIRECTION_MAP[direction]; diff --git a/packages/motion-presets/src/library/entrance/FoldIn.ts b/packages/motion-presets/src/library/entrance/FoldIn.ts index 15a088d2..92b09438 100644 --- a/packages/motion-presets/src/library/entrance/FoldIn.ts +++ b/packages/motion-presets/src/library/entrance/FoldIn.ts @@ -2,11 +2,15 @@ import type { EffectFourDirections, FoldIn, TimeAnimationOptions } from '../../t import { parseDirection, toKeyframeValue, getEntranceFill } from '../../utils'; import { FOUR_DIRECTIONS } from '../../consts'; -export function getNames(_: TimeAnimationOptions) { - return ['motion-fadeIn', 'motion-foldIn']; -} +export const schema = { + angle: { type: 'number', default: -90 }, + perspective: { type: 'number', min: 0, default: 800 }, + pivot: { type: 'enum', values: FOUR_DIRECTIONS, default: 'top' }, +} as const; -const DEFAULT_DIRECTION: EffectFourDirections = 'top'; +export function getNames({ suffix = '' }: TimeAnimationOptions) { + return ['motion-fadeIn', 'motion-foldIn'].map((name) => name + suffix); +} const PARAM_MAP: Record< EffectFourDirections, @@ -31,13 +35,17 @@ export function web(options: TimeAnimationOptions) { export function style(options: TimeAnimationOptions, asWeb = false) { const namedEffect = options.namedEffect as FoldIn; - const direction = parseDirection(namedEffect?.direction, FOUR_DIRECTIONS, DEFAULT_DIRECTION); - const { initialRotate = 90, perspective = 800 } = namedEffect; + const direction = parseDirection( + namedEffect?.pivot, + FOUR_DIRECTIONS, + schema.pivot.default, + ); + const { angle = schema.angle.default, perspective = schema.perspective.default } = namedEffect; const [fadeIn, foldIn] = getNames(options); const easing = options.easing || 'backOut'; const { x, y } = PARAM_MAP[direction].origin; - const from = getRotateFrom(direction, initialRotate); + const from = getRotateFrom(direction, -angle); const custom = { '--motion-perspective': `${perspective}px`, diff --git a/packages/motion-presets/src/library/entrance/GlideIn.ts b/packages/motion-presets/src/library/entrance/GlideIn.ts index 719e528b..b9e8aece 100644 --- a/packages/motion-presets/src/library/entrance/GlideIn.ts +++ b/packages/motion-presets/src/library/entrance/GlideIn.ts @@ -8,18 +8,21 @@ import { } from '../../utils'; import { FOUR_DIRECTIONS } from '../../consts'; -const DEFAULT_DIRECTION = 180; -const DEFAULT_DISTANCE = { value: 100, unit: 'percentage' }; +export const schema = { + from: { type: 'angle', default: 180 }, + travel: { type: 'length', default: { value: 100, unit: 'percentage' } }, +} as const; + const DIRECTION_KEYWORD_TO_ANGLE: Record = { - top: 90, + top: 270, right: 0, - bottom: 270, + bottom: 90, left: 180, }; const ALLOW_ANGLES = true; -export function getNames(_: TimeAnimationOptions) { - return ['motion-glideIn', 'motion-fadeIn']; +export function getNames({ suffix = '' }: TimeAnimationOptions) { + return ['motion-glideIn', 'motion-fadeIn'].map((name) => name + suffix); } export function web(options: TimeAnimationOptions) { @@ -29,10 +32,10 @@ export function web(options: TimeAnimationOptions) { export function style(options: TimeAnimationOptions, asWeb = false) { const namedEffect = options.namedEffect as GlideIn; - const parsedDirection = parseDirection( - namedEffect?.direction, + const parsedDirection = parseDirection( + namedEffect?.from, FOUR_DIRECTIONS, - DEFAULT_DIRECTION, + schema.from.default, ALLOW_ANGLES, ); const direction = @@ -40,7 +43,7 @@ export function style(options: TimeAnimationOptions, asWeb = false) { ? DIRECTION_KEYWORD_TO_ANGLE[parsedDirection] : parsedDirection; - const distance = parseLength(namedEffect.distance, DEFAULT_DISTANCE); + const distance = parseLength(namedEffect.travel, schema.travel.default); const angleInRad = (direction * Math.PI) / 180; const unit = getCssUnits(distance.unit); @@ -48,7 +51,7 @@ export function style(options: TimeAnimationOptions, asWeb = false) { const easing = options.easing || 'quintInOut'; const translateX = `${(Math.cos(angleInRad) * distance.value) | 0}${unit}`; - const translateY = `${(Math.sin(angleInRad) * distance.value * -1) | 0}${unit}`; + const translateY = `${(Math.sin(angleInRad) * distance.value) | 0}${unit}`; const custom = { '--motion-translate-x': `${translateX}`, diff --git a/packages/motion-presets/src/library/entrance/RevealIn.ts b/packages/motion-presets/src/library/entrance/RevealIn.ts index 9f23baaa..cfd214b8 100644 --- a/packages/motion-presets/src/library/entrance/RevealIn.ts +++ b/packages/motion-presets/src/library/entrance/RevealIn.ts @@ -2,10 +2,12 @@ import type { RevealIn, TimeAnimationOptions, EffectFourDirections } from '../.. import { getClipPolygonParams, parseDirection, getEntranceFill } from '../../utils'; import { FOUR_DIRECTIONS } from '../../consts'; -const DEFAULT_DIRECTION: EffectFourDirections = 'left'; +export const schema = { + from: { type: 'enum', values: FOUR_DIRECTIONS, default: 'left' }, +} as const; -export function getNames(_: TimeAnimationOptions) { - return ['motion-revealIn', 'motion-fadeIn']; +export function getNames({ suffix = '' }: TimeAnimationOptions) { + return ['motion-revealIn', 'motion-fadeIn'].map((name) => name + suffix); } export function web(options: TimeAnimationOptions) { @@ -14,7 +16,11 @@ export function web(options: TimeAnimationOptions) { export function style(options: TimeAnimationOptions) { const namedEffect = options.namedEffect as RevealIn; - const direction = parseDirection(namedEffect?.direction, FOUR_DIRECTIONS, DEFAULT_DIRECTION); + const direction = parseDirection( + namedEffect?.from, + FOUR_DIRECTIONS, + schema.from.default, + ); const [revealIn, fadeIn] = getNames(options); const easing = options.easing || 'cubicInOut'; diff --git a/packages/motion-presets/src/library/entrance/ShapeIn.ts b/packages/motion-presets/src/library/entrance/ShapeIn.ts index 677855d1..ff93215a 100644 --- a/packages/motion-presets/src/library/entrance/ShapeIn.ts +++ b/packages/motion-presets/src/library/entrance/ShapeIn.ts @@ -1,8 +1,13 @@ import type { Shape, ShapeIn, TimeAnimationOptions } from '../../types'; import { toKeyframeValue, getEntranceFill } from '../../utils'; +import { SHAPES } from '../../consts'; -export function getNames(_: TimeAnimationOptions) { - return ['motion-fadeIn', 'motion-shapeIn']; +export const schema = { + shape: { type: 'enum', values: SHAPES, default: 'rectangle' }, +} as const; + +export function getNames({ suffix = '' }: TimeAnimationOptions) { + return ['motion-fadeIn', 'motion-shapeIn'].map((name) => name + suffix); } const shapes: Record = { @@ -24,7 +29,7 @@ export function web(options: TimeAnimationOptions) { } export function style(options: TimeAnimationOptions, asWeb = false) { - const { shape = 'rectangle' } = options.namedEffect as ShapeIn; + const { shape = schema.shape.default } = options.namedEffect as ShapeIn; const [fadeIn, shapeIn] = getNames(options); const easing = options.easing || 'cubicInOut'; diff --git a/packages/motion-presets/src/library/entrance/ShuttersIn.ts b/packages/motion-presets/src/library/entrance/ShuttersIn.ts index 34a0ecb0..7d7ee82c 100644 --- a/packages/motion-presets/src/library/entrance/ShuttersIn.ts +++ b/packages/motion-presets/src/library/entrance/ShuttersIn.ts @@ -5,13 +5,18 @@ import { toKeyframeValue, parseDirection, getEntranceFill, + getOppositeDirection, } from '../../utils'; import { FOUR_DIRECTIONS } from '../../consts'; -const DEFAULT_DIRECTION: EffectFourDirections = 'right'; +export const schema = { + from: { type: 'enum', values: FOUR_DIRECTIONS, default: 'left' }, + shutters: { type: 'number', min: 1, int: true, default: 12 }, + staggered: { type: 'bool', default: true }, +} as const; -export function getNames(_: TimeAnimationOptions) { - return ['motion-shuttersIn', 'motion-fadeIn']; +export function getNames({ suffix = '' }: TimeAnimationOptions) { + return ['motion-shuttersIn', 'motion-fadeIn'].map((name) => name + suffix); } export function web(options: TimeAnimationOptions) { @@ -20,8 +25,13 @@ export function web(options: TimeAnimationOptions) { export function style(options: TimeAnimationOptions, asWeb = false) { const namedEffect = options.namedEffect as ShuttersIn; - const direction = parseDirection(namedEffect?.direction, FOUR_DIRECTIONS, DEFAULT_DIRECTION); - const { shutters = 12, staggered = true } = namedEffect; + const from = parseDirection( + namedEffect?.from, + FOUR_DIRECTIONS, + schema.from.default, + ); + const direction = getOppositeDirection([...FOUR_DIRECTIONS], from); + const { shutters = schema.shutters.default, staggered = schema.staggered.default } = namedEffect; const [shuttersIn, fadeIn] = getNames(options); const { clipStart, clipEnd } = getShuttersClipPaths(direction, shutters, staggered); diff --git a/packages/motion-presets/src/library/entrance/SlideIn.ts b/packages/motion-presets/src/library/entrance/SlideIn.ts index d037f4fa..e01f8953 100644 --- a/packages/motion-presets/src/library/entrance/SlideIn.ts +++ b/packages/motion-presets/src/library/entrance/SlideIn.ts @@ -2,10 +2,13 @@ import type { EffectFourDirections, SlideIn, TimeAnimationOptions } from '../../ import { getClipPolygonParams, parseDirection, getEntranceFill } from '../../utils'; import { FOUR_DIRECTIONS } from '../../consts'; -const DEFAULT_DIRECTION: EffectFourDirections = 'left'; +export const schema = { + from: { type: 'enum', values: FOUR_DIRECTIONS, default: 'left' }, + start: { type: 'number', min: 0, max: 1, default: 0 }, +} as const; -export function getNames(_: TimeAnimationOptions) { - return ['motion-slideIn', 'motion-fadeIn']; +export function getNames({ suffix = '' }: TimeAnimationOptions) { + return ['motion-slideIn', 'motion-fadeIn'].map((name) => name + suffix); } const PARAM_MAP: Record< @@ -24,12 +27,16 @@ export function web(options: TimeAnimationOptions) { export function style(options: TimeAnimationOptions) { const namedEffect = options.namedEffect as SlideIn; - const direction = parseDirection(namedEffect?.direction, FOUR_DIRECTIONS, DEFAULT_DIRECTION); - const { initialTranslate = 1 } = namedEffect; + const direction = parseDirection( + namedEffect?.from, + FOUR_DIRECTIONS, + schema.from.default, + ); + const { start: startTranslate = schema.start.default } = namedEffect; const [slideIn, fadeIn] = getNames(options); const easing = options.easing || 'cubicInOut'; - const minimum = 100 - initialTranslate * 100; + const minimum = startTranslate * 100; const start = getClipPolygonParams({ direction: PARAM_MAP[direction].clip, diff --git a/packages/motion-presets/src/library/entrance/SpinIn.ts b/packages/motion-presets/src/library/entrance/SpinIn.ts index 749e5837..f30bfc0d 100644 --- a/packages/motion-presets/src/library/entrance/SpinIn.ts +++ b/packages/motion-presets/src/library/entrance/SpinIn.ts @@ -2,10 +2,14 @@ import type { SpinIn, TimeAnimationOptions } from '../../types'; import { toKeyframeValue, parseDirection, getEntranceFill } from '../../utils'; import { SPIN_DIRECTIONS } from '../../consts'; -const DEFAULT_DIRECTION: (typeof SPIN_DIRECTIONS)[number] = 'clockwise'; +export const schema = { + direction: { type: 'enum', values: SPIN_DIRECTIONS, default: 'clockwise' }, + scale: { type: 'number', min: 0, default: 0 }, + spins: { type: 'number', min: 0, default: 0.5 }, +} as const; -export function getNames(_: TimeAnimationOptions) { - return ['motion-fadeIn', 'motion-spinIn']; +export function getNames({ suffix = '' }: TimeAnimationOptions) { + return ['motion-fadeIn', 'motion-spinIn'].map((name) => name + suffix); } const DIRECTION_MAP = { @@ -19,8 +23,12 @@ export function web(options: TimeAnimationOptions) { export function style(options: TimeAnimationOptions, asWeb = false) { const namedEffect = options.namedEffect as SpinIn; - const direction = parseDirection(namedEffect?.direction, SPIN_DIRECTIONS, DEFAULT_DIRECTION); - const { spins = 0.5, initialScale = 0 } = namedEffect; + const direction = parseDirection<(typeof SPIN_DIRECTIONS)[number]>( + namedEffect?.direction, + SPIN_DIRECTIONS, + schema.direction.default, + ); + const { spins = schema.spins.default, scale: initialScale = schema.scale.default } = namedEffect; const [fadeIn, spinIn] = getNames(options); const easing = options.easing || 'cubicInOut'; diff --git a/packages/motion-presets/src/library/entrance/TiltIn.ts b/packages/motion-presets/src/library/entrance/TiltIn.ts index 543c81f5..7dfcd2d7 100644 --- a/packages/motion-presets/src/library/entrance/TiltIn.ts +++ b/packages/motion-presets/src/library/entrance/TiltIn.ts @@ -8,11 +8,14 @@ import { import type { TiltIn, TimeAnimationOptions, EffectTwoSides } from '../../types'; import { TWO_SIDES_DIRECTIONS } from '../../consts'; -const DEFAULT_DIRECTION: EffectTwoSides = 'left'; -const DEFAULT_DEPTH = { value: 200, unit: 'px' }; +export const schema = { + depth: { type: 'length', default: { value: 100, unit: 'px' } }, + from: { type: 'enum', values: TWO_SIDES_DIRECTIONS, default: 'left' }, + perspective: { type: 'number', min: 0, default: 800 }, +} as const; -export function getNames(_: TimeAnimationOptions) { - return ['motion-fadeIn', 'motion-tiltInRotate', 'motion-tiltInClip']; +export function getNames({ suffix = '' }: TimeAnimationOptions) { + return ['motion-fadeIn', 'motion-tiltInRotate', 'motion-tiltInClip'].map((name) => name + suffix); } const ROTATION_MAP = { @@ -26,9 +29,13 @@ export function web(options: TimeAnimationOptions) { export function style(options: TimeAnimationOptions, asWeb = false) { const namedEffect = options.namedEffect as TiltIn; - const direction = parseDirection(namedEffect?.direction, TWO_SIDES_DIRECTIONS, DEFAULT_DIRECTION); - const depth = parseLength(namedEffect.depth, DEFAULT_DEPTH); - const { perspective = 800 } = namedEffect; + const direction = parseDirection( + namedEffect?.from, + TWO_SIDES_DIRECTIONS, + schema.from.default, + ); + const depth = parseLength(namedEffect.depth, schema.depth.default); + const { perspective = schema.perspective.default } = namedEffect; const [fadeIn, tiltInRotate, tiltInClip] = getNames(options); const easing = options.easing || 'cubicOut'; @@ -39,8 +46,8 @@ export function style(options: TimeAnimationOptions, asWeb = false) { const rotateCustom = { '--motion-perspective': `${perspective}px`, - '--motion-depth-negative': `calc(${depthValue} / 2 * -1)`, - '--motion-depth-positive': `calc(${depthValue} / 2)`, + '--motion-depth-negative': `calc(${depthValue} * -1)`, + '--motion-depth-positive': depthValue, }; const clipCustom = { diff --git a/packages/motion-presets/src/library/entrance/TurnIn.ts b/packages/motion-presets/src/library/entrance/TurnIn.ts index 8d5f679a..b35f3cdd 100644 --- a/packages/motion-presets/src/library/entrance/TurnIn.ts +++ b/packages/motion-presets/src/library/entrance/TurnIn.ts @@ -1,10 +1,13 @@ import type { TurnIn, TimeAnimationOptions, EffectFourCorners } from '../../types'; import { toKeyframeValue, parseDirection, getEntranceFill } from '../../utils'; import { FOUR_CORNERS_DIRECTIONS } from '../../consts'; -const DEFAULT_DIRECTION: EffectFourCorners = 'top-left'; -export function getNames(_: TimeAnimationOptions) { - return ['motion-fadeIn', 'motion-turnIn']; +export const schema = { + pivot: { type: 'enum', values: FOUR_CORNERS_DIRECTIONS, default: 'top-left' }, +} as const; + +export function getNames({ suffix = '' }: TimeAnimationOptions) { + return ['motion-fadeIn', 'motion-turnIn'].map((name) => name + suffix); } const DIRECTION_TO_TRANSFORM_MAP: Record< @@ -23,10 +26,10 @@ export function web(options: TimeAnimationOptions) { export function style(options: TimeAnimationOptions, asWeb = false) { const namedEffect = options.namedEffect as TurnIn; - const direction = parseDirection( - namedEffect?.direction, + const direction = parseDirection( + namedEffect?.pivot, FOUR_CORNERS_DIRECTIONS, - DEFAULT_DIRECTION, + schema.pivot.default, ); const [fadeIn, turnIn] = getNames(options); diff --git a/packages/motion-presets/src/library/entrance/WinkIn.ts b/packages/motion-presets/src/library/entrance/WinkIn.ts index 4ad4dfd1..65511a93 100644 --- a/packages/motion-presets/src/library/entrance/WinkIn.ts +++ b/packages/motion-presets/src/library/entrance/WinkIn.ts @@ -3,10 +3,13 @@ import type { TimeAnimationOptions, WinkIn } from '../../types'; import { AXIS_DIRECTIONS } from '../../consts'; const DIRECTIONS = AXIS_DIRECTIONS; -const DEFAULT_DIRECTION: (typeof DIRECTIONS)[number] = 'horizontal'; -export function getNames(_: TimeAnimationOptions) { - return ['motion-fadeIn', 'motion-winkInClip', 'motion-winkInRotate']; +export const schema = { + direction: { type: 'enum', values: AXIS_DIRECTIONS, default: 'horizontal' }, +} as const; + +export function getNames({ suffix = '' }: TimeAnimationOptions) { + return ['motion-fadeIn', 'motion-winkInClip', 'motion-winkInRotate'].map((name) => name + suffix); } const PARAM_MAP: Record<(typeof DIRECTIONS)[number], { scaleY: number; scaleX: number }> = { @@ -20,7 +23,11 @@ export function web(options: TimeAnimationOptions) { export function style(options: TimeAnimationOptions) { const namedEffect = options.namedEffect as WinkIn; - const direction = parseDirection(namedEffect?.direction, AXIS_DIRECTIONS, DEFAULT_DIRECTION); + const direction = parseDirection<(typeof DIRECTIONS)[number]>( + namedEffect?.direction, + AXIS_DIRECTIONS, + schema.direction.default, + ); const [fadeIn, winkInClip, winkInRotate] = getNames(options); const { scaleX, scaleY } = PARAM_MAP[direction]; diff --git a/packages/motion-presets/src/library/entrance/test/ArcIn.spec.ts b/packages/motion-presets/src/library/entrance/test/ArcIn.spec.ts index a924b1ce..d1cf5ed8 100644 --- a/packages/motion-presets/src/library/entrance/test/ArcIn.spec.ts +++ b/packages/motion-presets/src/library/entrance/test/ArcIn.spec.ts @@ -11,7 +11,7 @@ describe('ArcIn', () => { const mockOptions = { ...baseMockOptions, duration, - namedEffect: { direction } as ArcInType, + namedEffect: { from: direction } as ArcInType, }; const expectedResult: Partial[] = [ @@ -21,11 +21,11 @@ describe('ArcIn', () => { keyframes: [ { transform: - 'perspective(800px) translateZ(calc(-1 * 200px / 2)) rotateX(calc(0 * -1 * 80deg)) rotateY(calc(1 * -1 * 80deg)) translateZ(calc(200px / 2)) rotate(var(--motion-rotate, 0deg))', + 'perspective(800px) translateZ(calc(-1 * 100px)) rotateX(calc(0 * -1 * 80deg)) rotateY(calc(1 * -1 * 80deg)) translateZ(100px) rotate(var(--motion-rotate, 0deg))', }, { transform: - 'perspective(800px) translateZ(calc(-1 * 200px / 2)) rotateX(0deg) rotateY(0deg) translateZ(calc(200px / 2)) rotate(var(--motion-rotate, 0deg))', + 'perspective(800px) translateZ(calc(-1 * 100px)) rotateX(0deg) rotateY(0deg) translateZ(100px) rotate(var(--motion-rotate, 0deg))', }, ], }, @@ -42,7 +42,7 @@ describe('ArcIn', () => { const mockOptions = { ...baseMockOptions, duration, - namedEffect: { direction } as ArcInType, + namedEffect: { from: direction } as ArcInType, }; const expectedResult: Partial[] = [ @@ -52,11 +52,11 @@ describe('ArcIn', () => { keyframes: [ { transform: - 'perspective(800px) translateZ(calc(-1 * 200px / 2)) rotateX(calc(1 * 1 * 80deg)) rotateY(calc(0 * 1 * 80deg)) translateZ(calc(200px / 2)) rotate(var(--motion-rotate, 0deg))', + 'perspective(800px) translateZ(calc(-1 * 100px)) rotateX(calc(1 * 1 * 80deg)) rotateY(calc(0 * 1 * 80deg)) translateZ(100px) rotate(var(--motion-rotate, 0deg))', }, { transform: - 'perspective(800px) translateZ(calc(-1 * 200px / 2)) rotateX(0deg) rotateY(0deg) translateZ(calc(200px / 2)) rotate(var(--motion-rotate, 0deg))', + 'perspective(800px) translateZ(calc(-1 * 100px)) rotateX(0deg) rotateY(0deg) translateZ(100px) rotate(var(--motion-rotate, 0deg))', }, ], }, @@ -73,7 +73,7 @@ describe('ArcIn', () => { const mockOptions = { ...baseMockOptions, duration, - namedEffect: { direction } as ArcInType, + namedEffect: { from: direction } as ArcInType, }; const expectedResult: Partial[] = [ @@ -83,11 +83,11 @@ describe('ArcIn', () => { keyframes: [ { transform: - 'perspective(800px) translateZ(calc(-1 * 200px / 2)) rotateX(calc(1 * -1 * 80deg)) rotateY(calc(0 * -1 * 80deg)) translateZ(calc(200px / 2)) rotate(var(--motion-rotate, 0deg))', + 'perspective(800px) translateZ(calc(-1 * 100px)) rotateX(calc(1 * -1 * 80deg)) rotateY(calc(0 * -1 * 80deg)) translateZ(100px) rotate(var(--motion-rotate, 0deg))', }, { transform: - 'perspective(800px) translateZ(calc(-1 * 200px / 2)) rotateX(0deg) rotateY(0deg) translateZ(calc(200px / 2)) rotate(var(--motion-rotate, 0deg))', + 'perspective(800px) translateZ(calc(-1 * 100px)) rotateX(0deg) rotateY(0deg) translateZ(100px) rotate(var(--motion-rotate, 0deg))', }, ], }, @@ -111,11 +111,11 @@ describe('ArcIn', () => { keyframes: [ { transform: - 'perspective(800px) translateZ(calc(-1 * 200px / 2)) rotateX(calc(0 * 1 * 80deg)) rotateY(calc(1 * 1 * 80deg)) translateZ(calc(200px / 2)) rotate(var(--motion-rotate, 0deg))', + 'perspective(800px) translateZ(calc(-1 * 100px)) rotateX(calc(0 * 1 * 80deg)) rotateY(calc(1 * 1 * 80deg)) translateZ(100px) rotate(var(--motion-rotate, 0deg))', }, { transform: - 'perspective(800px) translateZ(calc(-1 * 200px / 2)) rotateX(0deg) rotateY(0deg) translateZ(calc(200px / 2)) rotate(var(--motion-rotate, 0deg))', + 'perspective(800px) translateZ(calc(-1 * 100px)) rotateX(0deg) rotateY(0deg) translateZ(100px) rotate(var(--motion-rotate, 0deg))', }, ], }, @@ -132,7 +132,7 @@ describe('ArcIn', () => { const mockOptions = { ...baseMockOptions, duration, - namedEffect: { direction } as ArcInType, + namedEffect: { from: direction } as ArcInType, }; const expectedResult: Partial[] = [ @@ -151,8 +151,8 @@ describe('ArcIn', () => { '--motion-arc-x': '0', '--motion-arc-y': '1', '--motion-arc-sign': '-1', - '--motion-depth-negative': 'calc(-1 * 200px / 2)', - '--motion-depth-positive': 'calc(200px / 2)', + '--motion-depth-negative': 'calc(-1 * 100px)', + '--motion-depth-positive': '100px', }, keyframes: [ { @@ -178,7 +178,7 @@ describe('ArcIn', () => { const mockOptions = { ...baseMockOptions, duration, - namedEffect: { direction } as ArcInType, + namedEffect: { from: direction } as ArcInType, }; const expectedResult: Partial[] = [ @@ -197,8 +197,8 @@ describe('ArcIn', () => { '--motion-arc-x': '1', '--motion-arc-y': '0', '--motion-arc-sign': '1', - '--motion-depth-negative': 'calc(-1 * 200px / 2)', - '--motion-depth-positive': 'calc(200px / 2)', + '--motion-depth-negative': 'calc(-1 * 100px)', + '--motion-depth-positive': '100px', }, keyframes: [ { @@ -240,8 +240,8 @@ describe('ArcIn', () => { '--motion-arc-x': '0', '--motion-arc-y': '1', '--motion-arc-sign': '1', - '--motion-depth-negative': 'calc(-1 * 200px / 2)', - '--motion-depth-positive': 'calc(200px / 2)', + '--motion-depth-negative': 'calc(-1 * 100px)', + '--motion-depth-positive': '100px', }, keyframes: [ { @@ -267,7 +267,7 @@ describe('ArcIn', () => { const mockOptions = { ...baseMockOptions, duration, - namedEffect: { direction } as ArcInType, + namedEffect: { from: direction } as ArcInType, }; const expectedResult: Partial[] = [ @@ -286,8 +286,8 @@ describe('ArcIn', () => { '--motion-arc-x': '1', '--motion-arc-y': '0', '--motion-arc-sign': '-1', - '--motion-depth-negative': 'calc(-1 * 200px / 2)', - '--motion-depth-positive': 'calc(200px / 2)', + '--motion-depth-negative': 'calc(-1 * 100px)', + '--motion-depth-positive': '100px', }, keyframes: [ { diff --git a/packages/motion-presets/src/library/entrance/test/BlurIn.spec.ts b/packages/motion-presets/src/library/entrance/test/BlurIn.spec.ts index b969d593..e4d84257 100644 --- a/packages/motion-presets/src/library/entrance/test/BlurIn.spec.ts +++ b/packages/motion-presets/src/library/entrance/test/BlurIn.spec.ts @@ -116,4 +116,19 @@ describe('BlurIn', () => { expect(result).toMatchObject(expectedResult); }); + + test('BlurIn appends the suffix to the animation names', () => { + const mockOptions = { + ...baseMockOptions, + duration: 1000, + suffix: '-1', + namedEffect: {} as BlurInType, + }; + + expect(BlurIn.getNames(mockOptions)).toEqual(['motion-fadeIn-1', 'motion-blurIn-1']); + expect(BlurIn.style(mockOptions).map(({ name }) => name)).toEqual([ + 'motion-fadeIn-1', + 'motion-blurIn-1', + ]); + }); }); diff --git a/packages/motion-presets/src/library/entrance/test/BounceIn.spec.ts b/packages/motion-presets/src/library/entrance/test/BounceIn.spec.ts index aca6937f..a42b2736 100644 --- a/packages/motion-presets/src/library/entrance/test/BounceIn.spec.ts +++ b/packages/motion-presets/src/library/entrance/test/BounceIn.spec.ts @@ -93,7 +93,7 @@ describe('BounceIn', () => { const mockOptions = { ...baseMockOptions, duration, - namedEffect: { distanceFactor: 2 } as BounceInType, + namedEffect: { travel: { value: 100, unit: 'px' } } as BounceInType, }; const expectedResult: Partial[] = [ @@ -174,7 +174,7 @@ describe('BounceIn', () => { const mockOptions = { ...baseMockOptions, duration, - namedEffect: { direction: 'top' } as BounceInType, + namedEffect: { from: 'top' } as BounceInType, }; const expectedResult: Partial[] = [ @@ -255,7 +255,7 @@ describe('BounceIn', () => { const mockOptions = { ...baseMockOptions, duration, - namedEffect: { direction: 'right' } as BounceInType, + namedEffect: { from: 'right' } as BounceInType, }; const expectedResult: Partial[] = [ @@ -336,7 +336,7 @@ describe('BounceIn', () => { const mockOptions = { ...baseMockOptions, duration, - namedEffect: { direction: 'left' } as BounceInType, + namedEffect: { from: 'left' } as BounceInType, }; const expectedResult: Partial[] = [ @@ -412,12 +412,12 @@ describe('BounceIn', () => { expect(result).toMatchObject(expectedResult); }); - test('BounceIn animation with center direction', () => { + test('BounceIn animation with back direction', () => { const duration = 1000; const mockOptions = { ...baseMockOptions, duration, - namedEffect: { direction: 'center' } as BounceInType, + namedEffect: { from: 'back' } as BounceInType, }; const expectedResult: Partial[] = [ @@ -598,7 +598,7 @@ describe('BounceIn style method', () => { const mockOptions = { ...baseMockOptions, duration, - namedEffect: { distanceFactor: 2 } as BounceInType, + namedEffect: { travel: { value: 100, unit: 'px' } } as BounceInType, }; const expectedResult: Partial[] = [ @@ -692,7 +692,7 @@ describe('BounceIn style method', () => { const mockOptions = { ...baseMockOptions, duration, - namedEffect: { direction: 'top' } as BounceInType, + namedEffect: { from: 'top' } as BounceInType, }; const expectedResult: Partial[] = [ @@ -786,7 +786,7 @@ describe('BounceIn style method', () => { const mockOptions = { ...baseMockOptions, duration, - namedEffect: { direction: 'right' } as BounceInType, + namedEffect: { from: 'right' } as BounceInType, }; const expectedResult: Partial[] = [ @@ -880,7 +880,7 @@ describe('BounceIn style method', () => { const mockOptions = { ...baseMockOptions, duration, - namedEffect: { direction: 'left' } as BounceInType, + namedEffect: { from: 'left' } as BounceInType, }; const expectedResult: Partial[] = [ @@ -969,12 +969,12 @@ describe('BounceIn style method', () => { expect(result).toMatchObject(expectedResult); }); - test('BounceIn style with center direction', () => { + test('BounceIn style with back direction', () => { const duration = 1000; const mockOptions = { ...baseMockOptions, duration, - namedEffect: { direction: 'center' } as BounceInType, + namedEffect: { from: 'back' } as BounceInType, }; const expectedResult: Partial[] = [ diff --git a/packages/motion-presets/src/library/entrance/test/CurveIn.spec.ts b/packages/motion-presets/src/library/entrance/test/CurveIn.spec.ts index 550961ea..8e182784 100644 --- a/packages/motion-presets/src/library/entrance/test/CurveIn.spec.ts +++ b/packages/motion-presets/src/library/entrance/test/CurveIn.spec.ts @@ -20,11 +20,11 @@ describe('CurveIn', () => { keyframes: [ { transform: - 'perspective(200px) translateZ(calc(300px * -3)) rotateX(0deg) rotateY(180deg) translateZ(calc(300px * 3)) rotateZ(var(--motion-rotate, 0deg))', + 'perspective(200px) translateZ(calc(-1 * 900px)) rotateX(0deg) rotateY(180deg) translateZ(900px) rotateZ(var(--motion-rotate, 0deg))', }, { transform: - 'perspective(200px) translateZ(calc(300px * -3)) rotateX(0deg) rotateY(0deg) translateZ(calc(300px * 3)) rotateZ(var(--motion-rotate, 0deg))', + 'perspective(200px) translateZ(calc(-1 * 900px)) rotateX(0deg) rotateY(0deg) translateZ(900px) rotateZ(var(--motion-rotate, 0deg))', }, ], }, @@ -43,7 +43,7 @@ describe('CurveIn', () => { const duration = 1500; const mockOptions = { ...baseMockOptions, - namedEffect: { direction: 'left' } as CurveInType, + namedEffect: { from: 'left' } as CurveInType, duration, }; @@ -53,11 +53,11 @@ describe('CurveIn', () => { keyframes: [ { transform: - 'perspective(200px) translateZ(calc(300px * -3)) rotateX(0deg) rotateY(-180deg) translateZ(calc(300px * 3)) rotateZ(var(--motion-rotate, 0deg))', + 'perspective(200px) translateZ(calc(-1 * 900px)) rotateX(0deg) rotateY(-180deg) translateZ(900px) rotateZ(var(--motion-rotate, 0deg))', }, { transform: - 'perspective(200px) translateZ(calc(300px * -3)) rotateX(0deg) rotateY(0deg) translateZ(calc(300px * 3)) rotateZ(var(--motion-rotate, 0deg))', + 'perspective(200px) translateZ(calc(-1 * 900px)) rotateX(0deg) rotateY(0deg) translateZ(900px) rotateZ(var(--motion-rotate, 0deg))', }, ], }, @@ -119,7 +119,7 @@ describe('CurveIn', () => { const duration = 1500; const mockOptions = { ...baseMockOptions, - namedEffect: { direction: 'left' } as CurveInType, + namedEffect: { from: 'left' } as CurveInType, duration, }; diff --git a/packages/motion-presets/src/library/entrance/test/FlipIn.spec.ts b/packages/motion-presets/src/library/entrance/test/FlipIn.spec.ts index 97497d48..4110d6b4 100644 --- a/packages/motion-presets/src/library/entrance/test/FlipIn.spec.ts +++ b/packages/motion-presets/src/library/entrance/test/FlipIn.spec.ts @@ -36,13 +36,13 @@ describe('FlipIn', () => { test('FlipIn animation with custom values', () => { const easing = 'backIn'; - const direction = 'right'; + const direction = 'horizontal'; const mockOptions = { ...baseMockOptions, easing, namedEffect: { direction, - initialRotate: 50, + angle: -50, } as FlipInType, }; @@ -108,13 +108,13 @@ describe('FlipIn', () => { test('FlipIn style with custom values', () => { const easing = 'backIn'; - const direction = 'right'; + const direction = 'horizontal'; const mockOptions = { ...baseMockOptions, easing, namedEffect: { direction, - initialRotate: 50, + angle: -50, } as FlipInType, }; diff --git a/packages/motion-presets/src/library/entrance/test/FloatIn.spec.ts b/packages/motion-presets/src/library/entrance/test/FloatIn.spec.ts index 7fa7da8e..f82d31c0 100644 --- a/packages/motion-presets/src/library/entrance/test/FloatIn.spec.ts +++ b/packages/motion-presets/src/library/entrance/test/FloatIn.spec.ts @@ -37,7 +37,7 @@ describe('FloatIn', () => { test('FloatIn animation with custom direction', () => { const mockOptions = { ...baseMockOptions, - namedEffect: { direction: 'top' } as FloatInType, + namedEffect: { from: 'top' } as FloatInType, }; const expectedResult: Partial[] = [ @@ -101,7 +101,7 @@ describe('FloatIn', () => { test('FloatIn style animation with custom direction', () => { const mockOptions = { ...baseMockOptions, - namedEffect: { direction: 'top' } as FloatInType, + namedEffect: { from: 'top' } as FloatInType, }; const expectedResult: Partial[] = [ diff --git a/packages/motion-presets/src/library/entrance/test/FoldIn.spec.ts b/packages/motion-presets/src/library/entrance/test/FoldIn.spec.ts index 3915e44b..1689d695 100644 --- a/packages/motion-presets/src/library/entrance/test/FoldIn.spec.ts +++ b/packages/motion-presets/src/library/entrance/test/FoldIn.spec.ts @@ -41,8 +41,8 @@ describe('FoldIn', () => { ...baseMockOptions, easing: 'cubicInOut', namedEffect: { - direction: 'left', - initialRotate: 45, + pivot: 'left', + angle: -45, } as FoldInType, }; @@ -117,8 +117,8 @@ describe('FoldIn', () => { ...baseMockOptions, easing: 'cubicInOut', namedEffect: { - direction: 'left', - initialRotate: 45, + pivot: 'left', + angle: -45, } as FoldInType, }; diff --git a/packages/motion-presets/src/library/entrance/test/GlideIn.spec.ts b/packages/motion-presets/src/library/entrance/test/GlideIn.spec.ts index 5f990804..97692b3e 100644 --- a/packages/motion-presets/src/library/entrance/test/GlideIn.spec.ts +++ b/packages/motion-presets/src/library/entrance/test/GlideIn.spec.ts @@ -33,12 +33,12 @@ describe('GlideIn', () => { expect(result).toMatchObject(expectedResult); }); - test('GlideIn animation with custom direction and distance', () => { - const direction = 45; - const distance = { value: 200, unit: 'px' }; + test('GlideIn animation with custom from and travel', () => { + const from = 315; + const travel = { value: 200, unit: 'px' }; const mockOptions = { ...baseMockOptions, - namedEffect: { direction, distance } as GlideInType, + namedEffect: { from, travel } as GlideInType, }; const expectedResult: Partial[] = [ @@ -98,12 +98,12 @@ describe('GlideIn', () => { expect(result).toMatchObject(expectedResult); }); - test('GlideIn style animation with custom direction and distance', () => { - const direction = 45; - const distance = { value: 200, unit: 'px' }; + test('GlideIn style animation with custom from and travel', () => { + const from = 315; + const travel = { value: 200, unit: 'px' }; const mockOptions = { ...baseMockOptions, - namedEffect: { direction, distance } as GlideInType, + namedEffect: { from, travel } as GlideInType, }; const expectedResult: Partial[] = [ diff --git a/packages/motion-presets/src/library/entrance/test/RevealIn.spec.ts b/packages/motion-presets/src/library/entrance/test/RevealIn.spec.ts index 3bfb1066..5d093bf5 100644 --- a/packages/motion-presets/src/library/entrance/test/RevealIn.spec.ts +++ b/packages/motion-presets/src/library/entrance/test/RevealIn.spec.ts @@ -39,7 +39,7 @@ describe('RevealIn', () => { test('RevealIn animation with custom direction', () => { const mockOptions = { ...baseMockOptions, - namedEffect: { direction: 'right' } as RevealInType, + namedEffect: { from: 'right' } as RevealInType, }; const expectedResult: Partial[] = [ @@ -103,7 +103,7 @@ describe('RevealIn', () => { test('RevealIn style with custom direction', () => { const mockOptions = { ...baseMockOptions, - namedEffect: { direction: 'right' } as RevealInType, + namedEffect: { from: 'right' } as RevealInType, }; const expectedResult: Partial[] = [ diff --git a/packages/motion-presets/src/library/entrance/test/ShapeIn.spec.ts b/packages/motion-presets/src/library/entrance/test/ShapeIn.spec.ts index e63d6295..edd66bc2 100644 --- a/packages/motion-presets/src/library/entrance/test/ShapeIn.spec.ts +++ b/packages/motion-presets/src/library/entrance/test/ShapeIn.spec.ts @@ -259,4 +259,19 @@ describe('ShapeIn', () => { expect(result).toMatchObject(expectedResult); }); + + test('ShapeIn appends the suffix to its names', () => { + const mockOptions = { + ...baseMockOptions, + duration: 1000, + suffix: '-a', + namedEffect: {} as ShapeInType, + }; + + expect(ShapeIn.getNames(mockOptions)).toEqual(['motion-fadeIn-a', 'motion-shapeIn-a']); + expect(ShapeIn.style(mockOptions).map(({ name }) => name)).toEqual([ + 'motion-fadeIn-a', + 'motion-shapeIn-a', + ]); + }); }); diff --git a/packages/motion-presets/src/library/entrance/test/ShuttersIn.spec.ts b/packages/motion-presets/src/library/entrance/test/ShuttersIn.spec.ts index 5001baf3..eae033f6 100644 --- a/packages/motion-presets/src/library/entrance/test/ShuttersIn.spec.ts +++ b/packages/motion-presets/src/library/entrance/test/ShuttersIn.spec.ts @@ -10,10 +10,10 @@ const baseMockOptions: TimeAnimationOptions = { }; describe('ShuttersIn.web()', () => { - test('default options (right direction)', () => { + test('default options (from left)', () => { const mockOptions: TimeAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'right' } as ShuttersInType, + namedEffect: { from: 'left' } as ShuttersInType, }; const expectedResult = [ @@ -40,10 +40,10 @@ describe('ShuttersIn.web()', () => { expect(result).toMatchObject(expectedResult); }); - test('left direction', () => { + test('from right', () => { const mockOptions: TimeAnimationOptions = { ...baseMockOptions, - namedEffect: { type: 'ShuttersIn', direction: 'left' } as ShuttersInType, + namedEffect: { type: 'ShuttersIn', from: 'right' } as ShuttersInType, }; const expectedResult = [ @@ -74,7 +74,7 @@ describe('ShuttersIn.web()', () => { ...baseMockOptions, namedEffect: { type: 'ShuttersIn', - direction: 'right', + from: 'left', shutters: 6, } as ShuttersInType, }; @@ -107,7 +107,7 @@ describe('ShuttersIn.web()', () => { ...baseMockOptions, namedEffect: { type: 'ShuttersIn', - direction: 'right', + from: 'left', staggered: false, } as ShuttersInType, }; @@ -139,7 +139,7 @@ describe('ShuttersIn.web()', () => { const mockOptions: TimeAnimationOptions = { ...baseMockOptions, easing: 'easeInOutQuad', - namedEffect: { type: 'ShuttersIn', direction: 'right' } as ShuttersInType, + namedEffect: { type: 'ShuttersIn', from: 'left' } as ShuttersInType, }; const expectedResult = [ @@ -169,10 +169,10 @@ describe('ShuttersIn.web()', () => { }); describe('ShuttersIn.style()', () => { - test('default options (right direction)', () => { + test('default options (from left)', () => { const mockOptions: TimeAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'right' } as ShuttersInType, + namedEffect: { from: 'left' } as ShuttersInType, }; const expectedResult = [ @@ -206,10 +206,10 @@ describe('ShuttersIn.style()', () => { expect(result).toMatchObject(expectedResult); }); - test('left direction', () => { + test('from right', () => { const mockOptions: TimeAnimationOptions = { ...baseMockOptions, - namedEffect: { type: 'ShuttersIn', direction: 'left' } as ShuttersInType, + namedEffect: { type: 'ShuttersIn', from: 'right' } as ShuttersInType, }; const expectedResult = [ @@ -247,7 +247,7 @@ describe('ShuttersIn.style()', () => { ...baseMockOptions, namedEffect: { type: 'ShuttersIn', - direction: 'right', + from: 'left', shutters: 6, } as ShuttersInType, }; @@ -287,7 +287,7 @@ describe('ShuttersIn.style()', () => { ...baseMockOptions, namedEffect: { type: 'ShuttersIn', - direction: 'right', + from: 'left', staggered: false, } as ShuttersInType, }; @@ -326,7 +326,7 @@ describe('ShuttersIn.style()', () => { const mockOptions: TimeAnimationOptions = { ...baseMockOptions, easing: 'easeInOutQuad', - namedEffect: { type: 'ShuttersIn', direction: 'right' } as ShuttersInType, + namedEffect: { type: 'ShuttersIn', from: 'left' } as ShuttersInType, }; const expectedResult = [ diff --git a/packages/motion-presets/src/library/entrance/test/SlideIn.spec.ts b/packages/motion-presets/src/library/entrance/test/SlideIn.spec.ts index fa9bc160..8313bd99 100644 --- a/packages/motion-presets/src/library/entrance/test/SlideIn.spec.ts +++ b/packages/motion-presets/src/library/entrance/test/SlideIn.spec.ts @@ -48,7 +48,7 @@ describe('SlideIn', () => { test('SlideIn animation with custom direction', () => { const mockOptions = { ...baseMockOptions, - namedEffect: { direction: 'right', initialTranslate: 1 } as SlideInType, + namedEffect: { from: 'right', start: 0 } as SlideInType, }; const expectedResult: Partial[] = [ @@ -128,7 +128,7 @@ describe('SlideIn', () => { test('SlideIn style with custom direction', () => { const mockOptions = { ...baseMockOptions, - namedEffect: { direction: 'right', initialTranslate: 1 } as SlideInType, + namedEffect: { from: 'right', start: 0 } as SlideInType, }; const expectedResult: Partial[] = [ diff --git a/packages/motion-presets/src/library/entrance/test/SpinIn.spec.ts b/packages/motion-presets/src/library/entrance/test/SpinIn.spec.ts index 3114b16a..e67476e9 100644 --- a/packages/motion-presets/src/library/entrance/test/SpinIn.spec.ts +++ b/packages/motion-presets/src/library/entrance/test/SpinIn.spec.ts @@ -44,7 +44,7 @@ describe('SpinIn', () => { namedEffect: { direction: 'counter-clockwise', spins: 2, - initialScale: 1, + scale: 1, } as SpinInType, }; @@ -120,7 +120,7 @@ describe('SpinIn', () => { namedEffect: { direction: 'counter-clockwise', spins: 2, - initialScale: 1, + scale: 1, } as SpinInType, }; diff --git a/packages/motion-presets/src/library/entrance/test/TiltIn.spec.ts b/packages/motion-presets/src/library/entrance/test/TiltIn.spec.ts index 67b616a8..b6e8e3af 100644 --- a/packages/motion-presets/src/library/entrance/test/TiltIn.spec.ts +++ b/packages/motion-presets/src/library/entrance/test/TiltIn.spec.ts @@ -48,7 +48,7 @@ describe('TiltIn', () => { ...baseMockOptions, easing, duration, - namedEffect: { direction: 'right' } as TiltInType, + namedEffect: { from: 'right' } as TiltInType, }; const expectedResult: Partial[] = [ @@ -97,8 +97,8 @@ describe('TiltIn', () => { easing: 'cubicOut', custom: { '--motion-perspective': '800px', - '--motion-depth-negative': 'calc(200px / 2 * -1)', - '--motion-depth-positive': 'calc(200px / 2)', + '--motion-depth-negative': 'calc(100px * -1)', + '--motion-depth-positive': '100px', }, keyframes: [ { @@ -145,7 +145,7 @@ describe('TiltIn', () => { ...baseMockOptions, easing, duration, - namedEffect: { direction: 'right' } as TiltInType, + namedEffect: { from: 'right' } as TiltInType, }; const expectedResult: Partial[] = [ @@ -161,8 +161,8 @@ describe('TiltIn', () => { easing, custom: { '--motion-perspective': '800px', - '--motion-depth-negative': 'calc(200px / 2 * -1)', - '--motion-depth-positive': 'calc(200px / 2)', + '--motion-depth-negative': 'calc(100px * -1)', + '--motion-depth-positive': '100px', }, keyframes: [ { diff --git a/packages/motion-presets/src/library/entrance/test/TurnIn.spec.ts b/packages/motion-presets/src/library/entrance/test/TurnIn.spec.ts index 872a3b8f..13e0e922 100644 --- a/packages/motion-presets/src/library/entrance/test/TurnIn.spec.ts +++ b/packages/motion-presets/src/library/entrance/test/TurnIn.spec.ts @@ -39,7 +39,7 @@ describe('TurnIn', () => { test('TurnIn animation with bottom-right direction', () => { const mockOptions = { ...baseMockOptions, - namedEffect: { direction: 'bottom-right' } as TurnInType, + namedEffect: { pivot: 'bottom-right' } as TurnInType, }; const expectedResult: Partial[] = [ @@ -65,7 +65,7 @@ describe('TurnIn', () => { test('TurnIn animation with top-right direction', () => { const mockOptions = { ...baseMockOptions, - namedEffect: { direction: 'top-right' } as TurnInType, + namedEffect: { pivot: 'top-right' } as TurnInType, }; const expectedResult: Partial[] = [ @@ -91,7 +91,7 @@ describe('TurnIn', () => { test('TurnIn animation with bottom-left direction', () => { const mockOptions = { ...baseMockOptions, - namedEffect: { direction: 'bottom-left' } as TurnInType, + namedEffect: { pivot: 'bottom-left' } as TurnInType, }; const expectedResult: Partial[] = [ @@ -184,7 +184,7 @@ describe('TurnIn', () => { test('TurnIn.style animation with bottom-right direction', () => { const mockOptions = { ...baseMockOptions, - namedEffect: { direction: 'bottom-right' } as TurnInType, + namedEffect: { pivot: 'bottom-right' } as TurnInType, }; const expectedResult: Partial[] = [ diff --git a/packages/motion-presets/src/library/mouse/AiryMouse.ts b/packages/motion-presets/src/library/mouse/AiryMouse.ts index a363e370..c177b633 100644 --- a/packages/motion-presets/src/library/mouse/AiryMouse.ts +++ b/packages/motion-presets/src/library/mouse/AiryMouse.ts @@ -14,11 +14,15 @@ import type { } from '../../types'; import { CustomMouse } from './CustomMouse'; -const DEFAULT_DISTANCE = { value: 200, unit: 'px' }; -const DEFAULT_ANGLE = 30; -const DEFAULT_AXIS: MouseEffectAxis = 'both'; const AXES = ['both', 'horizontal', 'vertical'] as const; +export const schema = { + angle: { type: 'number', default: 30 }, + axis: { type: 'enum', values: AXES, default: 'both' }, + distance: { type: 'length', default: { value: 200, unit: 'px' } }, + inverted: { type: 'bool', default: false }, +} as const; + class AiryMouseAnimation extends CustomMouse { progress({ x: progressX, y: progressY }: Progress) { let translateX = 0; @@ -48,10 +52,10 @@ class AiryMouseAnimation extends CustomMouse { export default function create(options: ScrubAnimationOptions & AnimationExtraOptions) { const { transitionDuration, transitionEasing } = options; const namedEffect = options.namedEffect as AiryMouse; - const inverted = namedEffect.inverted ?? false; - const distance = parseLength(namedEffect.distance, DEFAULT_DISTANCE); - const angle = parseDirection(namedEffect.angle, [], DEFAULT_ANGLE, true) as number; - const axis = parseDirection(namedEffect.axis, AXES, DEFAULT_AXIS) as MouseEffectAxis; + const inverted = namedEffect.inverted ?? schema.inverted.default; + const distance = parseLength(namedEffect.distance, schema.distance.default); + const angle = parseDirection(namedEffect.angle, [], schema.angle.default, true) as number; + const axis = parseDirection(namedEffect.axis, AXES, schema.axis.default) as MouseEffectAxis; const invert = inverted ? -1 : 1; const animationOptions = { transition: transitionDuration @@ -65,3 +69,5 @@ export default function create(options: ScrubAnimationOptions & AnimationExtraOp return (target: HTMLElement) => new AiryMouseAnimation(target, animationOptions); } + +create.schema = schema; diff --git a/packages/motion-presets/src/library/mouse/BlobMouse.ts b/packages/motion-presets/src/library/mouse/BlobMouse.ts index d99aa37e..94ea9194 100644 --- a/packages/motion-presets/src/library/mouse/BlobMouse.ts +++ b/packages/motion-presets/src/library/mouse/BlobMouse.ts @@ -2,7 +2,11 @@ import { getCssUnits, getMouseTransitionEasing, mapRange, parseLength } from '.. import { CustomMouse } from './CustomMouse'; import { ScrubAnimationOptions, AnimationExtraOptions, BlobMouse, Progress } from '../../types'; -const DEFAULT_DISTANCE = { value: 200, unit: 'px' }; +export const schema = { + distance: { type: 'length', default: { value: 200, unit: 'px' } }, + inverted: { type: 'bool', default: false }, + scale: { type: 'number', min: 0, default: 1.4 }, +} as const; class BlobMouseAnimation extends CustomMouse { progress({ x: progressX, y: progressY }: Progress) { @@ -36,8 +40,8 @@ class BlobMouseAnimation extends CustomMouse { export default function create(options: ScrubAnimationOptions & AnimationExtraOptions) { const { transitionDuration, transitionEasing } = options; const namedEffect = options.namedEffect as BlobMouse; - const { inverted = false, scale = 1.4 } = namedEffect; - const distance = parseLength(namedEffect.distance, DEFAULT_DISTANCE); + const { inverted = schema.inverted.default, scale = schema.scale.default } = namedEffect; + const distance = parseLength(namedEffect.distance, schema.distance.default); const invert = inverted ? -1 : 1; const animationOptions = { @@ -51,3 +55,5 @@ export default function create(options: ScrubAnimationOptions & AnimationExtraOp return (target: HTMLElement) => new BlobMouseAnimation(target, animationOptions); } + +create.schema = schema; diff --git a/packages/motion-presets/src/library/mouse/BlurMouse.ts b/packages/motion-presets/src/library/mouse/BlurMouse.ts index 52bea0d8..ab6fbb70 100644 --- a/packages/motion-presets/src/library/mouse/BlurMouse.ts +++ b/packages/motion-presets/src/library/mouse/BlurMouse.ts @@ -10,8 +10,14 @@ import { quadInOut } from '@wix/motion'; import { CustomMouse } from './CustomMouse'; import { ScrubAnimationOptions, AnimationExtraOptions, BlurMouse, Progress } from '../../types'; -const DEFAULT_DISTANCE = { value: 80, unit: 'px' }; -const DEFAULT_ANGLE = 5; +export const schema = { + angle: { type: 'number', default: 5 }, + blur: { type: 'number', min: 0, default: 20 }, + distance: { type: 'length', default: { value: 80, unit: 'px' } }, + inverted: { type: 'bool', default: false }, + perspective: { type: 'number', min: 0, default: 600 }, + scale: { type: 'number', min: 0, default: 0.3 }, +} as const; class BlurMouseAnimation extends CustomMouse { progress({ x: progressX, y: progressY }: Progress) { @@ -61,10 +67,14 @@ class BlurMouseAnimation extends CustomMouse { export default function create(options: ScrubAnimationOptions & AnimationExtraOptions) { const { transitionDuration, transitionEasing } = options; const namedEffect = options.namedEffect as BlurMouse; - const inverted = namedEffect.inverted ?? false; - const distance = parseLength(namedEffect.distance, DEFAULT_DISTANCE); - const angle = parseDirection(namedEffect.angle, [], DEFAULT_ANGLE, true) as number; - const { scale = 0.3, blur = 20, perspective = 600 } = namedEffect; + const inverted = namedEffect.inverted ?? schema.inverted.default; + const distance = parseLength(namedEffect.distance, schema.distance.default); + const angle = parseDirection(namedEffect.angle, [], schema.angle.default, true) as number; + const { + scale = schema.scale.default, + blur = schema.blur.default, + perspective = schema.perspective.default, + } = namedEffect; const invert = inverted ? -1 : 1; const animationOptions = { transition: transitionDuration @@ -82,3 +92,5 @@ export default function create(options: ScrubAnimationOptions & AnimationExtraOp return (target: HTMLElement) => new BlurMouseAnimation(target, animationOptions); } + +create.schema = schema; diff --git a/packages/motion-presets/src/library/mouse/BounceMouse.ts b/packages/motion-presets/src/library/mouse/BounceMouse.ts index 55924bc0..81cd339c 100644 --- a/packages/motion-presets/src/library/mouse/BounceMouse.ts +++ b/packages/motion-presets/src/library/mouse/BounceMouse.ts @@ -1,12 +1,16 @@ import { ScrubAnimationOptions, AnimationExtraOptions, BounceMouse, TrackMouse } from '../../types'; import { parseLength } from '../../utils'; -import createTrack from './TrackMouse'; +import createTrack, { AXES } from './TrackMouse'; -const DEFAULT_DISTANCE = { value: 80, unit: 'px' }; +export const schema = { + axis: { type: 'enum', values: AXES, default: 'both' }, + distance: { type: 'length', default: { value: 80, unit: 'px' } }, + inverted: { type: 'bool', default: false }, +} as const; export default function create(options: ScrubAnimationOptions & AnimationExtraOptions) { const namedEffect = options.namedEffect as BounceMouse; - const distance = parseLength(namedEffect.distance, DEFAULT_DISTANCE); + const distance = parseLength(namedEffect.distance, schema.distance.default); const { transitionEasing = 'elastic' } = options; return createTrack({ ...options, @@ -14,3 +18,5 @@ export default function create(options: ScrubAnimationOptions & AnimationExtraOp namedEffect: { ...options.namedEffect, distance } as TrackMouse, }); } + +create.schema = schema; diff --git a/packages/motion-presets/src/library/mouse/ScaleMouse.ts b/packages/motion-presets/src/library/mouse/ScaleMouse.ts index 1bb26f37..2c41dac0 100644 --- a/packages/motion-presets/src/library/mouse/ScaleMouse.ts +++ b/packages/motion-presets/src/library/mouse/ScaleMouse.ts @@ -14,10 +14,15 @@ import { MouseEffectAxis, } from '../../types'; -const DEFAULT_DISTANCE = { value: 80, unit: 'px' }; -const DEFAULT_AXIS: MouseEffectAxis = 'both'; const AXES = ['both', 'horizontal', 'vertical'] as const; +export const schema = { + axis: { type: 'enum', values: AXES, default: 'both' }, + distance: { type: 'length', default: { value: 80, unit: 'px' } }, + inverted: { type: 'bool', default: false }, + scale: { type: 'number', min: 0, default: 1.4 }, +} as const; + class ScaleMouseAnimation extends CustomMouse { progress({ x: progressX, y: progressY }: Progress) { const { distance, scale, invert, axis } = this.options; @@ -61,10 +66,10 @@ class ScaleMouseAnimation extends CustomMouse { export default function create(options: ScrubAnimationOptions & AnimationExtraOptions) { const { transitionDuration, transitionEasing } = options; const namedEffect = options.namedEffect as ScaleMouse; - const inverted = namedEffect.inverted ?? false; - const distance = parseLength(namedEffect.distance, DEFAULT_DISTANCE); - const axis = parseDirection(namedEffect.axis, AXES, DEFAULT_AXIS) as MouseEffectAxis; - const { scale = 1.4 } = namedEffect; + const inverted = namedEffect.inverted ?? schema.inverted.default; + const distance = parseLength(namedEffect.distance, schema.distance.default); + const axis = parseDirection(namedEffect.axis, AXES, schema.axis.default) as MouseEffectAxis; + const { scale = schema.scale.default } = namedEffect; const invert = inverted ? -1 : 1; const animationOptions = { transition: transitionDuration @@ -78,3 +83,5 @@ export default function create(options: ScrubAnimationOptions & AnimationExtraOp return (target: HTMLElement) => new ScaleMouseAnimation(target, animationOptions); } + +create.schema = schema; diff --git a/packages/motion-presets/src/library/mouse/SkewMouse.ts b/packages/motion-presets/src/library/mouse/SkewMouse.ts index f11652b0..04b6e6ec 100644 --- a/packages/motion-presets/src/library/mouse/SkewMouse.ts +++ b/packages/motion-presets/src/library/mouse/SkewMouse.ts @@ -15,11 +15,15 @@ import { import { circInOut } from '@wix/motion'; import { CustomMouse } from './CustomMouse'; -const DEFAULT_DISTANCE = { value: 200, unit: 'px' }; -const DEFAULT_ANGLE = 25; -const DEFAULT_AXIS: MouseEffectAxis = 'both'; const AXES = ['both', 'horizontal', 'vertical'] as const; +export const schema = { + angle: { type: 'number', default: 25 }, + axis: { type: 'enum', values: AXES, default: 'both' }, + distance: { type: 'length', default: { value: 200, unit: 'px' } }, + inverted: { type: 'bool', default: false }, +} as const; + class SkewMouseAnimation extends CustomMouse { progress({ x: progressX, y: progressY }: Progress) { let translateX = 0; @@ -65,10 +69,10 @@ class SkewMouseAnimation extends CustomMouse { export default function create(options: ScrubAnimationOptions & AnimationExtraOptions) { const { transitionDuration, transitionEasing } = options; const namedEffect = options.namedEffect as SkewMouse; - const inverted = namedEffect.inverted ?? false; - const distance = parseLength(namedEffect.distance, DEFAULT_DISTANCE); - const angle = parseDirection(namedEffect.angle, [], DEFAULT_ANGLE, true) as number; - const axis = parseDirection(namedEffect.axis, AXES, DEFAULT_AXIS) as MouseEffectAxis; + const inverted = namedEffect.inverted ?? schema.inverted.default; + const distance = parseLength(namedEffect.distance, schema.distance.default); + const angle = parseDirection(namedEffect.angle, [], schema.angle.default, true) as number; + const axis = parseDirection(namedEffect.axis, AXES, schema.axis.default) as MouseEffectAxis; const invert = inverted ? -1 : 1; const animationOptions = { transition: transitionDuration @@ -82,3 +86,5 @@ export default function create(options: ScrubAnimationOptions & AnimationExtraOp return (target: HTMLElement) => new SkewMouseAnimation(target, animationOptions); } + +create.schema = schema; diff --git a/packages/motion-presets/src/library/mouse/SpinMouse.ts b/packages/motion-presets/src/library/mouse/SpinMouse.ts index 286f3efb..94b4a4f0 100644 --- a/packages/motion-presets/src/library/mouse/SpinMouse.ts +++ b/packages/motion-presets/src/library/mouse/SpinMouse.ts @@ -8,9 +8,13 @@ import { import { getMouseTransitionEasing, getAngleInDeg, parseDirection } from '../../utils'; import { CustomMouse } from './CustomMouse'; -const DEFAULT_AXIS: MouseEffectAxis = 'both'; const AXES = ['both', 'horizontal', 'vertical'] as const; +export const schema = { + axis: { type: 'enum', values: AXES, default: 'both' }, + inverted: { type: 'bool', default: false }, +} as const; + class SpinMouseAnimation extends CustomMouse { progress({ x: progressX, y: progressY }: Progress) { const { invert, axis } = this.options; @@ -34,8 +38,8 @@ class SpinMouseAnimation extends CustomMouse { export default function create(options: ScrubAnimationOptions & AnimationExtraOptions) { const { transitionDuration, transitionEasing = 'linear' } = options; const namedEffect = options.namedEffect as SpinMouse; - const inverted = namedEffect.inverted ?? false; - const axis = parseDirection(namedEffect.axis, AXES, DEFAULT_AXIS) as MouseEffectAxis; + const inverted = namedEffect.inverted ?? schema.inverted.default; + const axis = parseDirection(namedEffect.axis, AXES, schema.axis.default) as MouseEffectAxis; const invert = inverted ? -1 : 1; const animationOptions = { transition: transitionDuration @@ -51,3 +55,5 @@ export default function create(options: ScrubAnimationOptions & AnimationExtraOp export function web(options: ScrubAnimationOptions & AnimationExtraOptions) { return create(options); } + +create.schema = schema; diff --git a/packages/motion-presets/src/library/mouse/SwivelMouse.ts b/packages/motion-presets/src/library/mouse/SwivelMouse.ts index d7751118..82c8af8c 100644 --- a/packages/motion-presets/src/library/mouse/SwivelMouse.ts +++ b/packages/motion-presets/src/library/mouse/SwivelMouse.ts @@ -8,10 +8,15 @@ import { import { getMouseTransitionEasing, mapRange, parseDirection } from '../../utils'; import { CustomMouse } from './CustomMouse'; -const DEFAULT_ANGLE = 5; -const DEFAULT_PIVOT_AXIS: MousePivotAxis = 'center-horizontal'; const AXES = ['top', 'bottom', 'right', 'left', 'center-horizontal', 'center-vertical'] as const; +export const schema = { + angle: { type: 'number', default: 5 }, + inverted: { type: 'bool', default: false }, + perspective: { type: 'number', min: 0, default: 800 }, + pivotAxis: { type: 'enum', values: AXES, default: 'center-horizontal' }, +} as const; + const transformOrigins: Record = { top: [0, -50], bottom: [0, 50], @@ -51,14 +56,14 @@ class SwivelMouseAnimation extends CustomMouse { export default function create(options: ScrubAnimationOptions & AnimationExtraOptions) { const { transitionDuration, transitionEasing } = options; const namedEffect = options.namedEffect as SwivelMouse; - const inverted = namedEffect.inverted ?? false; - const angle = parseDirection(namedEffect.angle, [], DEFAULT_ANGLE, true) as number; + const inverted = namedEffect.inverted ?? schema.inverted.default; + const angle = parseDirection(namedEffect.angle, [], schema.angle.default, true) as number; const pivotAxis = parseDirection( namedEffect.pivotAxis, AXES, - DEFAULT_PIVOT_AXIS, + schema.pivotAxis.default, ) as MousePivotAxis; - const { perspective = 800 } = namedEffect; + const { perspective = schema.perspective.default } = namedEffect; const invert = inverted ? -1 : 1; const animationOptions = { transition: transitionDuration @@ -72,3 +77,5 @@ export default function create(options: ScrubAnimationOptions & AnimationExtraOp return (target: HTMLElement) => new SwivelMouseAnimation(target, animationOptions); } + +create.schema = schema; diff --git a/packages/motion-presets/src/library/mouse/Tilt3DMouse.ts b/packages/motion-presets/src/library/mouse/Tilt3DMouse.ts index 01d4593d..ea00708f 100644 --- a/packages/motion-presets/src/library/mouse/Tilt3DMouse.ts +++ b/packages/motion-presets/src/library/mouse/Tilt3DMouse.ts @@ -2,7 +2,11 @@ import { getMouseTransitionEasing, mapRange, parseDirection } from '../../utils' import { CustomMouse } from './CustomMouse'; import { ScrubAnimationOptions, AnimationExtraOptions, Tilt3DMouse, Progress } from '../../types'; -const DEFAULT_ANGLE = 5; +export const schema = { + angle: { type: 'number', default: 5 }, + inverted: { type: 'bool', default: false }, + perspective: { type: 'number', min: 0, default: 800 }, +} as const; class Tilt3DMouseAnimation extends CustomMouse { progress({ x: progressX, y: progressY }: Progress) { @@ -24,9 +28,9 @@ class Tilt3DMouseAnimation extends CustomMouse { export default function create(options: ScrubAnimationOptions & AnimationExtraOptions) { const { transitionDuration, transitionEasing } = options; const namedEffect = options.namedEffect as Tilt3DMouse; - const inverted = namedEffect.inverted ?? false; - const angle = parseDirection(namedEffect.angle, [], DEFAULT_ANGLE, true) as number; - const { perspective = 800 } = namedEffect; + const inverted = namedEffect.inverted ?? schema.inverted.default; + const angle = parseDirection(namedEffect.angle, [], schema.angle.default, true) as number; + const { perspective = schema.perspective.default } = namedEffect; const invert = inverted ? -1 : 1; const animationOptions = { transition: transitionDuration @@ -39,3 +43,5 @@ export default function create(options: ScrubAnimationOptions & AnimationExtraOp return (target: HTMLElement) => new Tilt3DMouseAnimation(target, animationOptions); } + +create.schema = schema; diff --git a/packages/motion-presets/src/library/mouse/Track3DMouse.ts b/packages/motion-presets/src/library/mouse/Track3DMouse.ts index a747264f..b87b0aa2 100644 --- a/packages/motion-presets/src/library/mouse/Track3DMouse.ts +++ b/packages/motion-presets/src/library/mouse/Track3DMouse.ts @@ -14,11 +14,16 @@ import { } from '../../utils'; import { CustomMouse } from './CustomMouse'; -const DEFAULT_DISTANCE = { value: 200, unit: 'px' }; -const DEFAULT_ANGLE = 5; -const DEFAULT_AXIS: MouseEffectAxis = 'both'; const AXES = ['both', 'horizontal', 'vertical'] as const; +export const schema = { + angle: { type: 'number', default: 5 }, + axis: { type: 'enum', values: AXES, default: 'both' }, + distance: { type: 'length', default: { value: 200, unit: 'px' } }, + inverted: { type: 'bool', default: false }, + perspective: { type: 'number', min: 0, default: 800 }, +} as const; + class Track3DMouseAnimation extends CustomMouse { progress({ x: progressX, y: progressY }: Progress) { const { invert, distance, angle, axis, perspective } = this.options; @@ -53,11 +58,11 @@ class Track3DMouseAnimation extends CustomMouse { export default function create(options: ScrubAnimationOptions & AnimationExtraOptions) { const { transitionDuration, transitionEasing } = options; const namedEffect = options.namedEffect as Track3DMouse; - const inverted = namedEffect.inverted ?? false; - const distance = parseLength(namedEffect.distance, DEFAULT_DISTANCE); - const angle = parseDirection(namedEffect.angle, [], DEFAULT_ANGLE, true) as number; - const axis = parseDirection(namedEffect.axis, AXES, DEFAULT_AXIS) as MouseEffectAxis; - const { perspective = 800 } = namedEffect; + const inverted = namedEffect.inverted ?? schema.inverted.default; + const distance = parseLength(namedEffect.distance, schema.distance.default); + const angle = parseDirection(namedEffect.angle, [], schema.angle.default, true) as number; + const axis = parseDirection(namedEffect.axis, AXES, schema.axis.default) as MouseEffectAxis; + const { perspective = schema.perspective.default } = namedEffect; const invert = inverted ? -1 : 1; const animationOptions = { transition: transitionDuration @@ -72,3 +77,5 @@ export default function create(options: ScrubAnimationOptions & AnimationExtraOp return (target: HTMLElement) => new Track3DMouseAnimation(target, animationOptions); } + +create.schema = schema; diff --git a/packages/motion-presets/src/library/mouse/TrackMouse.ts b/packages/motion-presets/src/library/mouse/TrackMouse.ts index d3abac94..30564c1c 100644 --- a/packages/motion-presets/src/library/mouse/TrackMouse.ts +++ b/packages/motion-presets/src/library/mouse/TrackMouse.ts @@ -14,9 +14,13 @@ import { } from '../../utils'; import { CustomMouse } from './CustomMouse'; -const DEFAULT_DISTANCE = { value: 200, unit: 'px' }; -const DEFAULT_AXIS: MouseEffectAxis = 'both'; -const AXES = ['both', 'horizontal', 'vertical'] as const; +export const AXES = ['both', 'horizontal', 'vertical'] as const; + +export const schema = { + axis: { type: 'enum', values: AXES, default: 'both' }, + distance: { type: 'length', default: { value: 200, unit: 'px' } }, + inverted: { type: 'bool', default: false }, +} as const; class TrackMouseAnimation extends CustomMouse { progress({ x: progressX, y: progressY }: Progress) { @@ -49,9 +53,9 @@ class TrackMouseAnimation extends CustomMouse { export default function create(options: ScrubAnimationOptions & AnimationExtraOptions) { const { transitionDuration, transitionEasing } = options; const namedEffect = options.namedEffect as TrackMouse; - const inverted = namedEffect.inverted ?? false; - const distance = parseLength(namedEffect.distance, DEFAULT_DISTANCE); - const axis = parseDirection(namedEffect.axis, AXES, DEFAULT_AXIS) as MouseEffectAxis; + const inverted = namedEffect.inverted ?? schema.inverted.default; + const distance = parseLength(namedEffect.distance, schema.distance.default); + const axis = parseDirection(namedEffect.axis, AXES, schema.axis.default) as MouseEffectAxis; const invert = inverted ? -1 : 1; const animationOptions = { transition: transitionDuration @@ -64,3 +68,5 @@ export default function create(options: ScrubAnimationOptions & AnimationExtraOp return (target: HTMLElement) => new TrackMouseAnimation(target, animationOptions); } + +create.schema = schema; diff --git a/packages/motion-presets/src/library/ongoing/Bounce.ts b/packages/motion-presets/src/library/ongoing/Bounce.ts index fa29302f..ab67c4b3 100644 --- a/packages/motion-presets/src/library/ongoing/Bounce.ts +++ b/packages/motion-presets/src/library/ongoing/Bounce.ts @@ -1,8 +1,12 @@ import type { TimeAnimationOptions, Bounce, AnimationExtraOptions, DomApi } from '../../types'; -import { getEasing, getTimingFactor, toKeyframeValue, mapRange } from '../../utils'; +import { getEasing, getTimingFactor, toKeyframeValue, parseLength, getCssUnits } from '../../utils'; -const BOUNCE_FACTOR_SOFT = 1; -const BOUNCE_FACTOR_HARD = 3; +const PEAK_TRANSLATE = 49; + +export const schema = { + iterationDelay: { type: 'number', min: 0, default: 0 }, + travel: { type: 'length', default: { value: 49, unit: 'px' } }, +} as const; const TRANSLATE_Y_KEYFRAMES = [ { keyframe: 0, translateY: 0 }, @@ -26,14 +30,15 @@ export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom? export function style(options: TimeAnimationOptions & AnimationExtraOptions, asWeb = false) { const namedEffect = options.namedEffect as Bounce; - const { intensity = 0 } = namedEffect; + const travel = parseLength(namedEffect.travel, schema.travel.default); + const unit = getCssUnits(travel.unit); const duration = options.duration || 1; - const iterationDelay = namedEffect?.iterationDelay || 0; + const iterationDelay = namedEffect?.iterationDelay || schema.iterationDelay.default; const timingFactor = getTimingFactor(duration, iterationDelay) as number; const [name] = getNames(options); - const bounceFactor = mapRange(0, 1, BOUNCE_FACTOR_SOFT, BOUNCE_FACTOR_HARD, intensity); + const bounceFactor = travel.value / PEAK_TRANSLATE; const easing = getEasing('sineOut'); @@ -43,7 +48,7 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW const keyframes = TRANSLATE_Y_KEYFRAMES.map(({ keyframe, translateY }) => ({ offset: (keyframe / 100) * timingFactor, - translate: `0px calc(${translateY / 2}px * ${toKeyframeValue( + translate: `0px calc(${translateY / 2}${unit} * ${toKeyframeValue( custom, '--motion-bounce-factor', asWeb, @@ -64,8 +69,10 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW } export function getNames(options: TimeAnimationOptions & AnimationExtraOptions) { - const iterationDelay = (options.namedEffect as Bounce)?.iterationDelay || 0; + const iterationDelay = + (options.namedEffect as Bounce)?.iterationDelay || schema.iterationDelay.default; const timingFactor = getTimingFactor(options.duration!, iterationDelay, true); + const { suffix = '' } = options; - return [`motion-bounce-${timingFactor}`]; + return [`motion-bounce-${timingFactor}`].map((name) => name + suffix); } diff --git a/packages/motion-presets/src/library/ongoing/Breathe.ts b/packages/motion-presets/src/library/ongoing/Breathe.ts index d80ac2b2..aeb87e0c 100644 --- a/packages/motion-presets/src/library/ongoing/Breathe.ts +++ b/packages/motion-presets/src/library/ongoing/Breathe.ts @@ -10,9 +10,14 @@ import { } from '../../utils'; import { AXIS_DIRECTIONS } from '../../consts'; -const DEFAULT_DISTANCE = { value: 25, unit: 'px' }; const DIRECTIONS = [...AXIS_DIRECTIONS, 'center'] as const; -const DEFAULT_DIRECTION: (typeof DIRECTIONS)[number] = 'vertical'; + +export const schema = { + direction: { type: 'enum', values: DIRECTIONS, default: 'vertical' }, + iterationDelay: { type: 'number', min: 0, default: 0 }, + perspective: { type: 'number', min: 0, default: 800 }, + travel: { type: 'length', default: { value: 25, unit: 'px' } }, +} as const; const DIRECTION_MAP = { vertical: { x: 0, y: 1, z: 0 }, @@ -34,13 +39,17 @@ export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom? export function style(options: TimeAnimationOptions & AnimationExtraOptions, asWeb = false) { const namedEffect = options.namedEffect as Breathe; - const direction = parseDirection(namedEffect?.direction, DIRECTIONS, DEFAULT_DIRECTION); - const distance = parseLength(namedEffect.distance, DEFAULT_DISTANCE); - const { perspective = 800 } = namedEffect; + const direction = parseDirection<(typeof DIRECTIONS)[number]>( + namedEffect?.direction, + DIRECTIONS, + schema.direction.default, + ); + const distance = parseLength(namedEffect.travel, schema.travel.default); + const { perspective = schema.perspective.default } = namedEffect; const easing = options.easing || 'sineInOut'; const duration = options.duration || 1; - const iterationDelay = namedEffect?.iterationDelay || 0; + const iterationDelay = namedEffect?.iterationDelay || schema.iterationDelay.default; const totalDuration = duration + iterationDelay; const timingFactor = getTimingFactor(duration, iterationDelay) as number; const [name] = getNames(options); @@ -117,8 +126,10 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW } export function getNames(options: TimeAnimationOptions & AnimationExtraOptions) { - const iterationDelay = (options.namedEffect as Breathe)?.iterationDelay || 0; + const iterationDelay = + (options.namedEffect as Breathe)?.iterationDelay || schema.iterationDelay.default; const timingFactor = getTimingFactor(options.duration!, iterationDelay, true); + const { suffix = '' } = options; - return [`motion-breathe-${timingFactor}`]; + return [`motion-breathe-${timingFactor}`].map((name) => name + suffix); } diff --git a/packages/motion-presets/src/library/ongoing/Cross.ts b/packages/motion-presets/src/library/ongoing/Cross.ts index 9f0118ac..aa9e6983 100644 --- a/packages/motion-presets/src/library/ongoing/Cross.ts +++ b/packages/motion-presets/src/library/ongoing/Cross.ts @@ -11,7 +11,10 @@ import type { } from '../../types'; import { getElementOffset, getTimingFactor, parseDirection } from '../../utils'; -const DEFAULT_DIRECTION: EffectEightDirections = 'right'; +export const schema = { + direction: { type: 'enum', values: EIGHT_DIRECTIONS, default: 'right' }, + iterationDelay: { type: 'number', min: 0, default: 0 }, +} as const; const FOUR_DIRECTIONS_TRANSLATIONS = { // 100cqw - left @@ -166,9 +169,13 @@ function generateTranslate(direction: keyof typeof FOUR_CORNERS_TRANSLATIONS) { export function web(options: TimeAnimationOptions & AnimationExtraOptions, dom?: DomApi) { const namedEffect = options.namedEffect as Cross; - const direction = parseDirection(namedEffect?.direction, EIGHT_DIRECTIONS, DEFAULT_DIRECTION); + const direction = parseDirection( + namedEffect?.direction, + EIGHT_DIRECTIONS, + schema.direction.default, + ); const duration = options.duration || 1; - const iterationDelay = namedEffect?.iterationDelay || 0; + const iterationDelay = namedEffect?.iterationDelay || schema.iterationDelay.default; const timingFactor = getTimingFactor(duration, iterationDelay) as number; const [name] = getNames(options); @@ -281,6 +288,6 @@ export function web(options: TimeAnimationOptions & AnimationExtraOptions, dom?: ]; } -export function getNames(_: TimeAnimationOptions & AnimationExtraOptions) { - return ['motion-cross']; +export function getNames({ suffix = '' }: TimeAnimationOptions & AnimationExtraOptions) { + return ['motion-cross'].map((name) => name + suffix); } diff --git a/packages/motion-presets/src/library/ongoing/DVD.ts b/packages/motion-presets/src/library/ongoing/DVD.ts index ceeb8739..0cfc45fe 100644 --- a/packages/motion-presets/src/library/ongoing/DVD.ts +++ b/packages/motion-presets/src/library/ongoing/DVD.ts @@ -95,6 +95,6 @@ export function web(options: TimeAnimationOptions, dom?: DomApi) { ]; } -export function getNames(_: TimeAnimationOptions) { - return ['motion-dvd-x', 'motion-dvd-y']; +export function getNames({ suffix = '' }: TimeAnimationOptions) { + return ['motion-dvd-x', 'motion-dvd-y'].map((name) => name + suffix); } diff --git a/packages/motion-presets/src/library/ongoing/Flash.ts b/packages/motion-presets/src/library/ongoing/Flash.ts index 42838285..1d6f4e36 100644 --- a/packages/motion-presets/src/library/ongoing/Flash.ts +++ b/packages/motion-presets/src/library/ongoing/Flash.ts @@ -1,6 +1,10 @@ import type { TimeAnimationOptions, DomApi, AnimationExtraOptions, Flash } from '../../types'; import { getEasing, getTimingFactor } from '../../utils'; +export const schema = { + iterationDelay: { type: 'number', min: 0, default: 0 }, +} as const; + export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom?: DomApi) { return style(options, true); } @@ -8,7 +12,7 @@ export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom? export function style(options: TimeAnimationOptions & AnimationExtraOptions, _asWeb = false) { const namedEffect = options.namedEffect as Flash; const duration = options.duration || 1; - const iterationDelay = namedEffect?.iterationDelay || 0; + const iterationDelay = namedEffect?.iterationDelay || schema.iterationDelay.default; const easing = getEasing(options.easing || 'cubicInOut'); const timingFactor = getTimingFactor(duration, iterationDelay) as number; const [name] = getNames(options); @@ -46,8 +50,10 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, _as } export function getNames(options: TimeAnimationOptions & AnimationExtraOptions) { - const iterationDelay = (options.namedEffect as Flash)?.iterationDelay || 0; + const iterationDelay = + (options.namedEffect as Flash)?.iterationDelay || schema.iterationDelay.default; const timingFactor = getTimingFactor(options.duration!, iterationDelay, true); + const { suffix = '' } = options; - return [`motion-flash-${timingFactor}`]; + return [`motion-flash-${timingFactor}`].map((name) => name + suffix); } diff --git a/packages/motion-presets/src/library/ongoing/Flip.ts b/packages/motion-presets/src/library/ongoing/Flip.ts index 069c079a..90133b58 100644 --- a/packages/motion-presets/src/library/ongoing/Flip.ts +++ b/packages/motion-presets/src/library/ongoing/Flip.ts @@ -2,7 +2,11 @@ import type { AnimationExtraOptions, DomApi, Flip, TimeAnimationOptions } from ' import { getEasing, getTimingFactor, toKeyframeValue, parseDirection } from '../../utils'; import { AXIS_DIRECTIONS } from '../../consts'; -const DEFAULT_DIRECTION: (typeof AXIS_DIRECTIONS)[number] = 'horizontal'; +export const schema = { + direction: { type: 'enum', values: AXIS_DIRECTIONS, default: 'horizontal' }, + iterationDelay: { type: 'number', min: 0, default: 0 }, + perspective: { type: 'number', min: 0, default: 800 }, +} as const; const DIRECTION_MAP = { vertical: { x: '1', y: '0' }, @@ -15,11 +19,15 @@ export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom? export function style(options: TimeAnimationOptions & AnimationExtraOptions, asWeb = false) { const namedEffect = options.namedEffect as Flip; - const direction = parseDirection(namedEffect?.direction, AXIS_DIRECTIONS, DEFAULT_DIRECTION); - const { perspective = 800 } = namedEffect; + const direction = parseDirection<(typeof AXIS_DIRECTIONS)[number]>( + namedEffect?.direction, + AXIS_DIRECTIONS, + schema.direction.default, + ); + const { perspective = schema.perspective.default } = namedEffect; const duration = options.duration || 1; - const iterationDelay = namedEffect?.iterationDelay || 0; + const iterationDelay = namedEffect?.iterationDelay || schema.iterationDelay.default; const offset = getTimingFactor(duration, iterationDelay) as number; const [name] = getNames(options); @@ -71,8 +79,10 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW } export function getNames(options: TimeAnimationOptions & AnimationExtraOptions) { - const iterationDelay = (options.namedEffect as Flip)?.iterationDelay || 0; + const iterationDelay = + (options.namedEffect as Flip)?.iterationDelay || schema.iterationDelay.default; const timingFactor = getTimingFactor(options.duration!, iterationDelay, true); + const { suffix = '' } = options; - return [`motion-flip-${timingFactor}`]; + return [`motion-flip-${timingFactor}`].map((name) => name + suffix); } diff --git a/packages/motion-presets/src/library/ongoing/Fold.ts b/packages/motion-presets/src/library/ongoing/Fold.ts index 4570b0b4..29622822 100644 --- a/packages/motion-presets/src/library/ongoing/Fold.ts +++ b/packages/motion-presets/src/library/ongoing/Fold.ts @@ -14,7 +14,12 @@ import { } from '../../utils'; import { FOUR_DIRECTIONS } from '../../consts'; -const DEFAULT_DIRECTION: EffectFourDirections = 'top'; +export const schema = { + angle: { type: 'number', min: 0, default: 15 }, + iterationDelay: { type: 'number', min: 0, default: 0 }, + perspective: { type: 'number', min: 0, default: 800 }, + pivot: { type: 'enum', values: FOUR_DIRECTIONS, default: 'top' }, +} as const; const DIRECTION_MAP = { top: { @@ -26,17 +31,15 @@ const DIRECTION_MAP = { origin: { x: 50, y: 0 }, }, bottom: { - rotation: { x: 1, y: 0 }, + rotation: { x: -1, y: 0 }, origin: { x: 0, y: 50 }, }, left: { - rotation: { x: 0, y: 1 }, + rotation: { x: 0, y: -1 }, origin: { x: -50, y: 0 }, }, }; -const MIN_ROTATE_ANGLE = 15; - const KEYFRAME_FACTORS = [ { fold: 1, frameFactor: 0.1 }, { fold: -0.7, frameFactor: 0.302 }, @@ -53,16 +56,16 @@ export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom? export function style(options: TimeAnimationOptions & AnimationExtraOptions, asWeb = false) { const namedEffect = options.namedEffect as Fold; - const direction = parseDirection( - namedEffect.direction, + const direction = parseDirection( + namedEffect.pivot, FOUR_DIRECTIONS, - DEFAULT_DIRECTION, - ) as EffectFourDirections; - const { angle = MIN_ROTATE_ANGLE } = namedEffect; + schema.pivot.default, + ); + const { angle = schema.angle.default, perspective = schema.perspective.default } = namedEffect; const easing = options.easing || 'cubicInOut'; const duration = options.duration || 1; - const iterationDelay = +(namedEffect?.iterationDelay || 0); + const iterationDelay = +(namedEffect?.iterationDelay || schema.iterationDelay.default); const [name] = getNames(options); const { rotation, origin } = DIRECTION_MAP[direction]; @@ -85,7 +88,7 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW custom, '--motion-origin-x', asWeb, - )}) translateY(${toKeyframeValue(custom, '--motion-origin-y', asWeb)}) perspective(800px)`; + )}) translateY(${toKeyframeValue(custom, '--motion-origin-y', asWeb)}) perspective(${perspective}px)`; const transformRight = `translateX(calc(-1 * ${toKeyframeValue( custom, '--motion-origin-x', @@ -152,13 +155,16 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW export function getNames(options: TimeAnimationOptions & AnimationExtraOptions) { const duration = options.duration || 1; - const iterationDelay = +((options.namedEffect as Fold)?.iterationDelay || 0); + const iterationDelay = +( + (options.namedEffect as Fold)?.iterationDelay || schema.iterationDelay.default + ); + const { suffix = '' } = options; if (!iterationDelay) { - return ['motion-fold']; + return ['motion-fold'].map((name) => name + suffix); } const timingFactor = getTimingFactor(duration, iterationDelay, true) as string; - return [`motion-fold-${timingFactor}`]; + return [`motion-fold-${timingFactor}`].map((name) => name + suffix); } diff --git a/packages/motion-presets/src/library/ongoing/Jello.ts b/packages/motion-presets/src/library/ongoing/Jello.ts index 999c6c57..3938ef18 100644 --- a/packages/motion-presets/src/library/ongoing/Jello.ts +++ b/packages/motion-presets/src/library/ongoing/Jello.ts @@ -1,8 +1,12 @@ import type { Jello, TimeAnimationOptions, DomApi, AnimationExtraOptions } from '../../types'; -import { getTimingFactor, toKeyframeValue, mapRange } from '../../utils'; +import { getTimingFactor, toKeyframeValue } from '../../utils'; -const JELLO_FACTOR_SOFT = 1; -const JELLO_FACTOR_HARD = 4; +const PEAK_SKEW = 7; + +export const schema = { + iterationDelay: { type: 'number', min: 0, default: 0 }, + skew: { type: 'number', default: 12.25 }, +} as const; const SKEW_Y_KEYFRAMES = [ { keyframe: 24, skewY: 7 }, @@ -18,14 +22,14 @@ export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom? export function style(options: TimeAnimationOptions & AnimationExtraOptions, asWeb = false) { const namedEffect = options.namedEffect as Jello; - const { intensity = 0.25 } = namedEffect; + const { skew = schema.skew.default } = namedEffect; const duration = options.duration || 1; - const iterationDelay = namedEffect?.iterationDelay || 0; + const iterationDelay = namedEffect?.iterationDelay || schema.iterationDelay.default; const [name] = getNames(options); const timingFactor = getTimingFactor(duration, iterationDelay) as number; - const jelloFactor = mapRange(0, 1, JELLO_FACTOR_SOFT, JELLO_FACTOR_HARD, intensity); + const jelloFactor = skew / PEAK_SKEW; // Create CSS custom properties for the jello configuration const custom: Record = { @@ -58,8 +62,10 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW } export function getNames(options: TimeAnimationOptions & AnimationExtraOptions) { - const iterationDelay = (options.namedEffect as Jello)?.iterationDelay || 0; + const iterationDelay = + (options.namedEffect as Jello)?.iterationDelay || schema.iterationDelay.default; const timingFactor = getTimingFactor(options.duration!, iterationDelay, true); + const { suffix = '' } = options; - return [`motion-jello-${timingFactor}`]; + return [`motion-jello-${timingFactor}`].map((name) => name + suffix); } diff --git a/packages/motion-presets/src/library/ongoing/Poke.ts b/packages/motion-presets/src/library/ongoing/Poke.ts index a9a8e852..70528088 100644 --- a/packages/motion-presets/src/library/ongoing/Poke.ts +++ b/packages/motion-presets/src/library/ongoing/Poke.ts @@ -5,10 +5,20 @@ import type { AnimationExtraOptions, EffectFourDirections, } from '../../types'; -import { getTimingFactor, toKeyframeValue, mapRange, parseDirection } from '../../utils'; +import { + getCssUnits, + getTimingFactor, + toKeyframeValue, + parseDirection, + parseLength, +} from '../../utils'; import { FOUR_DIRECTIONS } from '../../consts'; -const DEFAULT_DIRECTION: EffectFourDirections = 'right'; +export const schema = { + direction: { type: 'enum', values: FOUR_DIRECTIONS, default: 'right' }, + iterationDelay: { type: 'number', min: 0, default: 0 }, + travel: { type: 'length', default: { value: 62.5, unit: 'px' } }, +} as const; const TRANSLATE_KEYFRAMES = [ { keyframe: 17, translate: 7 }, @@ -20,8 +30,7 @@ const TRANSLATE_KEYFRAMES = [ { keyframe: 100, translate: 0 }, ]; -const POKE_FACTOR_SOFT = 1; -const POKE_FACTOR_HARD = 4; +const PEAK_TRANSLATE = 25; const DIRECTION_MAP = { top: { x: 0, y: -1 }, @@ -36,16 +45,21 @@ export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom? export function style(options: TimeAnimationOptions & AnimationExtraOptions, asWeb = false) { const namedEffect = options.namedEffect as Poke; - const direction = parseDirection(namedEffect?.direction, FOUR_DIRECTIONS, DEFAULT_DIRECTION); - const { intensity = 0.5 } = namedEffect; + const direction = parseDirection( + namedEffect?.direction, + FOUR_DIRECTIONS, + schema.direction.default, + ); + const travel = parseLength(namedEffect.travel, schema.travel.default); + const unit = getCssUnits(travel.unit); const duration = options.duration || 1; - const iterationDelay = +(namedEffect?.iterationDelay || 0); + const iterationDelay = +(namedEffect?.iterationDelay || schema.iterationDelay.default); const { x, y } = DIRECTION_MAP[direction]; const timingFactor = getTimingFactor(duration, iterationDelay) as number; const [name] = getNames(options); - const pokeFactor = mapRange(0, 1, POKE_FACTOR_SOFT, POKE_FACTOR_HARD, intensity); + const pokeFactor = travel.value / PEAK_TRANSLATE; // Create CSS custom properties for the poke configuration const custom: Record = { @@ -58,11 +72,11 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW custom, '--motion-translate-x', asWeb, - )} * ${translate}px) calc(${toKeyframeValue( + )} * ${translate}${unit}) calc(${toKeyframeValue( custom, '--motion-translate-y', asWeb, - )} * ${translate}px)`; + )} * ${translate}${unit})`; return { offset: (keyframe / 100) * timingFactor, @@ -83,8 +97,10 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW } export function getNames(options: TimeAnimationOptions & AnimationExtraOptions) { - const iterationDelay = (options.namedEffect as Poke)?.iterationDelay || 0; + const iterationDelay = + (options.namedEffect as Poke)?.iterationDelay || schema.iterationDelay.default; const timingFactor = getTimingFactor(options.duration!, iterationDelay, true) as string; + const { suffix = '' } = options; - return [`motion-poke-${timingFactor}`]; + return [`motion-poke-${timingFactor}`].map((name) => name + suffix); } diff --git a/packages/motion-presets/src/library/ongoing/Pulse.ts b/packages/motion-presets/src/library/ongoing/Pulse.ts index b80fa7fe..a0e23da6 100644 --- a/packages/motion-presets/src/library/ongoing/Pulse.ts +++ b/packages/motion-presets/src/library/ongoing/Pulse.ts @@ -1,8 +1,10 @@ import type { Pulse, TimeAnimationOptions, DomApi, AnimationExtraOptions } from '../../types'; -import { getTimingFactor, toKeyframeValue, mapRange } from '../../utils'; +import { getTimingFactor, roundNumber, toKeyframeValue } from '../../utils'; -const PULSE_OFFSET_SOFT = 0; -const PULSE_OFFSET_HARD = 0.12; +export const schema = { + iterationDelay: { type: 'number', min: 0, default: 0 }, + scale: { type: 'number', min: 0, default: 0.93 }, +} as const; const SCALE_KEYFRAMES = [ { keyframe: 27, scale: 0.96 }, @@ -17,14 +19,14 @@ export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom? export function style(options: TimeAnimationOptions & AnimationExtraOptions, asWeb = false) { const namedEffect = options.namedEffect as Pulse; - const { intensity = 0 } = namedEffect; + const { scale: minScale = schema.scale.default } = namedEffect; const duration = options.duration || 1; - const iterationDelay = namedEffect?.iterationDelay || 0; + const iterationDelay = namedEffect?.iterationDelay || schema.iterationDelay.default; const timingFactor = getTimingFactor(duration, iterationDelay) as number; const [name] = getNames(options); - const pulseOffset = mapRange(0, 1, PULSE_OFFSET_SOFT, PULSE_OFFSET_HARD, intensity); + const pulseOffset = roundNumber(schema.scale.default - minScale, 4); // Create CSS custom properties for the pulse configuration const custom: Record = { @@ -64,8 +66,10 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW } export function getNames(options: TimeAnimationOptions & AnimationExtraOptions) { - const iterationDelay = (options.namedEffect as Pulse)?.iterationDelay || 0; + const iterationDelay = + (options.namedEffect as Pulse)?.iterationDelay || schema.iterationDelay.default; const timingFactor = getTimingFactor(options.duration!, iterationDelay, true); + const { suffix = '' } = options; - return [`motion-pulse-${timingFactor}`]; + return [`motion-pulse-${timingFactor}`].map((name) => name + suffix); } diff --git a/packages/motion-presets/src/library/ongoing/Rubber.ts b/packages/motion-presets/src/library/ongoing/Rubber.ts index 0cef3180..06922484 100644 --- a/packages/motion-presets/src/library/ongoing/Rubber.ts +++ b/packages/motion-presets/src/library/ongoing/Rubber.ts @@ -1,8 +1,12 @@ import type { Rubber, TimeAnimationOptions, DomApi, AnimationExtraOptions } from '../../types'; -import { getTimingFactor, roundNumber, toKeyframeValue, mapRange } from '../../utils'; +import { getTimingFactor, roundNumber, toKeyframeValue } from '../../utils'; -const RUBBER_OFFSET_SOFT = 0; -const RUBBER_OFFSET_HARD = 0.1; +const BASE_STRETCH = 0.05; + +export const schema = { + iterationDelay: { type: 'number', min: 0, default: 0 }, + stretch: { type: 'number', min: -1, max: 1, default: 0.1 }, +} as const; const SCALE_KEYFRAMES = [ { keyframe: 45, scaleX: 1.03, scaleY: 0.93 }, @@ -19,14 +23,14 @@ export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom? export function style(options: TimeAnimationOptions & AnimationExtraOptions, asWeb = false) { const namedEffect = options.namedEffect as Rubber; - const { intensity = 0.5 } = namedEffect; + const { stretch = schema.stretch.default } = namedEffect; const duration = options.duration || 1; - const iterationDelay = namedEffect?.iterationDelay || 0; + const iterationDelay = namedEffect?.iterationDelay || schema.iterationDelay.default; const timingFactor = getTimingFactor(duration, iterationDelay) as number; const [name] = getNames(options); - const rubberOffset = mapRange(0, 1, RUBBER_OFFSET_SOFT, RUBBER_OFFSET_HARD, intensity); + const rubberOffset = stretch - BASE_STRETCH; // Create CSS custom properties for the rubber configuration const custom: Record = {}; @@ -70,8 +74,10 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW } export function getNames(options: TimeAnimationOptions & AnimationExtraOptions) { - const iterationDelay = (options.namedEffect as Rubber)?.iterationDelay || 0; + const iterationDelay = + (options.namedEffect as Rubber)?.iterationDelay || schema.iterationDelay.default; const timingFactor = getTimingFactor(options.duration!, iterationDelay, true); + const { suffix = '' } = options; - return [`motion-rubber-${timingFactor}`]; + return [`motion-rubber-${timingFactor}`].map((name) => name + suffix); } diff --git a/packages/motion-presets/src/library/ongoing/Spin.ts b/packages/motion-presets/src/library/ongoing/Spin.ts index 1b2014bf..cc20cac0 100644 --- a/packages/motion-presets/src/library/ongoing/Spin.ts +++ b/packages/motion-presets/src/library/ongoing/Spin.ts @@ -2,7 +2,10 @@ import type { AnimationExtraOptions, DomApi, Spin, TimeAnimationOptions } from ' import { getEasing, getTimingFactor, toKeyframeValue, parseDirection } from '../../utils'; import { SPIN_DIRECTIONS } from '../../consts'; -const DEFAULT_DIRECTION: (typeof SPIN_DIRECTIONS)[number] = 'clockwise'; +export const schema = { + direction: { type: 'enum', values: SPIN_DIRECTIONS, default: 'clockwise' }, + iterationDelay: { type: 'number', min: 0, default: 0 }, +} as const; const DIRECTION_MAP = { clockwise: -1, @@ -15,10 +18,14 @@ export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom? export function style(options: TimeAnimationOptions & AnimationExtraOptions, asWeb = false) { const namedEffect = options.namedEffect as Spin; - const direction = parseDirection(namedEffect?.direction, SPIN_DIRECTIONS, DEFAULT_DIRECTION); + const direction = parseDirection<(typeof SPIN_DIRECTIONS)[number]>( + namedEffect?.direction, + SPIN_DIRECTIONS, + schema.direction.default, + ); const duration = options.duration || 1; - const iterationDelay = namedEffect?.iterationDelay || 0; + const iterationDelay = namedEffect?.iterationDelay || schema.iterationDelay.default; const timingFactor = getTimingFactor(duration, iterationDelay) as number; const [name] = getNames(options); @@ -53,8 +60,10 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW } export function getNames(options: TimeAnimationOptions & AnimationExtraOptions) { - const iterationDelay = (options.namedEffect as Spin)?.iterationDelay || 0; + const iterationDelay = + (options.namedEffect as Spin)?.iterationDelay || schema.iterationDelay.default; const timingFactor = getTimingFactor(options.duration!, iterationDelay, true); + const { suffix = '' } = options; - return [`motion-spin-${timingFactor}`]; + return [`motion-spin-${timingFactor}`].map((name) => name + suffix); } diff --git a/packages/motion-presets/src/library/ongoing/Swing.ts b/packages/motion-presets/src/library/ongoing/Swing.ts index a56a0e44..945355bf 100644 --- a/packages/motion-presets/src/library/ongoing/Swing.ts +++ b/packages/motion-presets/src/library/ongoing/Swing.ts @@ -14,7 +14,11 @@ import { } from '../../utils'; import { FOUR_DIRECTIONS } from '../../consts'; -const DEFAULT_DIRECTION: EffectFourDirections = 'top'; +export const schema = { + angle: { type: 'number', min: 0, default: 20 }, + iterationDelay: { type: 'number', min: 0, default: 0 }, + pivot: { type: 'enum', values: FOUR_DIRECTIONS, default: 'top' }, +} as const; const DIRECTION_MAP = { top: { x: 0, y: -1 }, @@ -41,11 +45,15 @@ export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom? export function style(options: TimeAnimationOptions & AnimationExtraOptions, asWeb = false) { const namedEffect = options.namedEffect as Swing; - const direction = parseDirection(namedEffect?.direction, FOUR_DIRECTIONS, DEFAULT_DIRECTION); - const { swing = 20 } = namedEffect; + const direction = parseDirection( + namedEffect?.pivot, + FOUR_DIRECTIONS, + schema.pivot.default, + ); + const { angle: swing = schema.angle.default } = namedEffect; const duration = options.duration || 1; - const iterationDelay = namedEffect?.iterationDelay || 0; + const iterationDelay = namedEffect?.iterationDelay || schema.iterationDelay.default; const easing = options.easing || 'sineInOut'; const ease = getEasingFamily(easing); const [name] = getNames(options); @@ -135,8 +143,10 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW } export function getNames(options: TimeAnimationOptions & AnimationExtraOptions) { - const iterationDelay = (options.namedEffect as Swing)?.iterationDelay || 0; + const iterationDelay = + (options.namedEffect as Swing)?.iterationDelay || schema.iterationDelay.default; const timingFactor = getTimingFactor(options.duration!, iterationDelay, true); + const { suffix = '' } = options; - return [`motion-swing-${timingFactor}`]; + return [`motion-swing-${timingFactor}`].map((name) => name + suffix); } diff --git a/packages/motion-presets/src/library/ongoing/Wiggle.ts b/packages/motion-presets/src/library/ongoing/Wiggle.ts index dd4d2398..6948d0de 100644 --- a/packages/motion-presets/src/library/ongoing/Wiggle.ts +++ b/packages/motion-presets/src/library/ongoing/Wiggle.ts @@ -1,8 +1,20 @@ import type { Wiggle, TimeAnimationOptions, DomApi, AnimationExtraOptions } from '../../types'; -import { getTimingFactor, roundNumber, toKeyframeValue, mapRange } from '../../utils'; +import { + getCssUnits, + getTimingFactor, + roundNumber, + toKeyframeValue, + parseLength, +} from '../../utils'; -const WIGGLE_FACTOR_SOFT = 1; -const WIGGLE_FACTOR_HARD = 4; +const PEAK_ROTATE = 10; +const PEAK_TRANSLATE = 10; + +export const schema = { + angle: { type: 'number', default: 25 }, + iterationDelay: { type: 'number', min: 0, default: 0 }, + travel: { type: 'length', default: { value: 25, unit: 'px' } }, +} as const; const TRANSFORM_KEYFRAMES = [ { keyframe: 18, transY: -10, accRotate: 10 }, @@ -18,13 +30,15 @@ export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom? export function style(options: TimeAnimationOptions & AnimationExtraOptions, asWeb = false) { const namedEffect = options.namedEffect as Wiggle; - const { intensity = 0.5 } = namedEffect; + const { angle = schema.angle.default } = namedEffect; + const travel = parseLength(namedEffect.travel, schema.travel.default); const duration = options.duration || 1; - const iterationDelay = namedEffect?.iterationDelay || 0; + const iterationDelay = namedEffect?.iterationDelay || schema.iterationDelay.default; const timingFactor = getTimingFactor(duration, iterationDelay) as number; const [name] = getNames(options); - const wiggleFactor = mapRange(0, 1, WIGGLE_FACTOR_SOFT, WIGGLE_FACTOR_HARD, intensity); + const wiggleFactor = angle / PEAK_ROTATE; + const translateFactor = travel.value / PEAK_TRANSLATE; let currentRotation = 0; @@ -38,7 +52,7 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW const rotateValue = `calc(var(--motion-rotate, 0deg) + ${roundNumber( currentRotation + accRotate * wiggleFactor, )}deg)`; - const translateYValue = `${transY * wiggleFactor}px`; + const translateYValue = `${transY * translateFactor}${getCssUnits(travel.unit)}`; const rotateKey = `--motion-rotate-${keyframe}`; const translateYKey = `--motion-translate-y-${keyframe}`; @@ -72,8 +86,10 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW } export function getNames(options: TimeAnimationOptions & AnimationExtraOptions) { - const iterationDelay = (options.namedEffect as Wiggle)?.iterationDelay || 0; + const iterationDelay = + (options.namedEffect as Wiggle)?.iterationDelay || schema.iterationDelay.default; const timingFactor = getTimingFactor(options.duration!, iterationDelay, true); + const { suffix = '' } = options; - return [`motion-wiggle-${timingFactor}`]; + return [`motion-wiggle-${timingFactor}`].map((name) => name + suffix); } diff --git a/packages/motion-presets/src/library/ongoing/test/Bounce.spec.ts b/packages/motion-presets/src/library/ongoing/test/Bounce.spec.ts index 05513003..660a71f9 100644 --- a/packages/motion-presets/src/library/ongoing/test/Bounce.spec.ts +++ b/packages/motion-presets/src/library/ongoing/test/Bounce.spec.ts @@ -97,18 +97,18 @@ describe('Bounce', () => { expect(result).toMatchObject(expectedResult); }); - test('custom duration and intensity', () => { + test('custom duration and travel', () => { const mockOptions: TimeAnimationOptions = { ...baseMockOptions, duration: 1000, - namedEffect: { type: 'Bounce', intensity: 0.8, iterationDelay: 500 }, + namedEffect: { type: 'Bounce', travel: 127.4, iterationDelay: 500 }, }; const expectedResult = [ { id: 'test-id', duration: 1500, - namedEffect: { type: 'Bounce', intensity: 0.8, iterationDelay: 500 }, + namedEffect: { type: 'Bounce', travel: 127.4, iterationDelay: 500 }, name: 'motion-bounce-067', easing: 'linear', custom: { @@ -282,18 +282,18 @@ describe('Bounce', () => { expect(result).toMatchObject(expectedResult); }); - test('custom duration and intensity', () => { + test('custom duration and travel', () => { const mockOptions: TimeAnimationOptions = { ...baseMockOptions, duration: 1000, - namedEffect: { type: 'Bounce', intensity: 0.8, iterationDelay: 500 }, + namedEffect: { type: 'Bounce', travel: 127.4, iterationDelay: 500 }, }; const expectedResult = [ { id: 'test-id', duration: 1500, - namedEffect: { type: 'Bounce', intensity: 0.8, iterationDelay: 500 }, + namedEffect: { type: 'Bounce', travel: 127.4, iterationDelay: 500 }, name: 'motion-bounce-067', easing: 'linear', custom: { @@ -444,5 +444,17 @@ describe('Bounce', () => { expect(result).toEqual(expectedResult); }); + + test('suffix', () => { + const mockOptions: TimeAnimationOptions = { + ...baseMockOptions, + duration: 1000, + suffix: '-1a', + namedEffect: { type: 'Bounce', iterationDelay: 500 }, + }; + + expect(Bounce.getNames(mockOptions)).toEqual(['motion-bounce-067-1a']); + expect(Bounce.style(mockOptions)[0].name).toBe('motion-bounce-067-1a'); + }); }); }); diff --git a/packages/motion-presets/src/library/ongoing/test/Breathe.spec.ts b/packages/motion-presets/src/library/ongoing/test/Breathe.spec.ts index 98bef54a..7e38d140 100644 --- a/packages/motion-presets/src/library/ongoing/test/Breathe.spec.ts +++ b/packages/motion-presets/src/library/ongoing/test/Breathe.spec.ts @@ -121,10 +121,10 @@ describe('Breathe', () => { expect(result).toMatchObject(expectedResult); }); - test('custom distance', () => { + test('custom travel', () => { const mockOptions: TimeAnimationOptions = { ...baseMockOptions, - namedEffect: { distance: { value: 50, unit: 'percentage' } } as Breathe, + namedEffect: { travel: { value: 50, unit: 'percentage' } } as Breathe, }; const expectedResult: Partial[] = [ @@ -436,10 +436,10 @@ describe('Breathe', () => { expect(result).toMatchObject(expectedResult); }); - test('custom distance', () => { + test('custom travel', () => { const mockOptions: TimeAnimationOptions = { ...baseMockOptions, - namedEffect: { distance: { value: 50, unit: 'percentage' } } as Breathe, + namedEffect: { travel: { value: 50, unit: 'percentage' } } as Breathe, }; const expectedResult: Partial[] = [ diff --git a/packages/motion-presets/src/library/ongoing/test/Fold.spec.ts b/packages/motion-presets/src/library/ongoing/test/Fold.spec.ts index b6fd7eb1..17058650 100644 --- a/packages/motion-presets/src/library/ongoing/test/Fold.spec.ts +++ b/packages/motion-presets/src/library/ongoing/test/Fold.spec.ts @@ -140,10 +140,10 @@ describe('Fold', () => { expect(result).toMatchObject(expectedResult); }); - test('custom direction - right', () => { + test('custom pivot - right', () => { const mockOptions: TimeAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'right' } as Fold, + namedEffect: { pivot: 'right' } as Fold, }; const expectedResult: Partial[] = [ @@ -189,10 +189,10 @@ describe('Fold', () => { expect(result).toMatchObject(expectedResult); }); - test('custom direction - left', () => { + test('custom pivot - left', () => { const mockOptions: TimeAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'left' } as Fold, + namedEffect: { pivot: 'left' } as Fold, }; const expectedResult: Partial[] = [ @@ -203,31 +203,31 @@ describe('Fold', () => { '--motion-origin-y': '0%', '--motion-rotate-angle': '15deg', '--motion-rotate-x': '0', - '--motion-rotate-y': '1', + '--motion-rotate-y': '-1', }, keyframes: [ { easing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', offset: 0, transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(-50%) translateY(0%) perspective(800px) rotateX(calc(0 * 0 * 15deg)) rotateY(calc(1 * 0 * 15deg)) translateX(calc(-1 * -50%)) translateY(calc(-1 * 0%))', + 'rotateZ(var(--motion-rotate, 0deg)) translateX(-50%) translateY(0%) perspective(800px) rotateX(calc(0 * 0 * 15deg)) rotateY(calc(-1 * 0 * 15deg)) translateX(calc(-1 * -50%)) translateY(calc(-1 * 0%))', }, { easing: 'cubic-bezier(0.645, 0.045, 0.355, 1)', offset: 0.25, transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(-50%) translateY(0%) perspective(800px) rotateX(calc(0 * 1 * 15deg)) rotateY(calc(1 * 1 * 15deg)) translateX(calc(-1 * -50%)) translateY(calc(-1 * 0%))', + 'rotateZ(var(--motion-rotate, 0deg)) translateX(-50%) translateY(0%) perspective(800px) rotateX(calc(0 * 1 * 15deg)) rotateY(calc(-1 * 1 * 15deg)) translateX(calc(-1 * -50%)) translateY(calc(-1 * 0%))', }, { easing: 'cubic-bezier(0.55, 0.055, 0.675, 0.19)', offset: 0.75, transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(-50%) translateY(0%) perspective(800px) rotateX(calc(0 * -1 * 15deg)) rotateY(calc(1 * -1 * 15deg)) translateX(calc(-1 * -50%)) translateY(calc(-1 * 0%))', + 'rotateZ(var(--motion-rotate, 0deg)) translateX(-50%) translateY(0%) perspective(800px) rotateX(calc(0 * -1 * 15deg)) rotateY(calc(-1 * -1 * 15deg)) translateX(calc(-1 * -50%)) translateY(calc(-1 * 0%))', }, { offset: 1, transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(-50%) translateY(0%) perspective(800px) rotateX(calc(0 * 0 * 15deg)) rotateY(calc(1 * 0 * 15deg)) translateX(calc(-1 * -50%)) translateY(calc(-1 * 0%))', + 'rotateZ(var(--motion-rotate, 0deg)) translateX(-50%) translateY(0%) perspective(800px) rotateX(calc(0 * 0 * 15deg)) rotateY(calc(-1 * 0 * 15deg)) translateX(calc(-1 * -50%)) translateY(calc(-1 * 0%))', }, ], }, @@ -238,10 +238,10 @@ describe('Fold', () => { expect(result).toMatchObject(expectedResult); }); - test('custom direction - bottom', () => { + test('custom pivot - bottom', () => { const mockOptions: TimeAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'bottom' } as Fold, + namedEffect: { pivot: 'bottom' } as Fold, }; const expectedResult: Partial[] = [ @@ -251,7 +251,7 @@ describe('Fold', () => { '--motion-origin-x': '0%', '--motion-origin-y': '50%', '--motion-rotate-angle': '15deg', - '--motion-rotate-x': '1', + '--motion-rotate-x': '-1', '--motion-rotate-y': '0', }, keyframes: [ @@ -259,24 +259,24 @@ describe('Fold', () => { easing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', offset: 0, transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(0%) translateY(50%) perspective(800px) rotateX(calc(1 * 0 * 15deg)) rotateY(calc(0 * 0 * 15deg)) translateX(calc(-1 * 0%)) translateY(calc(-1 * 50%))', + 'rotateZ(var(--motion-rotate, 0deg)) translateX(0%) translateY(50%) perspective(800px) rotateX(calc(-1 * 0 * 15deg)) rotateY(calc(0 * 0 * 15deg)) translateX(calc(-1 * 0%)) translateY(calc(-1 * 50%))', }, { easing: 'cubic-bezier(0.645, 0.045, 0.355, 1)', offset: 0.25, transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(0%) translateY(50%) perspective(800px) rotateX(calc(1 * 1 * 15deg)) rotateY(calc(0 * 1 * 15deg)) translateX(calc(-1 * 0%)) translateY(calc(-1 * 50%))', + 'rotateZ(var(--motion-rotate, 0deg)) translateX(0%) translateY(50%) perspective(800px) rotateX(calc(-1 * 1 * 15deg)) rotateY(calc(0 * 1 * 15deg)) translateX(calc(-1 * 0%)) translateY(calc(-1 * 50%))', }, { easing: 'cubic-bezier(0.55, 0.055, 0.675, 0.19)', offset: 0.75, transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(0%) translateY(50%) perspective(800px) rotateX(calc(1 * -1 * 15deg)) rotateY(calc(0 * -1 * 15deg)) translateX(calc(-1 * 0%)) translateY(calc(-1 * 50%))', + 'rotateZ(var(--motion-rotate, 0deg)) translateX(0%) translateY(50%) perspective(800px) rotateX(calc(-1 * -1 * 15deg)) rotateY(calc(0 * -1 * 15deg)) translateX(calc(-1 * 0%)) translateY(calc(-1 * 50%))', }, { offset: 1, transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(0%) translateY(50%) perspective(800px) rotateX(calc(1 * 0 * 15deg)) rotateY(calc(0 * 0 * 15deg)) translateX(calc(-1 * 0%)) translateY(calc(-1 * 50%))', + 'rotateZ(var(--motion-rotate, 0deg)) translateX(0%) translateY(50%) perspective(800px) rotateX(calc(-1 * 0 * 15deg)) rotateY(calc(0 * 0 * 15deg)) translateX(calc(-1 * 0%)) translateY(calc(-1 * 50%))', }, ], }, @@ -388,10 +388,10 @@ describe('Fold', () => { expect(result).toMatchObject(expectedResult); }); - test('custom direction - right', () => { + test('custom pivot - right', () => { const mockOptions: TimeAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'right' } as Fold, + namedEffect: { pivot: 'right' } as Fold, }; const expectedResult: Partial[] = [ diff --git a/packages/motion-presets/src/library/ongoing/test/Jello.spec.ts b/packages/motion-presets/src/library/ongoing/test/Jello.spec.ts index ba13dd95..93f624f2 100644 --- a/packages/motion-presets/src/library/ongoing/test/Jello.spec.ts +++ b/packages/motion-presets/src/library/ongoing/test/Jello.spec.ts @@ -49,11 +49,11 @@ describe('Jello.web()', () => { expect(result).toMatchObject(expectedResult); }); - test('custom intensity and duration', () => { + test('custom skew and duration', () => { const mockOptions: TimeAnimationOptions = { ...baseMockOptions, duration: 1000, - namedEffect: { intensity: 0.8, iterationDelay: 500 } as Jello, + namedEffect: { skew: 23.8, iterationDelay: 500 } as Jello, }; const expectedResult = [ @@ -62,30 +62,28 @@ describe('Jello.web()', () => { duration: 1500, easing: 'linear', custom: { - '--motion-skew-y': 3.4000000000000004, + '--motion-skew-y': 3.4, }, keyframes: [ { offset: 0.1608, - transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(3.4000000000000004 * 7deg))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(3.4 * 7deg))', }, { offset: 0.2546, - transform: - 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(3.4000000000000004 * -2deg))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(3.4 * -2deg))', }, { offset: 0.3886, - transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(3.4000000000000004 * 4deg))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(3.4 * 4deg))', }, { offset: 0.536, - transform: - 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(3.4000000000000004 * -2deg))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(3.4 * -2deg))', }, { offset: 0.67, - transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(3.4000000000000004 * 0deg))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(3.4 * 0deg))', }, ], }, @@ -147,11 +145,11 @@ describe('Jello.style()', () => { expect(result).toMatchObject(expectedResult); }); - test('custom intensity and duration', () => { + test('custom skew and duration', () => { const mockOptions: TimeAnimationOptions = { ...baseMockOptions, duration: 1000, - namedEffect: { intensity: 0.8, iterationDelay: 500 } as Jello, + namedEffect: { skew: 23.8, iterationDelay: 500 } as Jello, }; const expectedResult = [ @@ -160,7 +158,7 @@ describe('Jello.style()', () => { duration: 1500, easing: 'linear', custom: { - '--motion-skew-y': 3.4000000000000004, + '--motion-skew-y': 3.4, }, keyframes: [ { diff --git a/packages/motion-presets/src/library/ongoing/test/Poke.spec.ts b/packages/motion-presets/src/library/ongoing/test/Poke.spec.ts index cca9d04e..b89e0d9b 100644 --- a/packages/motion-presets/src/library/ongoing/test/Poke.spec.ts +++ b/packages/motion-presets/src/library/ongoing/test/Poke.spec.ts @@ -82,11 +82,11 @@ describe('Poke', () => { expect(result[0].keyframes[6].offset).toBeCloseTo(0.67, 2); }); - test('Poke animation with custom intensity', () => { + test('Poke animation with custom travel', () => { const mockOptions: TimeAnimationOptions = { ...baseMockOptions, duration: 1000, - namedEffect: { intensity: 0.8 } as Poke, + namedEffect: { travel: 85 } as Poke, }; const result = PokeAnimation.web(mockOptions); diff --git a/packages/motion-presets/src/library/ongoing/test/Pulse.spec.ts b/packages/motion-presets/src/library/ongoing/test/Pulse.spec.ts index 2f090c29..5be9057b 100644 --- a/packages/motion-presets/src/library/ongoing/test/Pulse.spec.ts +++ b/packages/motion-presets/src/library/ongoing/test/Pulse.spec.ts @@ -45,11 +45,11 @@ describe('Pulse', () => { expect(result).toMatchObject(expectedResult); }); - test('custom intensity and duration', () => { + test('custom scale and duration', () => { const mockOptions: TimeAnimationOptions = { ...baseMockOptions, duration: 1000, - namedEffect: { intensity: 0.8, iterationDelay: 500 } as Pulse, + namedEffect: { scale: 0.834, iterationDelay: 500 } as Pulse, }; const expectedResult: Partial[] = [ @@ -130,11 +130,11 @@ describe('Pulse', () => { expect(result).toMatchObject(expectedResult); }); - test('custom intensity and duration', () => { + test('custom scale and duration', () => { const mockOptions: TimeAnimationOptions = { ...baseMockOptions, duration: 1000, - namedEffect: { intensity: 0.8, iterationDelay: 500 } as Pulse, + namedEffect: { scale: 0.834, iterationDelay: 500 } as Pulse, }; const expectedResult: Partial[] = [ @@ -177,7 +177,7 @@ describe('Pulse', () => { test('style method should populate custom properties correctly', () => { const mockOptions: TimeAnimationOptions = { ...baseMockOptions, - namedEffect: { intensity: 0.5 } as Pulse, + namedEffect: { scale: 0.87 } as Pulse, }; const result = PulseAnimation.style(mockOptions); @@ -187,7 +187,7 @@ describe('Pulse', () => { // Verify the custom property contains the expected calculated value expect(typeof result[0].custom!['--motion-pulse-offset']).toBe('number'); - expect(result[0].custom!['--motion-pulse-offset']).toBe(0.06); // 0.5 intensity maps to 0.06 offset + expect(result[0].custom!['--motion-pulse-offset']).toBe(0.06); // scale 0.87 maps to 0.06 offset }); }); }); diff --git a/packages/motion-presets/src/library/ongoing/test/Rubber.spec.ts b/packages/motion-presets/src/library/ongoing/test/Rubber.spec.ts index b026fb5a..5f41d04c 100644 --- a/packages/motion-presets/src/library/ongoing/test/Rubber.spec.ts +++ b/packages/motion-presets/src/library/ongoing/test/Rubber.spec.ts @@ -135,11 +135,11 @@ describe('Rubber', () => { expect(result).toEqual(expectedResult); }); - test('custom intensity - style method', () => { + test('custom stretch - style method', () => { const mockOptions: TimeAnimationOptions = { ...baseMockOptions, duration: 1000, - namedEffect: { intensity: 0.8, iterationDelay: 1000 } as RubberType, + namedEffect: { stretch: 0.13, iterationDelay: 1000 } as RubberType, }; const expectedResult = [ @@ -201,11 +201,11 @@ describe('Rubber', () => { expect(result).toMatchObject(expectedResult); }); - test('custom intensity - web method', () => { + test('custom stretch - web method', () => { const mockOptions: TimeAnimationOptions = { ...baseMockOptions, duration: 1000, - namedEffect: { intensity: 0.8, iterationDelay: 1000 } as RubberType, + namedEffect: { stretch: 0.13, iterationDelay: 1000 } as RubberType, }; const expectedResult = [ diff --git a/packages/motion-presets/src/library/ongoing/test/Swing.spec.ts b/packages/motion-presets/src/library/ongoing/test/Swing.spec.ts index c1e73b91..062225c5 100644 --- a/packages/motion-presets/src/library/ongoing/test/Swing.spec.ts +++ b/packages/motion-presets/src/library/ongoing/test/Swing.spec.ts @@ -62,7 +62,7 @@ describe('Swing', () => { ...baseMockOptions, duration: 1000, easing: 'linear', - namedEffect: { swing: 30, iterationDelay: 500 } as SwingType, + namedEffect: { angle: 30, iterationDelay: 500 } as SwingType, }; const result = Swing.style(mockOptions); @@ -111,10 +111,10 @@ describe('Swing', () => { expect(keyframes[8].offset).toBe(1); }); - test('custom direction - right', () => { + test('custom pivot - right', () => { const mockOptions: TimeAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'right' } as SwingType, + namedEffect: { pivot: 'right' } as SwingType, }; const result = Swing.style(mockOptions); @@ -129,10 +129,10 @@ describe('Swing', () => { ); }); - test('custom direction - left', () => { + test('custom pivot - left', () => { const mockOptions: TimeAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'left' } as SwingType, + namedEffect: { pivot: 'left' } as SwingType, }; const result = Swing.style?.(mockOptions); @@ -143,10 +143,10 @@ describe('Swing', () => { }); }); - test('custom direction - bottom', () => { + test('custom pivot - bottom', () => { const mockOptions: TimeAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'bottom' } as SwingType, + namedEffect: { pivot: 'bottom' } as SwingType, }; const result = Swing.style?.(mockOptions); @@ -215,7 +215,7 @@ describe('Swing', () => { ...baseMockOptions, duration: 1000, easing: 'linear', - namedEffect: { swing: 30, iterationDelay: 500 } as SwingType, + namedEffect: { angle: 30, iterationDelay: 500 } as SwingType, }; const result = Swing.web(mockOptions); @@ -279,10 +279,10 @@ describe('Swing', () => { expect(keyframes[8].offset).toBe(1); }); - test('custom direction - right with web output', () => { + test('custom pivot - right with web output', () => { const mockOptions: TimeAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'right' } as SwingType, + namedEffect: { pivot: 'right' } as SwingType, }; const result = Swing.web(mockOptions); diff --git a/packages/motion-presets/src/library/ongoing/test/Wiggle.spec.ts b/packages/motion-presets/src/library/ongoing/test/Wiggle.spec.ts index 16621480..d3c32daa 100644 --- a/packages/motion-presets/src/library/ongoing/test/Wiggle.spec.ts +++ b/packages/motion-presets/src/library/ongoing/test/Wiggle.spec.ts @@ -46,11 +46,11 @@ describe('Wiggle', () => { expect(result).toMatchObject(expectedResult); }); - test('custom duration and intensity', () => { + test('custom duration, angle and travel', () => { const mockOptions: TimeAnimationOptions = { ...baseMockOptions, duration: 1000, - namedEffect: { intensity: 0.8, iterationDelay: 500 } as Wiggle, + namedEffect: { angle: 34, travel: 34, iterationDelay: 500 } as Wiggle, }; const expectedResult: Partial[] = [ @@ -142,11 +142,11 @@ describe('Wiggle', () => { expect(result).toMatchObject(expectedResult); }); - test('custom intensity and duration', () => { + test('custom angle, travel and duration', () => { const mockOptions: TimeAnimationOptions = { ...baseMockOptions, duration: 1000, - namedEffect: { intensity: 0.8, iterationDelay: 500 } as Wiggle, + namedEffect: { angle: 34, travel: 34, iterationDelay: 500 } as Wiggle, }; const expectedResult: Partial[] = [ @@ -199,7 +199,7 @@ describe('Wiggle', () => { test('style method should populate custom properties correctly', () => { const mockOptions: TimeAnimationOptions = { ...baseMockOptions, - namedEffect: { intensity: 0.5 } as Wiggle, + namedEffect: { angle: 25, travel: 25 } as Wiggle, }; const result = WiggleAnimation.style?.(mockOptions); diff --git a/packages/motion-presets/src/library/scroll/ArcScroll.ts b/packages/motion-presets/src/library/scroll/ArcScroll.ts index ccfcc01e..e9ff4642 100644 --- a/packages/motion-presets/src/library/scroll/ArcScroll.ts +++ b/packages/motion-presets/src/library/scroll/ArcScroll.ts @@ -1,17 +1,22 @@ import type { ArcScroll, AnimationFillMode, DomApi, ScrubAnimationOptions } from '../../types'; import { toKeyframeValue, parseDirection } from '../../utils'; -import { AXIS_DIRECTIONS } from '../../consts'; +import { AXIS_DIRECTIONS, SCROLL_RANGES } from '../../consts'; const ROTATION = 68; -const DEFAULT_DIRECTION: (typeof AXIS_DIRECTIONS)[number] = 'horizontal'; + +export const schema = { + direction: { type: 'enum', values: AXIS_DIRECTIONS, default: 'horizontal' }, + perspective: { type: 'number', min: 0, default: 500 }, + range: { type: 'enum', values: SCROLL_RANGES, default: 'in' }, +} as const; const ROTATE_DIRECTION_MAP = { vertical: 'rotateX', horizontal: 'rotateY', }; -export function getNames(_: ScrubAnimationOptions) { - return ['motion-arcScroll']; +export function getNames({ suffix = '' }: ScrubAnimationOptions) { + return ['motion-arcScroll'].map((name) => name + suffix); } export function web(options: ScrubAnimationOptions, _dom?: DomApi) { @@ -20,8 +25,12 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { export function style(options: ScrubAnimationOptions, asWeb = false) { const namedEffect = options.namedEffect as ArcScroll; - const direction = parseDirection(namedEffect?.direction, AXIS_DIRECTIONS, DEFAULT_DIRECTION); - const { range = 'in', perspective = 500 } = namedEffect; + const direction = parseDirection<(typeof AXIS_DIRECTIONS)[number]>( + namedEffect?.direction, + AXIS_DIRECTIONS, + schema.direction.default, + ); + const { range = schema.range.default, perspective = schema.perspective.default } = namedEffect; const fill = ( range === 'out' ? 'forwards' : range === 'in' ? 'backwards' : options.fill ) as AnimationFillMode; diff --git a/packages/motion-presets/src/library/scroll/BlurScroll.ts b/packages/motion-presets/src/library/scroll/BlurScroll.ts index fddbf123..76051884 100644 --- a/packages/motion-presets/src/library/scroll/BlurScroll.ts +++ b/packages/motion-presets/src/library/scroll/BlurScroll.ts @@ -1,8 +1,14 @@ import type { AnimationFillMode, BlurScroll, ScrubAnimationOptions, DomApi } from '../../types'; import { toKeyframeValue } from '../../utils'; +import { SCROLL_RANGES } from '../../consts'; -export function getNames(_: ScrubAnimationOptions) { - return ['motion-blurScroll']; +export const schema = { + blur: { type: 'number', min: 0, default: 6 }, + range: { type: 'enum', values: SCROLL_RANGES, default: 'in' }, +} as const; + +export function getNames({ suffix = '' }: ScrubAnimationOptions) { + return ['motion-blurScroll'].map((name) => name + suffix); } export function web(options: ScrubAnimationOptions, _dom?: DomApi) { @@ -10,7 +16,8 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { } export function style(options: ScrubAnimationOptions, asWeb = false) { - const { blur = 6, range = 'in' } = options.namedEffect as BlurScroll; + const { blur = schema.blur.default, range = schema.range.default } = + options.namedEffect as BlurScroll; const fromValue = range === 'out' ? 0 : blur; const toValue = range === 'out' ? blur : 0; diff --git a/packages/motion-presets/src/library/scroll/FadeScroll.ts b/packages/motion-presets/src/library/scroll/FadeScroll.ts index 32741ec1..0f274274 100644 --- a/packages/motion-presets/src/library/scroll/FadeScroll.ts +++ b/packages/motion-presets/src/library/scroll/FadeScroll.ts @@ -1,8 +1,14 @@ import type { FadeScroll, ScrubAnimationOptions, AnimationFillMode, DomApi } from '../../types'; import { toKeyframeValue } from '../../utils'; +import { SCROLL_RANGES } from '../../consts'; -export function getNames(_: ScrubAnimationOptions) { - return ['motion-fadeScroll']; +export const schema = { + opacity: { type: 'number', min: 0, max: 1, default: 0 }, + range: { type: 'enum', values: SCROLL_RANGES, default: 'in' }, +} as const; + +export function getNames({ suffix = '' }: ScrubAnimationOptions) { + return ['motion-fadeScroll'].map((name) => name + suffix); } export function web(options: ScrubAnimationOptions, _dom?: DomApi) { @@ -10,7 +16,8 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { } export function style(options: ScrubAnimationOptions, asWeb = false) { - const { opacity = 0, range = 'in' } = options.namedEffect as FadeScroll; + const { opacity = schema.opacity.default, range = schema.range.default } = + options.namedEffect as FadeScroll; const isOut = range === 'out'; const fromValue = isOut ? toKeyframeValue({}, '--comp-opacity', false, '1') : opacity; const toValue = isOut ? opacity : toKeyframeValue({}, '--comp-opacity', false, '1'); diff --git a/packages/motion-presets/src/library/scroll/FlipScroll.ts b/packages/motion-presets/src/library/scroll/FlipScroll.ts index e1a2281e..de99128a 100644 --- a/packages/motion-presets/src/library/scroll/FlipScroll.ts +++ b/packages/motion-presets/src/library/scroll/FlipScroll.ts @@ -1,16 +1,21 @@ import type { AnimationFillMode, DomApi, FlipScroll, ScrubAnimationOptions } from '../../types'; import { toKeyframeValue, parseDirection } from '../../utils'; -import { AXIS_DIRECTIONS } from '../../consts'; +import { AXIS_DIRECTIONS, SCROLL_RANGES } from '../../consts'; -const DEFAULT_DIRECTION: (typeof AXIS_DIRECTIONS)[number] = 'horizontal'; +export const schema = { + angle: { type: 'number', default: 240 }, + direction: { type: 'enum', values: AXIS_DIRECTIONS, default: 'horizontal' }, + perspective: { type: 'number', min: 0, default: 800 }, + range: { type: 'enum', values: SCROLL_RANGES, default: 'continuous' }, +} as const; const ROTATE_DIRECTION_MAP = { vertical: 'rotateX', horizontal: 'rotateY', }; -export function getNames(_: ScrubAnimationOptions) { - return ['motion-flipScroll']; +export function getNames({ suffix = '' }: ScrubAnimationOptions) { + return ['motion-flipScroll'].map((name) => name + suffix); } export function web(options: ScrubAnimationOptions, _dom?: DomApi) { @@ -19,13 +24,21 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { export function style(options: ScrubAnimationOptions, asWeb = false) { const namedEffect = options.namedEffect as FlipScroll; - const direction = parseDirection(namedEffect?.direction, AXIS_DIRECTIONS, DEFAULT_DIRECTION); - const { rotate = 240, range = 'continuous', perspective = 800 } = namedEffect; + const direction = parseDirection<(typeof AXIS_DIRECTIONS)[number]>( + namedEffect?.direction, + AXIS_DIRECTIONS, + schema.direction.default, + ); + const { + angle = schema.angle.default, + range = schema.range.default, + perspective = schema.perspective.default, + } = namedEffect; const rotationAxis = ROTATE_DIRECTION_MAP[direction]; - const fromValue = range === 'out' ? 0 : -rotate; - const toValue = range === 'in' ? 0 : rotate; + const fromValue = range === 'out' ? 0 : -angle; + const toValue = range === 'in' ? 0 : angle; const easing = 'linear'; const fill = ( range === 'out' ? 'forwards' : range === 'in' ? 'backwards' : options.fill diff --git a/packages/motion-presets/src/library/scroll/GrowScroll.ts b/packages/motion-presets/src/library/scroll/GrowScroll.ts index 21b09af7..242afade 100644 --- a/packages/motion-presets/src/library/scroll/GrowScroll.ts +++ b/packages/motion-presets/src/library/scroll/GrowScroll.ts @@ -6,10 +6,17 @@ import type { EffectNineDirections, } from '../../types'; import { toKeyframeValue, parseDirection } from '../../utils'; -import { NINE_DIRECTIONS } from '../../consts'; +import { NINE_DIRECTIONS, SCROLL_RANGES } from '../../consts'; const MAX_Y_TRAVEL = 40; -const DEFAULT_DIRECTION: EffectNineDirections = 'center'; +const EPSILON = 0.01; + +export const schema = { + pivot: { type: 'enum', values: NINE_DIRECTIONS, default: 'center' }, + range: { type: 'enum', values: SCROLL_RANGES, default: 'in' }, + scale: { type: 'number', min: 0, default: 0.25 }, + speed: { type: 'number', min: 0, default: 1 }, +} as const; const directionMap = { top: [0, -50], @@ -23,8 +30,8 @@ const directionMap = { center: [0, 0], }; -export function getNames(_: ScrubAnimationOptions) { - return ['motion-growScroll']; +export function getNames({ suffix = '' }: ScrubAnimationOptions) { + return ['motion-growScroll'].map((name) => name + suffix); } export function web(options: ScrubAnimationOptions, _dom?: DomApi) { @@ -33,16 +40,26 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { export function style(options: ScrubAnimationOptions, asWeb = false) { const namedEffect = options.namedEffect as GrowScroll; - const { range = 'in', scale = range === 'in' ? 0 : 4, speed = 0 } = namedEffect; - const direction = parseDirection(namedEffect?.direction, NINE_DIRECTIONS, DEFAULT_DIRECTION); + const { + range = schema.range.default, + scale: inputScale = schema.scale.default, + speed = schema.speed.default, + } = namedEffect; + const direction = parseDirection( + namedEffect?.pivot, + NINE_DIRECTIONS, + schema.pivot.default, + ); const easing = 'linear'; const fill = ( range === 'out' ? 'forwards' : range === 'in' ? 'backwards' : options.fill ) as AnimationFillMode; + const invScale = 1 / Math.max(inputScale, EPSILON); + const scale = range === 'in' ? Math.min(inputScale, invScale) : Math.max(inputScale, invScale); const scaleFrom = scale; const scaleTo = scale; - const travelY = speed; + const travelY = 1 / Math.max(speed, EPSILON) - 1; const travel = travelY * MAX_Y_TRAVEL; const fromValues = { diff --git a/packages/motion-presets/src/library/scroll/MoveScroll.ts b/packages/motion-presets/src/library/scroll/MoveScroll.ts index f4473c45..32f2a68b 100644 --- a/packages/motion-presets/src/library/scroll/MoveScroll.ts +++ b/packages/motion-presets/src/library/scroll/MoveScroll.ts @@ -6,12 +6,16 @@ import { parseLength, parseDirection, } from '../../utils'; +import { SCROLL_RANGES } from '../../consts'; -const DEFAULT_ANGLE = 120; -const DEFAULT_DISTANCE = { value: 400, unit: 'px' }; +export const schema = { + direction: { type: 'number', default: -60 }, + range: { type: 'enum', values: SCROLL_RANGES, default: 'in' }, + travel: { type: 'length', default: { value: 400, unit: 'px' } }, +} as const; -export function getNames(_: ScrubAnimationOptions) { - return ['motion-moveScroll']; +export function getNames({ suffix = '' }: ScrubAnimationOptions) { + return ['motion-moveScroll'].map((name) => name + suffix); } export function web(options: ScrubAnimationOptions, _?: DomApi, config?: Record) { @@ -20,15 +24,21 @@ export function web(options: ScrubAnimationOptions, _?: DomApi, config?: Record< export function style(options: ScrubAnimationOptions, config?: Record, asWeb = false) { const namedEffect = options.namedEffect as MoveScroll; - const angle = parseDirection(namedEffect?.angle, [], DEFAULT_ANGLE, true); - const { range = 'in' } = namedEffect; + const direction = parseDirection( + namedEffect?.direction, + [], + schema.direction.default, + true, + ); + const { range = schema.range.default } = namedEffect; + const angle = range === 'out' ? direction : direction + 180; const easing = 'linear'; const fill = ( range === 'out' ? 'forwards' : range === 'in' ? 'backwards' : options.fill ) as AnimationFillMode; - const distance = parseLength(namedEffect.distance, DEFAULT_DISTANCE); + const distance = parseLength(namedEffect.travel, schema.travel.default); let [travelX, travelY] = transformPolarToXY(angle, distance.value); const unit = getCssUnits(distance.unit); diff --git a/packages/motion-presets/src/library/scroll/PanScroll.ts b/packages/motion-presets/src/library/scroll/PanScroll.ts index addb74bf..f9b40c34 100644 --- a/packages/motion-presets/src/library/scroll/PanScroll.ts +++ b/packages/motion-presets/src/library/scroll/PanScroll.ts @@ -6,13 +6,17 @@ import { EffectTwoSides, } from '../../types'; import { getCssUnits, toKeyframeValue, parseDirection, parseLength } from '../../utils'; -import { TWO_SIDES_DIRECTIONS } from '../../consts'; +import { SCROLL_RANGES, TWO_SIDES_DIRECTIONS } from '../../consts'; -const DEFAULT_DIRECTION: EffectTwoSides = 'left'; -const DEFAULT_DISTANCE = { value: 400, unit: 'px' }; +export const schema = { + direction: { type: 'enum', values: TWO_SIDES_DIRECTIONS, default: 'right' }, + distance: { type: 'length', default: { value: 400, unit: 'px' } }, + range: { type: 'enum', values: SCROLL_RANGES, default: 'in' }, + startFromOffScreen: { type: 'bool', default: true }, +} as const; -export function getNames(_: ScrubAnimationOptions) { - return ['motion-panScroll']; +export function getNames({ suffix = '' }: ScrubAnimationOptions) { + return ['motion-panScroll'].map((name) => name + suffix); } export function prepare(options: ScrubAnimationOptions, dom?: DomApi) { @@ -38,9 +42,15 @@ export function web(options: ScrubAnimationOptions, dom?: DomApi) { export function style(options: ScrubAnimationOptions, asWeb = false) { const namedEffect = options.namedEffect as PanScroll; - const direction = parseDirection(namedEffect?.direction, TWO_SIDES_DIRECTIONS, DEFAULT_DIRECTION); - const { startFromOffScreen = true, range = 'in' } = namedEffect; - const distance = parseLength(namedEffect.distance, DEFAULT_DISTANCE); + const toDirection = parseDirection( + namedEffect?.direction, + TWO_SIDES_DIRECTIONS, + schema.direction.default, + ); + const { startFromOffScreen = schema.startFromOffScreen.default, range = schema.range.default } = + namedEffect; + const direction = range === 'out' ? toDirection : toDirection === 'left' ? 'right' : 'left'; + const distance = parseLength(namedEffect.distance, schema.distance.default); const travel = distance.value * (direction === 'left' ? 1 : -1); let startX = `${-travel}${getCssUnits(distance.unit)}`; let endX = `${travel}${getCssUnits(distance.unit)}`; diff --git a/packages/motion-presets/src/library/scroll/ParallaxScroll.ts b/packages/motion-presets/src/library/scroll/ParallaxScroll.ts index 4ebde724..c20007bb 100644 --- a/packages/motion-presets/src/library/scroll/ParallaxScroll.ts +++ b/packages/motion-presets/src/library/scroll/ParallaxScroll.ts @@ -1,10 +1,16 @@ import type { ScrubAnimationOptions, ParallaxScroll, AnimationFillMode, DomApi } from '../../types'; import { toKeyframeValue } from '../../utils'; -const DEFAULT_PARALLAX_FACTOR = 0.5; +const PARALLAX_FACTOR_AT_DEFAULT_SPEED = 0.5; +const EPSILON = 0.01; -export function getNames(_: ScrubAnimationOptions) { - return ['motion-parallaxScroll']; +export const schema = { + center: { type: 'number', min: 0, max: 1, default: 0.5 }, + speed: { type: 'number', min: 0, default: 0.7159 }, +} as const; + +export function getNames({ suffix = '' }: ScrubAnimationOptions) { + return ['motion-parallaxScroll'].map((name) => name + suffix); } export function web(options: ScrubAnimationOptions, _dom?: DomApi) { @@ -13,11 +19,16 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { export function style(options: ScrubAnimationOptions, asWeb = false) { const namedEffect = options.namedEffect as ParallaxScroll; - const { parallaxFactor = DEFAULT_PARALLAX_FACTOR } = namedEffect; + const { speed = schema.speed.default, center: inputCenter = schema.center.default } = namedEffect; const easing = 'linear'; - const start = `${-50 * parallaxFactor}vh`; - const end = `${50 * parallaxFactor}vh`; + const parallaxFactor = + (PARALLAX_FACTOR_AT_DEFAULT_SPEED * (1 / Math.max(speed, EPSILON) - 1)) / + (1 / schema.speed.default - 1); + const center = Math.min(1 - EPSILON, Math.max(0, inputCenter)); + + const start = `${-100 * parallaxFactor * center}vh`; + const end = `${100 * parallaxFactor * (1 - center)}vh`; const [parallaxScroll] = getNames(options); @@ -37,7 +48,7 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { custom, keyframes: [ { - transform: `translateY(calc(-1 * ${toKeyframeValue( + transform: `translateY(calc(${-center / (1 - center)} * ${toKeyframeValue( custom, '--motion-parallax-to', asWeb, diff --git a/packages/motion-presets/src/library/scroll/RevealScroll.ts b/packages/motion-presets/src/library/scroll/RevealScroll.ts index a5c5a232..899b893e 100644 --- a/packages/motion-presets/src/library/scroll/RevealScroll.ts +++ b/packages/motion-presets/src/library/scroll/RevealScroll.ts @@ -11,14 +11,20 @@ import { toKeyframeValue, INITIAL_CLIP, parseDirection, + getOppositeDirection, + FOUR_DIRECTIONS as CLIP_DIRECTIONS, } from '../../utils'; -import { FOUR_DIRECTIONS } from '../../consts'; +import { FOUR_DIRECTIONS, SCROLL_RANGES } from '../../consts'; -const DEFAULT_DIRECTION: EffectFourDirections = 'bottom'; +export const schema = { + direction: { type: 'enum', values: FOUR_DIRECTIONS, default: 'bottom' }, + range: { type: 'enum', values: SCROLL_RANGES, default: 'in' }, +} as const; export function getNames(options: ScrubAnimationOptions) { - const { range = 'in' } = options.namedEffect as RevealScroll; - return [`motion-revealScroll${range === 'continuous' ? '-continuous' : ''}`]; + const { range = schema.range.default } = options.namedEffect as RevealScroll; + const { suffix = '' } = options; + return [`motion-revealScroll${range === 'continuous' ? '-continuous' : ''}${suffix}`]; } export function web(options: ScrubAnimationOptions, _dom?: DomApi) { @@ -27,8 +33,14 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { export function style(options: ScrubAnimationOptions) { const namedEffect = options.namedEffect as RevealScroll; - const direction = parseDirection(namedEffect?.direction, FOUR_DIRECTIONS, DEFAULT_DIRECTION); - const { range = 'in' } = namedEffect; + const { range = schema.range.default } = namedEffect; + const toDirection = parseDirection( + namedEffect?.direction, + FOUR_DIRECTIONS, + schema.direction.default, + ); + const direction = + range === 'out' ? getOppositeDirection(CLIP_DIRECTIONS, toDirection) : toDirection; const easing = 'linear'; const fill = ( range === 'out' ? 'forwards' : range === 'in' ? 'backwards' : options.fill diff --git a/packages/motion-presets/src/library/scroll/ShapeScroll.ts b/packages/motion-presets/src/library/scroll/ShapeScroll.ts index a5cca13b..efe8e536 100644 --- a/packages/motion-presets/src/library/scroll/ShapeScroll.ts +++ b/packages/motion-presets/src/library/scroll/ShapeScroll.ts @@ -6,6 +6,13 @@ import type { ShapeType, } from '../../types'; import { toKeyframeValue, getEasing } from '../../utils'; +import { SCROLL_RANGES, SHAPES } from '../../consts'; + +export const schema = { + range: { type: 'enum', values: SCROLL_RANGES, default: 'in' }, + shape: { type: 'enum', values: SHAPES, default: 'circle' }, + start: { type: 'number', min: 0, max: 1, default: 0.5 }, +} as const; const RESPONSIVE_SHAPES_MAP: Record< ShapeType, @@ -32,8 +39,9 @@ const RESPONSIVE_SHAPES_MAP: Record< }; export function getNames(options: ScrubAnimationOptions) { - const { range = 'in' } = options.namedEffect as ShapeScroll; - return [`motion-shapeScroll${range === 'continuous' ? '-continuous' : ''}`]; + const { range = schema.range.default } = options.namedEffect as ShapeScroll; + const { suffix = '' } = options; + return [`motion-shapeScroll${range === 'continuous' ? '-continuous' : ''}${suffix}`]; } export function web(options: ScrubAnimationOptions, _dom?: DomApi) { @@ -41,17 +49,18 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { } export function style(options: ScrubAnimationOptions, asWeb = false) { - const { intensity = 0.5, range = 'in' } = options.namedEffect as ShapeScroll; - let { shape = 'circle' } = options.namedEffect as ShapeScroll; + const { start: startOpen = schema.start.default, range = schema.range.default } = + options.namedEffect as ShapeScroll; + let { shape = schema.shape.default } = options.namedEffect as ShapeScroll; if (!(shape in RESPONSIVE_SHAPES_MAP)) { - shape = 'circle'; + shape = schema.shape.default; } const fill = ( range === 'out' ? 'forwards' : range === 'in' ? 'backwards' : options.fill ) as AnimationFillMode; - const [start, end] = RESPONSIVE_SHAPES_MAP[shape](intensity * 100); + const [start, end] = RESPONSIVE_SHAPES_MAP[shape]((1 - startOpen) * 100); const [shapeScroll] = getNames(options); diff --git a/packages/motion-presets/src/library/scroll/ShrinkScroll.ts b/packages/motion-presets/src/library/scroll/ShrinkScroll.ts index bb4f672e..1a1efc38 100644 --- a/packages/motion-presets/src/library/scroll/ShrinkScroll.ts +++ b/packages/motion-presets/src/library/scroll/ShrinkScroll.ts @@ -6,10 +6,17 @@ import type { EffectNineDirections, } from '../../types'; import { toKeyframeValue, parseDirection } from '../../utils'; -import { NINE_DIRECTIONS } from '../../consts'; +import { NINE_DIRECTIONS, SCROLL_RANGES } from '../../consts'; const MAX_Y_TRAVEL = 40; -const DEFAULT_DIRECTION: EffectNineDirections = 'center'; +const EPSILON = 0.01; + +export const schema = { + pivot: { type: 'enum', values: NINE_DIRECTIONS, default: 'center' }, + range: { type: 'enum', values: SCROLL_RANGES, default: 'in' }, + scale: { type: 'number', min: 0, default: 1.2 }, + speed: { type: 'number', min: 0, default: 1 }, +} as const; const directionMap = { top: [0, -50], @@ -23,8 +30,8 @@ const directionMap = { center: [0, 0], }; -export function getNames(_: ScrubAnimationOptions) { - return ['motion-shrinkScroll']; +export function getNames({ suffix = '' }: ScrubAnimationOptions) { + return ['motion-shrinkScroll'].map((name) => name + suffix); } export function web(options: ScrubAnimationOptions, _dom?: DomApi) { @@ -33,16 +40,26 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { export function style(options: ScrubAnimationOptions, asWeb = false) { const namedEffect = options.namedEffect as ShrinkScroll; - const { range = 'in', scale = range === 'in' ? 1.2 : 0.8, speed = 0 } = namedEffect; - const direction = parseDirection(namedEffect?.direction, NINE_DIRECTIONS, DEFAULT_DIRECTION); + const { + range = schema.range.default, + scale: inputScale = schema.scale.default, + speed = schema.speed.default, + } = namedEffect; + const direction = parseDirection( + namedEffect?.pivot, + NINE_DIRECTIONS, + schema.pivot.default, + ); const easing = 'linear'; const fill = ( range === 'out' ? 'forwards' : range === 'in' ? 'backwards' : options.fill ) as AnimationFillMode; + const invScale = 1 / Math.max(inputScale, EPSILON); + const scale = range === 'in' ? Math.max(inputScale, invScale) : Math.min(inputScale, invScale); const scaleFrom = scale; const scaleTo = scale; - const travelY = speed; + const travelY = 1 / Math.max(speed, EPSILON) - 1; const travel = travelY * MAX_Y_TRAVEL; const fromValues = { diff --git a/packages/motion-presets/src/library/scroll/ShuttersScroll.ts b/packages/motion-presets/src/library/scroll/ShuttersScroll.ts index c5bf7e67..962178fc 100644 --- a/packages/motion-presets/src/library/scroll/ShuttersScroll.ts +++ b/packages/motion-presets/src/library/scroll/ShuttersScroll.ts @@ -10,15 +10,21 @@ import { getShuttersClipPaths, getEasing, toKeyframeValue, - FOUR_DIRECTIONS, parseDirection, } from '../../utils'; +import { FOUR_DIRECTIONS, SCROLL_RANGES } from '../../consts'; -const DEFAULT_DIRECTION: EffectFourDirections = 'right'; +export const schema = { + direction: { type: 'enum', values: FOUR_DIRECTIONS, default: 'right' }, + range: { type: 'enum', values: SCROLL_RANGES, default: 'in' }, + shutters: { type: 'number', min: 1, int: true, default: 12 }, + staggered: { type: 'bool', default: true }, +} as const; export function getNames(options: ScrubAnimationOptions) { - const { range = 'in' } = options.namedEffect as ShuttersScroll; - return [`motion-shuttersScroll-${range === 'continuous' ? '-continuous' : ''}`]; + const { range = schema.range.default } = options.namedEffect as ShuttersScroll; + const { suffix = '' } = options; + return [`motion-shuttersScroll-${range === 'continuous' ? '-continuous' : ''}${suffix}`]; } export function web(options: ScrubAnimationOptions, _dom?: DomApi) { @@ -27,15 +33,23 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { export function style(options: ScrubAnimationOptions, asWeb = false) { const namedEffect = options.namedEffect as ShuttersScroll; - const direction = parseDirection(namedEffect?.direction, FOUR_DIRECTIONS, DEFAULT_DIRECTION); - const { shutters = 12, staggered = true, range = 'in' } = namedEffect; + const direction = parseDirection( + namedEffect?.direction, + FOUR_DIRECTIONS, + schema.direction.default, + ); + const { + shutters = schema.shutters.default, + staggered = schema.staggered.default, + range = schema.range.default, + } = namedEffect; const fill = ( range === 'out' ? 'forwards' : range === 'in' ? 'backwards' : options.fill ) as AnimationFillMode; const easing = range === 'in' ? getEasing('sineIn') : getEasing('sineOut'); - const directionOpp = getOppositeDirection(FOUR_DIRECTIONS, direction); + const directionOpp = getOppositeDirection([...FOUR_DIRECTIONS], direction); const { clipStart, clipEnd } = getShuttersClipPaths( range === 'out' ? directionOpp : direction, diff --git a/packages/motion-presets/src/library/scroll/SkewPanScroll.ts b/packages/motion-presets/src/library/scroll/SkewPanScroll.ts index bdec01b6..a1521cf7 100644 --- a/packages/motion-presets/src/library/scroll/SkewPanScroll.ts +++ b/packages/motion-presets/src/library/scroll/SkewPanScroll.ts @@ -6,17 +6,21 @@ import type { EffectTwoSides, } from '../../types'; import { toKeyframeValue, parseDirection } from '../../utils'; -import { TWO_SIDES_DIRECTIONS } from '../../consts'; +import { SCROLL_RANGES, TWO_SIDES_DIRECTIONS } from '../../consts'; -const DEFAULT_DIRECTION: EffectTwoSides = 'right'; +export const schema = { + direction: { type: 'enum', values: TWO_SIDES_DIRECTIONS, default: 'left' }, + range: { type: 'enum', values: SCROLL_RANGES, default: 'in' }, + skew: { type: 'number', default: 10 }, +} as const; const DIRECTION_MAP = { right: -1, left: 1, }; -export function getNames(_: ScrubAnimationOptions) { - return ['motion-skewPanScroll']; +export function getNames({ suffix = '' }: ScrubAnimationOptions) { + return ['motion-skewPanScroll'].map((name) => name + suffix); } export function prepare(_: ScrubAnimationOptions, dom?: DomApi) { @@ -42,14 +46,20 @@ export function web(options: ScrubAnimationOptions, dom?: DomApi) { export function style(options: ScrubAnimationOptions, asWeb = false) { const namedEffect = options.namedEffect as SkewPanScroll; - const direction = parseDirection(namedEffect?.direction, TWO_SIDES_DIRECTIONS, DEFAULT_DIRECTION); - const { skew = 10, range = 'in' } = namedEffect; + const toDirection = parseDirection( + namedEffect?.direction, + TWO_SIDES_DIRECTIONS, + schema.direction.default, + ); + const { skew = schema.skew.default, range = schema.range.default } = namedEffect; + const fromDirection: EffectTwoSides = toDirection === 'left' ? 'right' : 'left'; + const direction = range === 'out' ? toDirection : fromDirection; const easing = 'linear'; const fill = ( range === 'out' ? 'forwards' : range === 'in' ? 'backwards' : options.fill ) as AnimationFillMode; - const skewX = skew * DIRECTION_MAP[direction]; + const skewX = skew * DIRECTION_MAP[fromDirection]; const startXLeft = `calc(${toKeyframeValue( {}, '--motion-left', diff --git a/packages/motion-presets/src/library/scroll/SlideScroll.ts b/packages/motion-presets/src/library/scroll/SlideScroll.ts index 4cd219de..f29fe083 100644 --- a/packages/motion-presets/src/library/scroll/SlideScroll.ts +++ b/packages/motion-presets/src/library/scroll/SlideScroll.ts @@ -12,11 +12,14 @@ import { getRevealClipTo, toKeyframeValue, INITIAL_CLIP, - FOUR_DIRECTIONS, parseDirection, } from '../../utils'; +import { FOUR_DIRECTIONS, SCROLL_RANGES } from '../../consts'; -const DEFAULT_DIRECTION: EffectFourDirections = 'bottom'; +export const schema = { + direction: { type: 'enum', values: FOUR_DIRECTIONS, default: 'top' }, + range: { type: 'enum', values: SCROLL_RANGES, default: 'in' }, +} as const; const DIRECTION_TRANSLATION_MAP: Record = { bottom: { x: '0', y: '100%' }, @@ -26,8 +29,9 @@ const DIRECTION_TRANSLATION_MAP: Record = { }; export function getNames(options: ScrubAnimationOptions) { - const { range = 'in' } = options.namedEffect as SlideScroll; - return [`motion-slideScroll${range === 'continuous' ? '-continuous' : ''}`]; + const { range = schema.range.default } = options.namedEffect as SlideScroll; + const { suffix = '' } = options; + return [`motion-slideScroll${range === 'continuous' ? '-continuous' : ''}${suffix}`]; } export function web(options: ScrubAnimationOptions, _dom?: DomApi) { @@ -36,13 +40,19 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { export function style(options: ScrubAnimationOptions, asWeb = false) { const namedEffect = options.namedEffect as SlideScroll; - const direction = parseDirection(namedEffect?.direction, FOUR_DIRECTIONS, DEFAULT_DIRECTION); - const { range = 'in' } = namedEffect; + const { range = schema.range.default } = namedEffect; + const toDirection = parseDirection( + namedEffect?.direction, + FOUR_DIRECTIONS, + schema.direction.default, + ); + const direction = + range === 'out' ? toDirection : getOppositeDirection([...FOUR_DIRECTIONS], toDirection); const easing = 'linear'; const fill = ( range === 'out' ? 'forwards' : range === 'in' ? 'backwards' : options.fill ) as AnimationFillMode; - const oppositeDirection = getOppositeDirection(FOUR_DIRECTIONS, direction); + const oppositeDirection = getOppositeDirection([...FOUR_DIRECTIONS], direction); const translateFrom = range === 'out' ? { x: '0', y: '0' } : DIRECTION_TRANSLATION_MAP[direction]; const translateTo = diff --git a/packages/motion-presets/src/library/scroll/Spin3dScroll.ts b/packages/motion-presets/src/library/scroll/Spin3dScroll.ts index cfc1db9e..4f78c0e5 100644 --- a/packages/motion-presets/src/library/scroll/Spin3dScroll.ts +++ b/packages/motion-presets/src/library/scroll/Spin3dScroll.ts @@ -1,10 +1,19 @@ import type { AnimationFillMode, ScrubAnimationOptions, Spin3dScroll, DomApi } from '../../types'; import { toKeyframeValue } from '../../utils'; +import { SCROLL_RANGES } from '../../consts'; const MAX_Y_TRAVEL = 40; +const EPSILON = 0.01; -export function getNames(_: ScrubAnimationOptions) { - return ['motion-spin3dScroll']; +export const schema = { + angle: { type: 'number', default: 100 }, + perspective: { type: 'number', min: 0, default: 1000 }, + range: { type: 'enum', values: SCROLL_RANGES, default: 'in' }, + speed: { type: 'number', min: 0, default: 1 }, +} as const; + +export function getNames({ suffix = '' }: ScrubAnimationOptions) { + return ['motion-spin3dScroll'].map((name) => name + suffix); } export function web(options: ScrubAnimationOptions, _dom?: DomApi) { @@ -13,17 +22,18 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { export function style(options: ScrubAnimationOptions, asWeb = false) { const { - rotate = -100, - speed = 0, - range = 'in', - perspective = 1000, + angle = schema.angle.default, + speed = schema.speed.default, + range = schema.range.default, + perspective = schema.perspective.default, } = options.namedEffect as Spin3dScroll; + const rotate = -angle; const easing = 'linear'; const fill = ( range === 'out' ? 'forwards' : range === 'in' ? 'backwards' : options.fill ) as AnimationFillMode; - const travel = speed * MAX_Y_TRAVEL; + const travel = (1 / Math.max(speed, EPSILON) - 1) * MAX_Y_TRAVEL; const fromValues = { rotationX: range === 'out' ? 0 : -2 * rotate, diff --git a/packages/motion-presets/src/library/scroll/SpinScroll.ts b/packages/motion-presets/src/library/scroll/SpinScroll.ts index 15080685..28fb72f9 100644 --- a/packages/motion-presets/src/library/scroll/SpinScroll.ts +++ b/packages/motion-presets/src/library/scroll/SpinScroll.ts @@ -1,16 +1,21 @@ import type { AnimationFillMode, DomApi, ScrubAnimationOptions, SpinScroll } from '../../types'; import { toKeyframeValue, parseDirection } from '../../utils'; -import { SPIN_DIRECTIONS } from '../../consts'; +import { SCROLL_RANGES, SPIN_DIRECTIONS } from '../../consts'; -const DEFAULT_DIRECTION: (typeof SPIN_DIRECTIONS)[number] = 'clockwise'; +export const schema = { + direction: { type: 'enum', values: SPIN_DIRECTIONS, default: 'clockwise' }, + range: { type: 'enum', values: SCROLL_RANGES, default: 'in' }, + scale: { type: 'number', min: 0, default: 1 }, + spins: { type: 'number', min: 0, default: 0.15 }, +} as const; const DIRECTION_MAP = { clockwise: 1, 'counter-clockwise': -1, }; -export function getNames(_: ScrubAnimationOptions) { - return ['motion-spinScroll']; +export function getNames({ suffix = '' }: ScrubAnimationOptions) { + return ['motion-spinScroll'].map((name) => name + suffix); } export function web(options: ScrubAnimationOptions, _dom?: DomApi) { @@ -19,8 +24,16 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { export function style(options: ScrubAnimationOptions, asWeb = false) { const namedEffect = options.namedEffect as SpinScroll; - const direction = parseDirection(namedEffect?.direction, SPIN_DIRECTIONS, DEFAULT_DIRECTION); - const { spins = 0.15, scale = 1, range = 'in' } = namedEffect; + const direction = parseDirection<(typeof SPIN_DIRECTIONS)[number]>( + namedEffect?.direction, + SPIN_DIRECTIONS, + schema.direction.default, + ); + const { + spins = schema.spins.default, + scale = schema.scale.default, + range = schema.range.default, + } = namedEffect; const easing = 'linear'; const fill = ( range === 'out' ? 'forwards' : range === 'in' ? 'backwards' : options.fill diff --git a/packages/motion-presets/src/library/scroll/StretchScroll.ts b/packages/motion-presets/src/library/scroll/StretchScroll.ts index fb5b1a6c..60daad7f 100644 --- a/packages/motion-presets/src/library/scroll/StretchScroll.ts +++ b/packages/motion-presets/src/library/scroll/StretchScroll.ts @@ -1,6 +1,12 @@ import { cssEasings as easings } from '@wix/motion'; import type { AnimationFillMode, ScrubAnimationOptions, StretchScroll, DomApi } from '../../types'; import { roundNumber, toKeyframeValue } from '../../utils'; +import { SCROLL_RANGES } from '../../consts'; + +export const schema = { + range: { type: 'enum', values: SCROLL_RANGES, default: 'out' }, + stretch: { type: 'number', min: -1, max: 1, default: 0.6 }, +} as const; const opacityKeyframesMap = { in: [ @@ -20,11 +26,12 @@ const opacityKeyframesMap = { }; export function getNames(options: ScrubAnimationOptions) { - const { range = 'out' } = options.namedEffect as StretchScroll; + const { range = schema.range.default } = options.namedEffect as StretchScroll; + const { suffix = '' } = options; return [ `motion-stretchScrollScale${range === 'continuous' ? '-continuous' : ''}`, `motion-stretchScrollOpacity-${range}`, - ]; + ].map((name) => name + suffix); } export function web(options: ScrubAnimationOptions, _dom?: DomApi) { @@ -32,7 +39,8 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { } export function style(options: ScrubAnimationOptions, asWeb = false) { - const { stretch = 0.6, range = 'out' } = options.namedEffect as StretchScroll; + const { stretch = schema.stretch.default, range = schema.range.default } = + options.namedEffect as StretchScroll; const easing = range === 'continuous' ? 'linear' : 'backInOut'; const fill = ( range === 'out' ? 'forwards' : range === 'in' ? 'backwards' : options.fill diff --git a/packages/motion-presets/src/library/scroll/TiltScroll.ts b/packages/motion-presets/src/library/scroll/TiltScroll.ts index 750f85d2..fd6648c1 100644 --- a/packages/motion-presets/src/library/scroll/TiltScroll.ts +++ b/packages/motion-presets/src/library/scroll/TiltScroll.ts @@ -3,23 +3,30 @@ import type { ScrubAnimationOptions, TiltScroll, DomApi, - EffectTwoSides, + EffectSpinDirection, } from '../../types'; import { cssEasings as easings } from '@wix/motion'; import { toKeyframeValue, parseDirection } from '../../utils'; -import { TWO_SIDES_DIRECTIONS } from '../../consts'; +import { SCROLL_RANGES, SPIN_DIRECTIONS } from '../../consts'; const MAX_Y_TRAVEL = 40; +const EPSILON = 0.01; const [ROTATION_X, ROTATION_Y, ROTATION_Z] = [10, 25, 25]; -const DEFAULT_DIRECTION: EffectTwoSides = 'right'; + +export const schema = { + direction: { type: 'enum', values: SPIN_DIRECTIONS, default: 'counter-clockwise' }, + perspective: { type: 'number', min: 0, default: 400 }, + range: { type: 'enum', values: SCROLL_RANGES, default: 'in' }, + speed: { type: 'number', min: 0, default: 1 }, +} as const; const DIRECTIONS_MAP = { - right: 1, - left: -1, + 'counter-clockwise': 1, + clockwise: -1, }; -export function getNames(_: ScrubAnimationOptions) { - return ['motion-tiltScrollTranslate', 'motion-tiltScrollRotate']; +export function getNames({ suffix = '' }: ScrubAnimationOptions) { + return ['motion-tiltScrollTranslate', 'motion-tiltScrollRotate'].map((name) => name + suffix); } export function web(options: ScrubAnimationOptions, _dom?: DomApi) { @@ -28,16 +35,20 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { export function style(options: ScrubAnimationOptions, asWeb = false) { const namedEffect = options.namedEffect as TiltScroll; - const direction = parseDirection(namedEffect?.direction, TWO_SIDES_DIRECTIONS, DEFAULT_DIRECTION); - const { parallaxFactor = 0, perspective = 400 } = namedEffect; - const { range = 'in' } = namedEffect; + const direction = parseDirection( + namedEffect?.direction, + SPIN_DIRECTIONS, + schema.direction.default, + ); + const { speed = schema.speed.default, perspective = schema.perspective.default } = namedEffect; + const { range = schema.range.default } = namedEffect; const easing = 'linear'; const fill = ( range === 'out' ? 'forwards' : range === 'in' ? 'backwards' : options.fill ) as AnimationFillMode; - const travelY = MAX_Y_TRAVEL * parallaxFactor; - const dir = DIRECTIONS_MAP[direction]; + const travelY = MAX_Y_TRAVEL * (1 - 1 / Math.max(speed, EPSILON)); + const dir = DIRECTIONS_MAP[direction] * (range === 'in' ? 1 : -1); const from = { x: ROTATION_X * (range === 'out' ? 0 : -1), diff --git a/packages/motion-presets/src/library/scroll/TurnScroll.ts b/packages/motion-presets/src/library/scroll/TurnScroll.ts index e4cc3937..7c402931 100644 --- a/packages/motion-presets/src/library/scroll/TurnScroll.ts +++ b/packages/motion-presets/src/library/scroll/TurnScroll.ts @@ -1,24 +1,29 @@ import type { AnimationFillMode, DomApi, + EffectSpinDirection, EffectTwoSides, ScrubAnimationOptions, TurnScroll, } from '../../types'; import { toKeyframeValue, parseDirection } from '../../utils'; -import { SPIN_DIRECTIONS, TWO_SIDES_DIRECTIONS } from '../../consts'; +import { SCROLL_RANGES, SPIN_DIRECTIONS, TWO_SIDES_DIRECTIONS } from '../../consts'; -const ELEMENT_ROTATION = 45; -const DEFAULT_DIRECTION: EffectTwoSides = 'right'; -const DEFAULT_SPIN: (typeof SPIN_DIRECTIONS)[number] = 'clockwise'; +export const schema = { + angle: { type: 'number', default: 45 }, + direction: { type: 'enum', values: TWO_SIDES_DIRECTIONS, default: 'left' }, + range: { type: 'enum', values: SCROLL_RANGES, default: 'in' }, + scale: { type: 'number', min: 0, default: 1 }, + spin: { type: 'enum', values: SPIN_DIRECTIONS, default: 'clockwise' }, +} as const; const ROTATE_DIRECTION_MAP = { clockwise: 1, 'counter-clockwise': -1, }; -export function getNames(_: ScrubAnimationOptions) { - return ['motion-turnScroll']; +export function getNames({ suffix = '' }: ScrubAnimationOptions) { + return ['motion-turnScroll'].map((name) => name + suffix); } export function prepare(_: ScrubAnimationOptions, dom?: DomApi) { @@ -44,9 +49,22 @@ export function web(options: ScrubAnimationOptions, dom?: DomApi) { export function style(options: ScrubAnimationOptions, asWeb = false) { const namedEffect = options.namedEffect as TurnScroll; - const direction = parseDirection(namedEffect?.direction, TWO_SIDES_DIRECTIONS, DEFAULT_DIRECTION); - const spin = parseDirection(namedEffect?.spin, SPIN_DIRECTIONS, DEFAULT_SPIN); - const { scale = 1, range = 'in' } = namedEffect; + const toDirection = parseDirection( + namedEffect?.direction, + TWO_SIDES_DIRECTIONS, + schema.direction.default, + ); + const direction: EffectTwoSides = toDirection === 'left' ? 'right' : 'left'; + const spin = parseDirection( + namedEffect?.spin, + SPIN_DIRECTIONS, + schema.spin.default, + ); + const { + angle = schema.angle.default, + scale = schema.scale.default, + range = schema.range.default, + } = namedEffect; const easing = 'linear'; const fill = ( range === 'out' ? 'forwards' : range === 'in' ? 'backwards' : options.fill @@ -61,7 +79,7 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { const endXLeft = `calc(100vw - ${toKeyframeValue({}, '--motion-left', false, '0px')})`; const [startX, endX] = direction === 'left' ? [startXLeft, endXLeft] : [endXLeft, startXLeft]; - const rotate = ELEMENT_ROTATION * ROTATE_DIRECTION_MAP[spin]; + const rotate = angle * ROTATE_DIRECTION_MAP[spin]; const fromValues = { rotation: range === 'out' ? 0 : -rotate, diff --git a/packages/motion-presets/src/library/scroll/test/FlipScroll.spec.ts b/packages/motion-presets/src/library/scroll/test/FlipScroll.spec.ts index 1501f274..c8213463 100644 --- a/packages/motion-presets/src/library/scroll/test/FlipScroll.spec.ts +++ b/packages/motion-presets/src/library/scroll/test/FlipScroll.spec.ts @@ -30,10 +30,10 @@ describe('FlipScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom rotate value', () => { + test('custom angle value', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { rotate: 180 } as FlipScrollType, + namedEffect: { angle: 180 } as FlipScrollType, }; const expectedResult = [ @@ -159,10 +159,10 @@ describe('FlipScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom rotate value', () => { + test('custom angle value', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { rotate: 180 } as FlipScrollType, + namedEffect: { angle: 180 } as FlipScrollType, }; const expectedResult = [ diff --git a/packages/motion-presets/src/library/scroll/test/GrowScroll.spec.ts b/packages/motion-presets/src/library/scroll/test/GrowScroll.spec.ts index 3b5e845e..0327dac0 100644 --- a/packages/motion-presets/src/library/scroll/test/GrowScroll.spec.ts +++ b/packages/motion-presets/src/library/scroll/test/GrowScroll.spec.ts @@ -20,7 +20,7 @@ describe('GrowScroll', () => { keyframes: [ { transform: - 'translateY(0vh) translate(0%, 0%) scale(0) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', + 'translateY(0vh) translate(0%, 0%) scale(0.25) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', }, { transform: @@ -64,10 +64,10 @@ describe('GrowScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom direction - top-right', () => { + test('custom pivot - top-right', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'top-right' } as GrowScrollType, + namedEffect: { pivot: 'top-right', scale: 0 } as GrowScrollType, }; const expectedResult = [ @@ -90,10 +90,10 @@ describe('GrowScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom direction - bottom-left', () => { + test('custom pivot - bottom-left', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'bottom-left' } as GrowScrollType, + namedEffect: { pivot: 'bottom-left', scale: 0 } as GrowScrollType, }; const expectedResult = [ @@ -116,10 +116,10 @@ describe('GrowScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom direction - bottom-right', () => { + test('custom pivot - bottom-right', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'bottom-right' } as GrowScrollType, + namedEffect: { pivot: 'bottom-right', scale: 0 } as GrowScrollType, }; const expectedResult = [ @@ -142,10 +142,10 @@ describe('GrowScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom direction - top-left', () => { + test('custom pivot - top-left', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'top-left' } as GrowScrollType, + namedEffect: { pivot: 'top-left', scale: 0 } as GrowScrollType, }; const expectedResult = [ @@ -168,10 +168,10 @@ describe('GrowScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom direction - bottom', () => { + test('custom pivot - bottom', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'bottom' } as GrowScrollType, + namedEffect: { pivot: 'bottom', scale: 0 } as GrowScrollType, }; const expectedResult = [ @@ -194,10 +194,10 @@ describe('GrowScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom direction - top', () => { + test('custom pivot - top', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'top' } as GrowScrollType, + namedEffect: { pivot: 'top', scale: 0 } as GrowScrollType, }; const expectedResult = [ @@ -220,10 +220,10 @@ describe('GrowScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom direction - left', () => { + test('custom pivot - left', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'left' } as GrowScrollType, + namedEffect: { pivot: 'left', scale: 0 } as GrowScrollType, }; const expectedResult = [ @@ -246,10 +246,10 @@ describe('GrowScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom direction - right', () => { + test('custom pivot - right', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'right' } as GrowScrollType, + namedEffect: { pivot: 'right', scale: 0 } as GrowScrollType, }; const expectedResult = [ @@ -275,17 +275,17 @@ describe('GrowScroll', () => { test('custom speed', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { speed: 0.5 } as GrowScrollType, + namedEffect: { speed: 0.5, scale: 0 } as GrowScrollType, }; const expectedResult = [ { - startOffsetAdd: '-20vh', + startOffsetAdd: '-40vh', endOffsetAdd: '0px', keyframes: [ { transform: - 'translateY(-20vh) translate(0%, 0%) scale(0) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', + 'translateY(-40vh) translate(0%, 0%) scale(0) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', }, { transform: @@ -360,10 +360,10 @@ describe('GrowScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom direction - top-right', () => { + test('custom pivot - top-right', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'top-right' } as GrowScrollType, + namedEffect: { pivot: 'top-right', scale: 0 } as GrowScrollType, }; const expectedResult = [ diff --git a/packages/motion-presets/src/library/scroll/test/MoveScroll.spec.ts b/packages/motion-presets/src/library/scroll/test/MoveScroll.spec.ts index 0c04b3f5..15f50273 100644 --- a/packages/motion-presets/src/library/scroll/test/MoveScroll.spec.ts +++ b/packages/motion-presets/src/library/scroll/test/MoveScroll.spec.ts @@ -33,11 +33,11 @@ describe('MoveScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom distance', () => { + test('custom travel', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, namedEffect: { - distance: { value: 200, unit: 'percentage' }, + travel: { value: 200, unit: 'percentage' }, } as MoveScrollType, }; @@ -61,10 +61,10 @@ describe('MoveScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom angle', () => { + test('custom direction', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { angle: 45 } as MoveScrollType, + namedEffect: { direction: -135 } as MoveScrollType, }; const expectedResult = [ @@ -88,7 +88,7 @@ describe('MoveScroll', () => { test('custom range - out', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { range: 'out' } as MoveScrollType, + namedEffect: { range: 'out', direction: 120 } as MoveScrollType, }; const expectedResult = [ @@ -169,11 +169,11 @@ describe('MoveScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom distance', () => { + test('custom travel', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, namedEffect: { - distance: { value: 200, unit: 'percentage' }, + travel: { value: 200, unit: 'percentage' }, } as MoveScrollType, }; @@ -202,7 +202,7 @@ describe('MoveScroll', () => { test('custom range - out', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { range: 'out' } as MoveScrollType, + namedEffect: { range: 'out', direction: 120 } as MoveScrollType, }; const expectedResult = [ diff --git a/packages/motion-presets/src/library/scroll/test/PanScroll.spec.ts b/packages/motion-presets/src/library/scroll/test/PanScroll.spec.ts index 0e4d835a..1199e81c 100644 --- a/packages/motion-presets/src/library/scroll/test/PanScroll.spec.ts +++ b/packages/motion-presets/src/library/scroll/test/PanScroll.spec.ts @@ -59,10 +59,10 @@ describe('PanScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom direction - right', () => { + test('custom direction - left', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'right' } as PanScrollType, + namedEffect: { direction: 'left' } as PanScrollType, }; const expectedResult = [ @@ -87,7 +87,7 @@ describe('PanScroll', () => { test('custom range - out', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { range: 'out' } as PanScrollType, + namedEffect: { range: 'out', direction: 'left' } as PanScrollType, }; const expectedResult = [ @@ -191,10 +191,10 @@ describe('PanScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom direction - right', () => { + test('custom direction - left', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'right' } as PanScrollType, + namedEffect: { direction: 'left' } as PanScrollType, }; const expectedResult = [ @@ -218,7 +218,7 @@ describe('PanScroll', () => { test('custom range - out', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { range: 'out' } as PanScrollType, + namedEffect: { range: 'out', direction: 'left' } as PanScrollType, }; const expectedResult = [ diff --git a/packages/motion-presets/src/library/scroll/test/ParallaxScroll.spec.ts b/packages/motion-presets/src/library/scroll/test/ParallaxScroll.spec.ts index 68c0d506..526a469b 100644 --- a/packages/motion-presets/src/library/scroll/test/ParallaxScroll.spec.ts +++ b/packages/motion-presets/src/library/scroll/test/ParallaxScroll.spec.ts @@ -34,22 +34,23 @@ describe('ParallaxScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom parallaxFactor', () => { + test('custom speed', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { parallaxFactor: 0.75 } as ParallaxScrollType, + namedEffect: { speed: 0.5 } as ParallaxScrollType, }; const expectedResult = [ { - startOffsetAdd: '-37.5vh', - endOffsetAdd: '37.5vh', + startOffsetAdd: '-62.9971840901091vh', + endOffsetAdd: '62.9971840901091vh', keyframes: [ { - transform: 'translateY(calc(-1 * 37.5vh)) rotate(var(--motion-rotate, 0))', + transform: + 'translateY(calc(-1 * 62.9971840901091vh)) rotate(var(--motion-rotate, 0))', }, { - transform: 'translateY(37.5vh) rotate(var(--motion-rotate, 0))', + transform: 'translateY(62.9971840901091vh) rotate(var(--motion-rotate, 0))', }, ], }, @@ -91,20 +92,20 @@ describe('ParallaxScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom parallaxFactor', () => { + test('custom center', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { parallaxFactor: 0.75 } as ParallaxScrollType, + namedEffect: { center: 0.25 } as ParallaxScrollType, }; const expectedResult = [ { - startOffsetAdd: '-37.5vh', + startOffsetAdd: '-12.5vh', endOffsetAdd: '37.5vh', keyframes: [ { transform: - 'translateY(calc(-1 * var(--motion-parallax-to))) rotate(var(--motion-rotate, 0))', + 'translateY(calc(-0.3333333333333333 * var(--motion-parallax-to))) rotate(var(--motion-rotate, 0))', }, { transform: 'translateY(var(--motion-parallax-to)) rotate(var(--motion-rotate, 0))', diff --git a/packages/motion-presets/src/library/scroll/test/RevealScroll.spec.ts b/packages/motion-presets/src/library/scroll/test/RevealScroll.spec.ts index afce28b8..ea165157 100644 --- a/packages/motion-presets/src/library/scroll/test/RevealScroll.spec.ts +++ b/packages/motion-presets/src/library/scroll/test/RevealScroll.spec.ts @@ -107,7 +107,7 @@ describe('RevealScroll', () => { test('custom range - out', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { range: 'out' } as RevealScrollType, + namedEffect: { range: 'out', direction: 'top' } as RevealScrollType, }; const expectedResult = [ @@ -211,7 +211,7 @@ describe('RevealScroll', () => { test('custom range - out', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { range: 'out' } as RevealScrollType, + namedEffect: { range: 'out', direction: 'top' } as RevealScrollType, }; const expectedResult = [ diff --git a/packages/motion-presets/src/library/scroll/test/ShapeScroll.spec.ts b/packages/motion-presets/src/library/scroll/test/ShapeScroll.spec.ts index 334687e6..95a275c0 100644 --- a/packages/motion-presets/src/library/scroll/test/ShapeScroll.spec.ts +++ b/packages/motion-presets/src/library/scroll/test/ShapeScroll.spec.ts @@ -129,10 +129,10 @@ describe('ShapeScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom intensity', () => { + test('custom start', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { intensity: 0.75 } as ShapeScrollType, + namedEffect: { start: 0.25 } as ShapeScrollType, }; const expectedResult = [ diff --git a/packages/motion-presets/src/library/scroll/test/ShrinkScroll.spec.ts b/packages/motion-presets/src/library/scroll/test/ShrinkScroll.spec.ts index 69cf751b..594f4ffd 100644 --- a/packages/motion-presets/src/library/scroll/test/ShrinkScroll.spec.ts +++ b/packages/motion-presets/src/library/scroll/test/ShrinkScroll.spec.ts @@ -39,7 +39,7 @@ describe('ShrinkScroll', () => { test('custom range - out', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { range: 'out' } as ShrinkScrollType, + namedEffect: { range: 'out', scale: 0.8 } as ShrinkScrollType, }; const expectedResult = [ @@ -65,10 +65,10 @@ describe('ShrinkScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom direction - top-right', () => { + test('custom pivot - top-right', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'top-right' } as ShrinkScrollType, + namedEffect: { pivot: 'top-right' } as ShrinkScrollType, }; const expectedResult = [ @@ -120,7 +120,7 @@ describe('ShrinkScroll', () => { test('custom speed', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { speed: 0.5 } as ShrinkScrollType, + namedEffect: { speed: 2 / 3 } as ShrinkScrollType, }; const expectedResult = [ @@ -180,7 +180,7 @@ describe('ShrinkScroll', () => { test('custom range - out', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { range: 'out' } as ShrinkScrollType, + namedEffect: { range: 'out', scale: 0.8 } as ShrinkScrollType, }; const expectedResult = [ @@ -206,10 +206,10 @@ describe('ShrinkScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom direction - top-right', () => { + test('custom pivot - top-right', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'top-right' } as ShrinkScrollType, + namedEffect: { pivot: 'top-right' } as ShrinkScrollType, }; const expectedResult = [ diff --git a/packages/motion-presets/src/library/scroll/test/SkewPanScroll.spec.ts b/packages/motion-presets/src/library/scroll/test/SkewPanScroll.spec.ts index 1ac1774e..ecf2ee6c 100644 --- a/packages/motion-presets/src/library/scroll/test/SkewPanScroll.spec.ts +++ b/packages/motion-presets/src/library/scroll/test/SkewPanScroll.spec.ts @@ -58,10 +58,10 @@ describe('SkewPanScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom direction - left', () => { + test('custom direction - right', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'left' } as SkewPanScrollType, + namedEffect: { direction: 'right' } as SkewPanScrollType, }; const expectedResult = [ @@ -86,7 +86,7 @@ describe('SkewPanScroll', () => { test('custom range - out', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { range: 'out' } as SkewPanScrollType, + namedEffect: { range: 'out', direction: 'right' } as SkewPanScrollType, }; const expectedResult = [ @@ -98,7 +98,7 @@ describe('SkewPanScroll', () => { }, { transform: - 'translateX(calc(100vw - var(--motion-left, 0px))) skewX(10deg) rotate(var(--motion-rotate, 0))', + 'translateX(calc(100vw - var(--motion-left, 0px))) skewX(-10deg) rotate(var(--motion-rotate, 0))', }, ], }, @@ -165,10 +165,10 @@ describe('SkewPanScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom direction - left', () => { + test('custom direction - right', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'left' } as SkewPanScrollType, + namedEffect: { direction: 'right' } as SkewPanScrollType, }; const expectedResult = [ @@ -194,7 +194,7 @@ describe('SkewPanScroll', () => { test('custom range - out', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { range: 'out' } as SkewPanScrollType, + namedEffect: { range: 'out', direction: 'right' } as SkewPanScrollType, }; const expectedResult = [ diff --git a/packages/motion-presets/src/library/scroll/test/SlideScroll.spec.ts b/packages/motion-presets/src/library/scroll/test/SlideScroll.spec.ts index 352395a9..374f187d 100644 --- a/packages/motion-presets/src/library/scroll/test/SlideScroll.spec.ts +++ b/packages/motion-presets/src/library/scroll/test/SlideScroll.spec.ts @@ -33,10 +33,10 @@ describe('SlideScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom direction - left', () => { + test('custom direction - right', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'left' } as SlideScrollType, + namedEffect: { direction: 'right' } as SlideScrollType, }; const expectedResult = [ @@ -62,7 +62,7 @@ describe('SlideScroll', () => { test('custom range - out', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { range: 'out' } as SlideScrollType, + namedEffect: { range: 'out', direction: 'bottom' } as SlideScrollType, }; const expectedResult = [ @@ -147,10 +147,10 @@ describe('SlideScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom direction - left', () => { + test('custom direction - right', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'left' } as SlideScrollType, + namedEffect: { direction: 'right' } as SlideScrollType, }; const expectedResult = [ @@ -178,7 +178,7 @@ describe('SlideScroll', () => { test('custom range - out', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { range: 'out' } as SlideScrollType, + namedEffect: { range: 'out', direction: 'bottom' } as SlideScrollType, }; const expectedResult = [ diff --git a/packages/motion-presets/src/library/scroll/test/Spin3dScroll.spec.ts b/packages/motion-presets/src/library/scroll/test/Spin3dScroll.spec.ts index 8e7d7e08..36078495 100644 --- a/packages/motion-presets/src/library/scroll/test/Spin3dScroll.spec.ts +++ b/packages/motion-presets/src/library/scroll/test/Spin3dScroll.spec.ts @@ -36,10 +36,10 @@ describe('Spin3dScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom rotate value', () => { + test('custom angle value', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { rotate: -50 } as Spin3dScrollType, + namedEffect: { angle: 50 } as Spin3dScrollType, }; const expectedResult = [ @@ -124,7 +124,7 @@ describe('Spin3dScroll', () => { test('custom speed', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { speed: 0.5 } as Spin3dScrollType, + namedEffect: { speed: 2 / 3 } as Spin3dScrollType, }; const expectedResult = [ @@ -184,10 +184,10 @@ describe('Spin3dScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom rotate value', () => { + test('custom angle value', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { rotate: -50 } as Spin3dScrollType, + namedEffect: { angle: 50 } as Spin3dScrollType, }; const expectedResult = [ @@ -281,7 +281,7 @@ describe('Spin3dScroll', () => { test('custom speed', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { speed: 0.5 } as Spin3dScrollType, + namedEffect: { speed: 2 / 3 } as Spin3dScrollType, }; const expectedResult = [ diff --git a/packages/motion-presets/src/library/scroll/test/SpinScroll.spec.ts b/packages/motion-presets/src/library/scroll/test/SpinScroll.spec.ts index 85a5cc93..b92ca4f8 100644 --- a/packages/motion-presets/src/library/scroll/test/SpinScroll.spec.ts +++ b/packages/motion-presets/src/library/scroll/test/SpinScroll.spec.ts @@ -314,4 +314,15 @@ describe('SpinScroll', () => { expect(result).toMatchObject(expectedResult); }); }); + + test('suffix is appended to the animation names', () => { + const mockOptions: ScrubAnimationOptions = { + ...baseMockOptions, + suffix: '-a', + namedEffect: {} as SpinScrollType, + }; + + expect(SpinScroll.getNames(mockOptions)).toEqual(['motion-spinScroll-a']); + expect(SpinScroll.style(mockOptions).map(({ name }) => name)).toEqual(['motion-spinScroll-a']); + }); }); diff --git a/packages/motion-presets/src/library/scroll/test/TiltScroll.spec.ts b/packages/motion-presets/src/library/scroll/test/TiltScroll.spec.ts index f7408749..e68b7d48 100644 --- a/packages/motion-presets/src/library/scroll/test/TiltScroll.spec.ts +++ b/packages/motion-presets/src/library/scroll/test/TiltScroll.spec.ts @@ -51,7 +51,7 @@ describe('TiltScroll', () => { test('custom range - out', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { range: 'out' } as TiltScrollType, + namedEffect: { range: 'out', direction: 'clockwise' } as TiltScrollType, }; const expectedResult = [ @@ -92,7 +92,7 @@ describe('TiltScroll', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, fill: 'both', - namedEffect: { range: 'continuous' } as TiltScrollType, + namedEffect: { range: 'continuous', direction: 'clockwise' } as TiltScrollType, }; const expectedResult = [ @@ -129,10 +129,10 @@ describe('TiltScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom direction - left', () => { + test('custom direction - clockwise', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'left' } as TiltScrollType, + namedEffect: { direction: 'clockwise' } as TiltScrollType, }; const expectedResult = [ @@ -163,10 +163,10 @@ describe('TiltScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom parallaxFactor', () => { + test('custom speed', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { parallaxFactor: 0.5 } as TiltScrollType, + namedEffect: { speed: 2 } as TiltScrollType, }; const expectedResult = [ @@ -245,7 +245,7 @@ describe('TiltScroll', () => { test('custom range - out', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { range: 'out' } as TiltScrollType, + namedEffect: { range: 'out', direction: 'clockwise' } as TiltScrollType, }; const expectedResult = [ @@ -291,7 +291,7 @@ describe('TiltScroll', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, fill: 'both', - namedEffect: { range: 'continuous' } as TiltScrollType, + namedEffect: { range: 'continuous', direction: 'clockwise' } as TiltScrollType, }; const expectedResult = [ @@ -333,10 +333,10 @@ describe('TiltScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom direction - left', () => { + test('custom direction - clockwise', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'left' } as TiltScrollType, + namedEffect: { direction: 'clockwise' } as TiltScrollType, }; const expectedResult = [ @@ -372,10 +372,10 @@ describe('TiltScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom parallaxFactor', () => { + test('custom speed', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { parallaxFactor: 0.5 } as TiltScrollType, + namedEffect: { speed: 2 } as TiltScrollType, }; const expectedResult = [ diff --git a/packages/motion-presets/src/library/scroll/test/TurnScroll.spec.ts b/packages/motion-presets/src/library/scroll/test/TurnScroll.spec.ts index 15dbfdd3..fbadf573 100644 --- a/packages/motion-presets/src/library/scroll/test/TurnScroll.spec.ts +++ b/packages/motion-presets/src/library/scroll/test/TurnScroll.spec.ts @@ -58,10 +58,10 @@ describe('TurnScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom direction - left', () => { + test('custom direction - right', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'left' } as TurnScrollType, + namedEffect: { direction: 'right' } as TurnScrollType, }; const expectedResult = [ @@ -216,10 +216,10 @@ describe('TurnScroll', () => { expect(result).toMatchObject(expectedResult); }); - test('custom direction - left', () => { + test('custom direction - right', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { direction: 'left' } as TurnScrollType, + namedEffect: { direction: 'right' } as TurnScrollType, }; const expectedResult = [ diff --git a/packages/motion-presets/src/test/presets.spec.ts b/packages/motion-presets/src/test/presets.spec.ts new file mode 100644 index 00000000..adc1206c --- /dev/null +++ b/packages/motion-presets/src/test/presets.spec.ts @@ -0,0 +1,128 @@ +import { describe, expect, test } from 'vitest'; + +import * as entrance from '../library/entrance'; +import * as scroll from '../library/scroll'; +import * as ongoing from '../library/ongoing'; +import * as mouse from '../library/mouse'; + +type Field = { + type: string; + values?: readonly string[]; + min?: number; + max?: number; + default: unknown; +}; +type PresetModule = { + schema?: Record; + getNames: (options: any) => string[]; + style?: (options: any, asWeb?: boolean) => any[]; + web: (options: any) => any[]; +}; + +const SKIP = ['DVD']; +// getNames appends an undefined suffix +const GROUP_OPTIONS = { + entrance: { duration: 1000 }, + scroll: { + startOffset: { name: 'cover', offset: { value: 0, unit: 'percentage' } }, + endOffset: { name: 'cover', offset: { value: 100, unit: 'percentage' } }, + }, + ongoing: { duration: 1000 }, +}; + +const presets = Object.entries({ entrance, scroll, ongoing }).flatMap(([group, modules]) => + Object.entries(modules as Record) + .filter(([name, module]) => module.schema && !SKIP.includes(name)) + .map(([name, module]) => ({ group: group as keyof typeof GROUP_OPTIONS, name, module })), +); + +const optionsFor = (group: keyof typeof GROUP_OPTIONS, namedEffect: Record) => + structuredClone({ ...GROUP_OPTIONS[group], namedEffect }); + +const defaultsOf = (schema: Record) => + Object.fromEntries( + Object.entries(schema).map(([key, field]) => [key, structuredClone(field.default)]), + ); + +const withoutNamedEffect = (animations: any[]) => + animations.map(({ namedEffect: _, ...rest }) => rest); +const namesOf = (animations: any[]) => + animations.map((animation) => animation.name).filter(Boolean); + +const expectValidDefaults = (schema: Record) => { + for (const [key, field] of Object.entries(schema)) { + if (field.type === 'enum') { + expect(field.values, key).toContain(field.default); + } + if (field.type === 'number') { + expect(typeof field.default, key).toBe('number'); + if (field.min !== undefined) + expect(field.default as number, key).toBeGreaterThanOrEqual(field.min); + if (field.max !== undefined) + expect(field.default as number, key).toBeLessThanOrEqual(field.max); + } + if (field.type === 'bool') { + expect(typeof field.default, key).toBe('boolean'); + } + } +}; + +describe.each(presets)('$group $name', ({ group, name, module }) => { + const schema = module.schema!; + const render = module.style ?? module.web; + + test('implicit defaults equal the schema defaults', () => { + const implicit = render(optionsFor(group, { type: name })); + const explicit = render(optionsFor(group, { type: name, ...defaultsOf(schema) })); + expect(withoutNamedEffect(implicit)).toEqual(withoutNamedEffect(explicit)); + }); + + test('getNames matches the animation names', () => { + const options = optionsFor(group, { type: name }); + expect(namesOf(render(structuredClone(options)))).toEqual(module.getNames(options)); + }); + + test.skipIf(!module.style)('web and style return the same animations', () => { + const webAnimations = module.web(optionsFor(group, { type: name })); + const styleAnimations = module.style!(optionsFor(group, { type: name })); + expect(namesOf(webAnimations)).toEqual(namesOf(styleAnimations)); + expect(webAnimations.length).toBe(styleAnimations.length); + }); + + test('schema defaults are valid', () => { + expectValidDefaults(schema); + }); +}); + +type MouseModule = ((options: any) => (target: HTMLElement) => { + progress: (progress: any) => void; +}) & { schema?: Record }; + +const mousePresets = Object.entries(mouse as Record) + .filter(([, create]) => create.schema) + .map(([name, create]) => ({ name, create })); + +const styleAfterProgress = (create: MouseModule, namedEffect: Record) => { + const target = document.createElement('div'); + create({ namedEffect: structuredClone(namedEffect) })(target).progress({ + x: 0.2, + y: 0.7, + v: { x: 1, y: -1 }, + active: true, + }); + return target.style.cssText; +}; + +describe.each(mousePresets)('mouse $name', ({ name, create }) => { + const schema = create.schema!; + + test('implicit defaults equal the schema defaults', () => { + expect(styleAfterProgress(create, { type: name })).toEqual( + styleAfterProgress(create, { type: name, ...defaultsOf(schema) }), + ); + }); + + test('schema defaults are valid', () => { + expectValidDefaults(schema); + }); +}); diff --git a/packages/motion-presets/src/types.ts b/packages/motion-presets/src/types.ts index 5f8aa06a..8ee2ce37 100644 --- a/packages/motion-presets/src/types.ts +++ b/packages/motion-presets/src/types.ts @@ -35,6 +35,7 @@ import type { EffectTwoSides, MouseAnimationFactoryCreate, ScrubAnimationOptions, + Shape, UnitLengthPercentage, WebAnimationEffectFactory, } from '@wix/motion'; @@ -55,100 +56,109 @@ export type VerticalOffsetByDirectionParams = { export type OffsetByDirectionParams = HorizontalOffsetByDirectionParams & VerticalOffsetByDirectionParams; -export type FadeIn = { type: 'FadeIn' }; +export type LengthValue = { value: number; unit: string }; +export type LengthInput = + | string + | number + | LengthValue + | { value: number; unit?: string } + | undefined; + +export type EffectTwoAxes = 'horizontal' | 'vertical'; +export type EffectSpinDirection = 'clockwise' | 'counter-clockwise'; + export type ArcIn = { type: 'ArcIn'; - direction?: EffectFourDirections; - depth?: UnitLengthPercentage; + depth?: LengthInput; + from?: EffectFourDirections; perspective?: number; }; +export type BlurIn = { + type: 'BlurIn'; + blur?: number; +}; +export type BounceIn = { + type: 'BounceIn'; + from?: EffectFourDirections | 'back'; + perspective?: number; + travel?: LengthInput; +}; export type CurveIn = { type: 'CurveIn'; - direction?: 'left' | 'right' | 'pseudoLeft' | 'pseudoRight'; - depth?: UnitLengthPercentage; + depth?: LengthInput; + from?: EffectFourDirections; perspective?: number; }; export type DropIn = { type: 'DropIn'; - initialScale?: number; + scale?: number; +}; +export type ExpandIn = { + type: 'ExpandIn'; + from?: number | string; + scale?: number; + travel?: LengthInput; }; +export type FadeIn = { type: 'FadeIn' }; export type FlipIn = { type: 'FlipIn'; - direction?: EffectFourDirections; - initialRotate?: number; + angle?: number; + direction?: EffectTwoAxes; perspective?: number; }; export type FloatIn = { type: 'FloatIn'; - direction?: EffectFourDirections; + from?: EffectFourDirections; }; export type FoldIn = { type: 'FoldIn'; - direction?: EffectFourDirections; - initialRotate?: number; - perspective?: number; -}; -export type SlideIn = { - type: 'SlideIn'; - direction?: EffectFourDirections; - initialTranslate?: number; -}; -export type SpinIn = { - type: 'SpinIn'; - spins?: number; - direction?: 'clockwise' | 'counter-clockwise'; - initialScale?: number; -}; -export type BounceIn = { - type: 'BounceIn'; - direction?: EffectFourDirections | 'center'; - distanceFactor?: number; + angle?: number; perspective?: number; + pivot?: EffectFourDirections; }; export type GlideIn = { type: 'GlideIn'; - direction?: number; - distance?: UnitLengthPercentage | EffectFourDirections; + from?: number | string; + travel?: LengthInput; }; -export type TurnIn = { - type: 'TurnIn'; - direction?: EffectFourCorners; -}; -export type WinkIn = { - type: 'WinkIn'; - direction?: 'vertical' | 'horizontal'; -}; -export type TiltIn = { - type: 'TiltIn'; - direction?: EffectTwoSides; - depth?: UnitLengthPercentage; - perspective?: number; +export type RevealIn = { + type: 'RevealIn'; + from?: EffectFourDirections; }; export type ShapeIn = { type: 'ShapeIn'; shape?: 'circle' | 'ellipse' | 'rectangle' | 'diamond' | 'window'; }; - export type ShuttersIn = { type: 'ShuttersIn'; - direction?: EffectFourDirections; + from?: EffectFourDirections; shutters?: number; staggered?: boolean; }; -export type RevealIn = { - type: 'RevealIn'; - direction?: EffectFourDirections; +export type SlideIn = { + type: 'SlideIn'; + from?: EffectFourDirections; + start?: number; }; -export type BlurIn = { - type: 'BlurIn'; - blur?: number; +export type SpinIn = { + type: 'SpinIn'; + direction?: EffectSpinDirection; + scale?: number; + spins?: number; }; - -export type ExpandIn = { - type: 'ExpandIn'; - direction?: number | string; - distance?: UnitLengthPercentage; - initialScale?: number; +export type TiltIn = { + type: 'TiltIn'; + depth?: LengthInput; + from?: EffectTwoSides; + perspective?: number; +}; +export type TurnIn = { + type: 'TurnIn'; + pivot?: EffectFourCorners; +}; +export type WinkIn = { + type: 'WinkIn'; + direction?: EffectTwoAxes; }; export type EntranceAnimation = @@ -176,65 +186,67 @@ export type EntranceAnimations = Record SlideIn with direction:'left', preserving effectId 'hero-in', key 'hero', viewEnter, triggerType:'once'. Cards: staggered entrance via a sequence (offset + offsetEasing) fired by a single viewEnter trigger, with the sequence's effect carrying a selector that picks the .feature-card items (one trigger fanning across the cards \u2014 the selector case, not per-card listContainer) \u2014 not per-card delays; keys a single wrapper (the grid/section) so the cards bind. Because the cards are source \u2260 target (trigger on the wrapper, effect's selector fans across the cards), generate() emits no FOUC-hiding rules for them, so the card entrance effect sets fill:'backwards' to hold each card in its first-keyframe state before it enters. Preserves registerEffects, generate(config, true), Interact.create and the web entry point. Edited config validated with validateInteractConfig; no validator reference left in edited files. Targeted edit, valid references.", + "expected_output": "EDITS the existing files in place. Hero: FadeIn -> SlideIn with from:'left', preserving effectId 'hero-in', key 'hero', viewEnter, triggerType:'once'. Cards: staggered entrance via a sequence (offset + offsetEasing) fired by a single viewEnter trigger, with the sequence's effect carrying a selector that picks the .feature-card items (one trigger fanning across the cards \u2014 the selector case, not per-card listContainer) \u2014 not per-card delays; keys a single wrapper (the grid/section) so the cards bind. Because the cards are source \u2260 target (trigger on the wrapper, effect's selector fans across the cards), generate() emits no FOUC-hiding rules for them, so the card entrance effect sets fill:'backwards' to hold each card in its first-keyframe state before it enters. Preserves registerEffects, generate(config, true), Interact.create and the web entry point. Edited config validated with validateInteractConfig; no validator reference left in edited files. Targeted edit, valid references.", "files": [ "skills/interactor/evals/files/edit-config/index.html", "skills/interactor/evals/files/edit-config/interactions.js" ], "assertions": [ - "Hero switched to SlideIn from the left (namedEffect SlideIn, direction:'left', replacing FadeIn).", + "Hero switched to SlideIn from the left (namedEffect SlideIn, from:'left', replacing FadeIn).", "Hero identity preserved: effectId 'hero-in', key 'hero', trigger 'viewEnter', triggerType:'once'.", "Edited config is structurally valid: would pass validateInteractConfig; all effectId/sequenceId references resolve.", "Shipped edited files contain no @wix/interact-validate, validateInteractConfig, assertValidInteractConfig, or InteractValidationError reference \u2014 scope: shipped CODE files only (a mention in EXPLANATION.md/docs is fine).", diff --git a/skills/interactor/examples/examples/text-animations/Ripple_Hover.md b/skills/interactor/examples/examples/text-animations/Ripple_Hover.md index b6a1892e..a77f4a97 100644 --- a/skills/interactor/examples/examples/text-animations/Ripple_Hover.md +++ b/skills/interactor/examples/examples/text-animations/Ripple_Hover.md @@ -228,8 +228,8 @@ const config = { 'fade-up': { namedEffect: { type: 'SlideIn', - direction: 'bottom', - initialTranslate: 0.3, + from: 'bottom', + start: 0.7, }, duration: 1000, easing: 'ease-out', diff --git a/skills/interactor/references/presets.md b/skills/interactor/references/presets.md index 2345b15d..fc16fd90 100644 --- a/skills/interactor/references/presets.md +++ b/skills/interactor/references/presets.md @@ -22,7 +22,7 @@ CDN/quick-start or when you genuinely use most of the catalog. ## Two parameter layers (don't confuse them) - **Animation options** go on the **effect** (outside `namedEffect`): `duration`, `delay`, `easing`, `iterations`, `alternate`, `fill`, `reversed`; for scroll add `rangeStart`/`rangeEnd`, `transition*`. -- **Preset parameters** go **inside `namedEffect`**: the per-preset knobs below (`direction`, `distance`, `blur`, `intensity`, …). +- **Preset parameters** go **inside `namedEffect`**: the per-preset knobs below (`from`, `direction`, `travel`, `blur`, …). **If you don't know a param's name/type, omit it — defaults apply.** Guessing produces silently-wrong output. Never invent a `type`. @@ -35,13 +35,15 @@ produces silently-wrong output. Never invent a `type`. ## Conventions - **Suffix encodes category:** `…In` = entrance, `…Scroll` = scroll, `…Mouse` = mouse, **no suffix** = ongoing. -- **`direction` is overloaded** — the accepted set depends on the preset (cardinal `top/right/bottom/left`, `+center`, two-sided `left/right`, corners, 8-way, 9-way, axis `horizontal/vertical`, rotation `clockwise/counter-clockwise`, or a `0–360` number). Use the value set listed per preset. -- **On entrances, `direction` names the edge the element comes _from_**, not where - it heads. `direction: 'bottom'` starts the element **below** its resting place and +- **`from` / `direction` / `pivot` are overloaded** — the accepted set depends on the preset (cardinal `top/right/bottom/left`, `+back`, two-sided `left/right`, corners, 8-way, 9-way, axis `horizontal/vertical`, rotation `clockwise/counter-clockwise`, or a `0–360` number). Use the value set listed per preset. +- **Entrances use `from` — the edge the element comes _from_**, not where + it heads. `from: 'bottom'` starts the element **below** its resting place and drifts it **upward** into position; `'left'` starts it to the left and moves it - right. So the everyday "fade **up** into place" is `direction: 'bottom'` — picking - `'top'` gives you the opposite, a downward drift. Verified across all four cardinals - on the translate-based presets (`FloatIn`, `GlideIn`, and `SlideIn`'s movement). + right. So the everyday "fade **up** into place" is `from: 'bottom'` — picking + `'top'` gives you the opposite, a downward drift. +- **Scroll and ongoing presets use `direction` — where the motion _goes_.** + `SlideScroll` `direction: 'top'` moves the element upward. `pivot` names the fixed + edge/corner/point it rotates or scales around (`FoldIn`, `TurnIn`, `Grow/ShrinkScroll`, `Fold`, `Swing`). - **The clip-based entrances anchor their mask differently from each other** — don't assume the movement rule carries over. `RevealIn` uncovers **from** the named edge (`'left'` → the mask opens at the left edge and sweeps right). `SlideIn` is the @@ -49,8 +51,8 @@ produces silently-wrong output. Never invent a `type`. from the **right** edge, which is what makes it read as sliding out from behind a fixed edge rather than being wiped on. If the exact reveal geometry matters, check the emitted `--motion-clip-start` polygon rather than reasoning from the name. -- **Angle convention** (numeric directions/angles): `0° = right`, increasing **counter-clockwise** → `90° = top`, `180° = left`, `270° = bottom`. Same polarity — the angle points at the edge it enters from. -- **Distances** use `{ value, unit }` with unit ∈ `px | em | rem | vh | vw | vmin | vmax | percentage`. Strings like `'120px'` also parse. +- **Angle convention** (numeric directions/angles): CSS convention — `0° = right`, increasing **clockwise** → `90° = bottom`, `180° = left`, `270° = top`. On entrances the angle points at the edge it enters from; on `MoveScroll` it points where the element moves. +- **Lengths of movement** are `travel`, using `{ value, unit }` with unit ∈ `px | em | rem | vh | vw | vmin | vmax | percentage`. Strings like `'120px'` and plain numbers (in the default unit) also parse. --- @@ -61,27 +63,27 @@ produces silently-wrong output. Never invent a `type`. For per-char or per-word text entrances, use the `$splitText` plugin to split text into spans, then stagger them with a sequence — see `plugins.md`. -| Preset | Params (default) | Look | -| :----------- | :------------------------------------------------------------------------------------------------------------ | :-------------------------------------- | -| `FadeIn` | — | transparent → opaque | -| `GlideIn` | `direction` 0–360 \| cardinal (`180`/left), `distance` (`{100,'percentage'}`) | glides in from off-screen — **no fade** | -| `SlideIn` | `direction` cardinal (`'left'`), `initialTranslate` 0–1 (`1`) | slides in behind a clip mask | -| `FloatIn` | `direction` cardinal (`'left'`) | gentle drift + fade | -| `RevealIn` | `direction` cardinal (`'left'`) | clip-path reveal from an edge | -| `ExpandIn` | `initialScale` (`0`), `direction` number\|cardinal (`90`), `distance` (`{120,'percentage'}`) | expands from a point + fade | -| `BlurIn` | `blur` px (`6`) | blurred → sharp + fade | -| `FlipIn` | `direction` cardinal (`'top'`), `initialRotate` deg (`90`), `perspective` (`800`) | 3D flip into place | -| `ArcIn` | `direction` cardinal (`'right'`), `depth` (`{200,'px'}`), `perspective` (`800`) | swings in along a 3D arc | -| `ShuttersIn` | `direction` cardinal (`'right'`), `shutters` (`12`), `staggered` (`true`) | shutter strips open — **no fade** | -| `CurveIn` | `direction` `left\|right\|pseudoLeft\|pseudoRight` (`'right'`), `depth` (`{300,'px'}`), `perspective` (`200`) | 180° swing arc | -| `DropIn` | `initialScale` (`1.6`) | shrinks from larger to natural size | -| `FoldIn` | `direction` cardinal (`'top'`), `initialRotate` deg (`90`), `perspective` (`800`) | unfolds at a hinged edge | -| `ShapeIn` | `shape` `circle\|ellipse\|rectangle\|diamond\|window` (`'rectangle'`) | expanding clip-path shape | -| `TiltIn` | `direction` `left\|right` (`'left'`), `depth` (`{200,'px'}`), `perspective` (`800`) | 3D tilt + clip reveal | -| `WinkIn` | `direction` `horizontal\|vertical` (`'horizontal'`) | expands from the center axis | -| `SpinIn` | `spins` (`0.5`), `direction` `clockwise\|counter-clockwise` (`'clockwise'`), `initialScale` (`0`) | spins + scales in | -| `TurnIn` | `direction` corner (`'top-left'`) | rotates around a corner pivot | -| `BounceIn` | `direction` cardinal\|`center` (`'bottom'`), `distanceFactor` (`1`), `perspective` (`800`) | elastic bounce in | +| Preset | Params (default) | Look | +| :----------- | :------------------------------------------------------------------------------------------- | :-------------------------------------- | +| `FadeIn` | — | transparent → opaque | +| `GlideIn` | `from` 0–360 \| cardinal (`180`/left), `travel` (`{100,'percentage'}`) | glides in from off-screen — **no fade** | +| `SlideIn` | `from` cardinal (`'left'`), `start` 0–1 (`0`) | slides in behind a clip mask | +| `FloatIn` | `from` cardinal (`'left'`) | gentle drift + fade | +| `RevealIn` | `from` cardinal (`'left'`) | clip-path reveal from an edge | +| `ExpandIn` | `scale` 0–1 (`0`), `from` number\|cardinal (`270`/top), `travel` (`{120,'percentage'}`) | expands from a point + fade | +| `BlurIn` | `blur` px (`6`) | blurred → sharp + fade | +| `FlipIn` | `direction` `horizontal\|vertical` (`'vertical'`), `angle` deg (`90`), `perspective` (`800`) | 3D flip into place | +| `ArcIn` | `from` cardinal (`'right'`), `depth` (`{100,'px'}`), `perspective` (`800`) | swings in along a 3D arc | +| `ShuttersIn` | `from` cardinal (`'left'`), `shutters` (`12`), `staggered` (`true`) | shutter strips open — **no fade** | +| `CurveIn` | `from` cardinal (`'right'`), `depth` (`{900,'px'}`), `perspective` (`200`) | 180° swing arc | +| `DropIn` | `scale` ≥1 (`1.6`) | shrinks from larger to natural size | +| `FoldIn` | `pivot` cardinal (`'top'`), `angle` deg (`-90`), `perspective` (`800`) | unfolds at a hinged edge | +| `ShapeIn` | `shape` `circle\|ellipse\|rectangle\|diamond\|window` (`'rectangle'`) | expanding clip-path shape | +| `TiltIn` | `from` `left\|right` (`'left'`), `depth` (`{100,'px'}`), `perspective` (`800`) | 3D tilt + clip reveal | +| `WinkIn` | `direction` `horizontal\|vertical` (`'horizontal'`) | expands from the center axis | +| `SpinIn` | `spins` (`0.5`), `direction` `clockwise\|counter-clockwise` (`'clockwise'`), `scale` (`0`) | spins + scales in | +| `TurnIn` | `pivot` corner (`'top-left'`) | rotates around a corner pivot | +| `BounceIn` | `from` cardinal\|`back` (`'bottom'`), `travel` (`{50,'px'}`), `perspective` (`800`) | elastic bounce in | --- @@ -89,7 +91,9 @@ text into spans, then stagger them with a sequence — see `plugins.md`. **All share `range: 'in' | 'out' | 'continuous'`** (in = animate in on enter, out = animate out on exit, continuous = pass through idle across the full range; prefer -`'continuous'`) — **except `ParallaxScroll`**. Use `fill: 'both'`. +`'continuous'`) — **except `ParallaxScroll`**. Use `fill: 'both'`. `direction` is +where the motion goes; `speed` is the approximate visual speed relative to the +scroll (`1` = moves with the page). > **Two different "range"s — don't confuse them.** The `range` above is a **preset > option** that lives _inside_ `namedEffect` (e.g. `namedEffect: { type: 'FadeScroll', @@ -97,29 +101,29 @@ range: 'continuous' }`). It is unrelated to the effect-level **`rangeStart`/ > `rangeEnd`** (the ViewTimeline scroll _window_, a `RangeOffset` — see > `config-schema.md`). Every `viewProgress` effect — including `ParallaxScroll` — > still takes `rangeStart`/`rangeEnd` to define its scroll window; `ParallaxScroll` -> just doesn't accept the `range` _option_ (it uses `parallaxFactor`). - -| Preset | Params (default) | -| :--------------- | :---------------------------------------------------------------------------------------------------------------------------- | -| `FadeScroll` | `opacity` (`0`), `range` (`'in'`) | -| `RevealScroll` | `direction` cardinal (`'bottom'`), `range` (`'in'`) | -| `ParallaxScroll` | `parallaxFactor` (`0.5`) — **no `range`** | -| `MoveScroll` | `angle` deg (`120`), `distance` (`{400,'px'}`), `range` (`'in'`) | -| `SlideScroll` | `direction` cardinal (`'bottom'`), `range` (`'in'`) | -| `GrowScroll` | `direction` 9-way (`'center'`), `scale` (`0` in / `4` out), `speed` (`0`), `range` (`'in'`) | -| `ShrinkScroll` | `direction` 9-way (`'center'`), `scale` (`1.2` in / `0.8` out), `speed` (`0`), `range` (`'in'`) | -| `TiltScroll` | `direction` `left\|right` (`'right'`), `parallaxFactor` (`0`), `perspective` (`400`), `range` (`'in'`) | -| `PanScroll` | `direction` `left\|right` (`'left'`), `distance` (`{400,'px'}`), `startFromOffScreen` (`true`), `range` (`'in'`) | -| `BlurScroll` | `blur` (`6`), `range` (`'in'`) | -| `FlipScroll` | `direction` `vertical\|horizontal` (`'horizontal'`), `rotate` deg (`240`), `perspective` (`800`), `range` (`'continuous'`) | -| `SpinScroll` | `direction` `clockwise\|counter-clockwise` (`'clockwise'`), `spins` (`0.15`), `scale` (`1`), `range` (`'in'`) | -| `ArcScroll` | `direction` `vertical\|horizontal` (`'horizontal'`), `perspective` (`500`), `range` (`'in'`) | -| `ShapeScroll` | `shape` (`'circle'`), `intensity` (`0.5`), `range` (`'in'`) | -| `ShuttersScroll` | `direction` cardinal (`'right'`), `shutters` (`12`), `staggered` (`true`), `range` (`'in'`) | -| `SkewPanScroll` | `direction` `left\|right` (`'right'`), `skew` deg (`10`), `range` (`'in'`) | -| `Spin3dScroll` | `rotate` deg (`-100`), `speed` (`0`), `perspective` (`1000`), `range` (`'in'`) | -| `StretchScroll` | `stretch` (`0.6`), `range` (`'out'`) | -| `TurnScroll` | `direction` `left\|right` (`'right'`), `spin` `clockwise\|counter-clockwise` (`'clockwise'`), `scale` (`1`), `range` (`'in'`) | +> just doesn't accept the `range` _option_ (it uses `speed`). + +| Preset | Params (default) | +| :--------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- | +| `FadeScroll` | `opacity` (`0`), `range` (`'in'`) | +| `RevealScroll` | `direction` cardinal (`'bottom'`), `range` (`'in'`) | +| `ParallaxScroll` | `speed` (`0.7159`), `center` 0–1 (`0.5`) — **no `range`** | +| `MoveScroll` | `direction` deg (`-60`), `travel` (`{400,'px'}`), `range` (`'in'`) | +| `SlideScroll` | `direction` cardinal (`'top'`), `range` (`'in'`) | +| `GrowScroll` | `pivot` 9-way (`'center'`), `scale` (`0.25`; inverted to >1 for out/continuous), `speed` (`1`), `range` (`'in'`) | +| `ShrinkScroll` | `pivot` 9-way (`'center'`), `scale` (`1.2`; inverted to <1 for out/continuous), `speed` (`1`), `range` (`'in'`) | +| `TiltScroll` | `direction` `clockwise\|counter-clockwise` (`'counter-clockwise'`), `speed` (`1`), `perspective` (`400`), `range` (`'in'`) | +| `PanScroll` | `direction` `left\|right` (`'right'`), `distance` (`{400,'px'}`), `startFromOffScreen` (`true`), `range` (`'in'`) | +| `BlurScroll` | `blur` (`6`), `range` (`'in'`) | +| `FlipScroll` | `direction` `vertical\|horizontal` (`'horizontal'`), `angle` deg (`240`), `perspective` (`800`), `range` (`'continuous'`) | +| `SpinScroll` | `direction` `clockwise\|counter-clockwise` (`'clockwise'`), `spins` (`0.15`), `scale` (`1`), `range` (`'in'`) | +| `ArcScroll` | `direction` `vertical\|horizontal` (`'horizontal'`), `perspective` (`500`), `range` (`'in'`) | +| `ShapeScroll` | `shape` (`'circle'`), `start` 0–1 (`0.5`), `range` (`'in'`) | +| `ShuttersScroll` | `direction` cardinal (`'right'`), `shutters` (`12`), `staggered` (`true`), `range` (`'in'`) | +| `SkewPanScroll` | `direction` `left\|right` (`'left'`), `skew` deg (`10`), `range` (`'in'`) | +| `Spin3dScroll` | `angle` deg (`100`), `speed` (`1`), `perspective` (`1000`), `range` (`'in'`) | +| `StretchScroll` | `stretch` -1–1 (`0.6`), `range` (`'out'`) | +| `TurnScroll` | `direction` `left\|right` (`'left'`), `spin` `clockwise\|counter-clockwise` (`'clockwise'`), `angle` deg (`45`), `scale` (`1`), `range` (`'in'`) | --- @@ -129,21 +133,21 @@ Run with `iterations: Infinity` (or large) for a perpetual loop, or attach to a trigger. **All accept `iterationDelay`** (ms, default `0`) inside `namedEffect` — an idle pause appended after each cycle. -| Preset | Params (default) | -| :-------- | :--------------------------------------------------------------------------------------------------------- | -| `Pulse` | `intensity` 0–1 (`0`) | -| `Spin` | `direction` `clockwise\|counter-clockwise` (`'clockwise'`) | -| `Breathe` | `direction` `vertical\|horizontal\|center` (`'vertical'`), `distance` (`{25,'px'}`), `perspective` (`800`) | -| `Bounce` | `intensity` 0–1 (`0`) | -| `Wiggle` | `intensity` 0–1 (`0.5`) | -| `Flash` | — (only `iterationDelay`) | -| `Flip` | `direction` `vertical\|horizontal` (`'horizontal'`), `perspective` (`800`) | -| `Fold` | `direction` cardinal (`'top'`), `angle` deg (`15`) | -| `Jello` | `intensity` 0–1 (`0.25`) | -| `Poke` | `direction` cardinal (`'right'`), `intensity` 0–1 (`0.5`) | -| `Rubber` | `intensity` 0–1 (`0.5`) | -| `Swing` | `direction` cardinal pivot (`'top'`), `swing` deg (`20`) | -| `Cross` | `direction` 8-way (`'right'`) | +| Preset | Params (default) | +| :-------- | :------------------------------------------------------------------------------------------------------- | +| `Pulse` | `scale` (`0.93`) | +| `Spin` | `direction` `clockwise\|counter-clockwise` (`'clockwise'`) | +| `Breathe` | `direction` `vertical\|horizontal\|center` (`'vertical'`), `travel` (`{25,'px'}`), `perspective` (`800`) | +| `Bounce` | `travel` (`{49,'px'}`) | +| `Wiggle` | `angle` deg (`25`), `travel` (`{25,'px'}`) | +| `Flash` | — (only `iterationDelay`) | +| `Flip` | `direction` `vertical\|horizontal` (`'horizontal'`), `perspective` (`800`) | +| `Fold` | `pivot` cardinal (`'top'`), `angle` deg (`15`), `perspective` (`800`) | +| `Jello` | `skew` deg (`12.25`) | +| `Poke` | `direction` cardinal (`'right'`), `travel` (`{62.5,'px'}`) | +| `Rubber` | `stretch` -1–1 (`0.1`) | +| `Swing` | `pivot` cardinal (`'top'`), `angle` deg (`20`) | +| `Cross` | `direction` 8-way (`'right'`) | --- diff --git a/skills/interactor/references/triggers.md b/skills/interactor/references/triggers.md index 8185e282..fe55985b 100644 --- a/skills/interactor/references/triggers.md +++ b/skills/interactor/references/triggers.md @@ -71,7 +71,7 @@ trigger params** — the range lives on the _effect_ via `rangeStart` / `rangeEn rangeStart: { name: 'cover', offset: { value: 0, unit: 'percentage' } }, rangeEnd: { name: 'cover', offset: { value: 100, unit: 'percentage' } }, fill: 'both', - namedEffect: { type: 'ParallaxScroll', parallaxFactor: 0.4 }, + namedEffect: { type: 'ParallaxScroll', speed: 0.75 }, } } } ``` @@ -83,7 +83,7 @@ Replace every such `overflow: hidden` with `overflow: clip` (Tailwind: **Scroll presets need `range`.** Every `*Scroll` preset takes `range: 'in' | 'out' | 'continuous'` (prefer `'continuous'` unless you specifically want enter-only / exit-only) — **except** `ParallaxScroll`, which uses -`parallaxFactor`. Use `fill: 'both'` for scrub effects. +`speed`. Use `fill: 'both'` for scrub effects. --- @@ -220,7 +220,7 @@ spans (`.split-c`, `.split-w`, etc.) — use `$splitText` on the interaction, th { interactions: [{ key: 'features', trigger: 'viewEnter', sequences: [{ offset: 120, offsetEasing: 'quadOut', effects: [{ effectId: 'card-in', selector: '.feature-card' }] }] }], - effects: { 'card-in': { duration: 600, easing: 'ease-out', fill: 'backwards', namedEffect: { type: 'SlideIn', direction: 'bottom' }, triggerType: 'once' } } } + effects: { 'card-in': { duration: 600, easing: 'ease-out', fill: 'backwards', namedEffect: { type: 'SlideIn', from: 'bottom' }, triggerType: 'once' } } } ``` - `offset` = ms between consecutive items' starts. From 2db73ef12023c695de73322a3a9542890a644116 Mon Sep 17 00:00:00 2001 From: ameerf-wix Date: Tue, 20 Oct 2026 02:46:07 +0300 Subject: [PATCH 2/4] fix(motion-presets): make every schema param take effect - calc() lengths for every length param and rad/grad/turn/calc() angles for GlideIn/ExpandIn `from` (CSS cos()/sin() path) - Cross moves toward its direction (keyframes were frozen before measuring) - StretchScroll CSS fade matches WAAPI; PanScroll prepare measures by default - SpinIn fade capped at the animation duration - keyframe names collide only when keyframes are identical: units and per-param values moved into custom properties, timing-based shapes keyed on the rounded timing factor, ShuttersScroll names per range/stagger, step-start fades renamed to motion-fadeInStep - custom properties carry the suffix, so keyframe slots don't override each other - shutter count clamped to a positive integer (non-integers looped forever) - CurveIn vertical curves (top/bottom) covered by tests Co-Authored-By: Claude Opus 5.5 (1M context) --- .../src/library/entrance/ArcIn.ts | 36 ++- .../src/library/entrance/BlurIn.ts | 5 +- .../src/library/entrance/BounceIn.ts | 49 ++-- .../src/library/entrance/CurveIn.ts | 30 +- .../src/library/entrance/DropIn.ts | 5 +- .../src/library/entrance/ExpandIn.ts | 35 ++- .../src/library/entrance/FlipIn.ts | 11 +- .../src/library/entrance/FloatIn.ts | 9 +- .../src/library/entrance/FoldIn.ts | 15 +- .../src/library/entrance/GlideIn.ts | 34 ++- .../src/library/entrance/RevealIn.ts | 5 +- .../src/library/entrance/ShapeIn.ts | 9 +- .../src/library/entrance/ShuttersIn.ts | 11 +- .../src/library/entrance/SlideIn.ts | 11 +- .../src/library/entrance/SpinIn.ts | 11 +- .../src/library/entrance/TiltIn.ts | 28 +- .../src/library/entrance/TurnIn.ts | 13 +- .../src/library/entrance/WinkIn.ts | 11 +- .../src/library/entrance/test/ArcIn.spec.ts | 15 + .../library/entrance/test/BounceIn.spec.ts | 273 ++++++++++-------- .../src/library/entrance/test/CurveIn.spec.ts | 13 + .../library/entrance/test/ExpandIn.spec.ts | 67 +++++ .../src/library/entrance/test/GlideIn.spec.ts | 47 ++- .../library/entrance/test/ShuttersIn.spec.ts | 10 +- .../src/library/entrance/test/SpinIn.spec.ts | 10 + .../src/library/mouse/AiryMouse.ts | 16 +- .../src/library/mouse/BlobMouse.ts | 16 +- .../src/library/mouse/BlurMouse.ts | 16 +- .../src/library/mouse/BounceMouse.ts | 4 +- .../src/library/mouse/ScaleMouse.ts | 16 +- .../src/library/mouse/SkewMouse.ts | 16 +- .../src/library/mouse/Track3DMouse.ts | 16 +- .../src/library/mouse/TrackMouse.ts | 16 +- .../src/library/mouse/test/TrackMouse.spec.ts | 40 +++ .../src/library/ongoing/Bounce.ts | 25 +- .../src/library/ongoing/Breathe.ts | 73 ++--- .../src/library/ongoing/Cross.ts | 201 +++++-------- .../src/library/ongoing/Flip.ts | 21 +- .../src/library/ongoing/Fold.ts | 74 ++--- .../src/library/ongoing/Jello.ts | 5 +- .../src/library/ongoing/Poke.ts | 27 +- .../src/library/ongoing/Pulse.ts | 5 +- .../src/library/ongoing/Rubber.ts | 5 +- .../src/library/ongoing/Spin.ts | 5 +- .../src/library/ongoing/Swing.ts | 94 +++--- .../src/library/ongoing/Wiggle.ts | 18 +- .../src/library/ongoing/test/Bounce.spec.ts | 112 +++---- .../src/library/ongoing/test/Cross.spec.ts | 112 ++++++- .../src/library/ongoing/test/Fold.spec.ts | 34 +-- .../src/library/ongoing/test/Poke.spec.ts | 111 +++---- .../src/library/scroll/ArcScroll.ts | 15 +- .../src/library/scroll/BlurScroll.ts | 9 +- .../src/library/scroll/FadeScroll.ts | 9 +- .../src/library/scroll/FlipScroll.ts | 15 +- .../src/library/scroll/GrowScroll.ts | 37 +-- .../src/library/scroll/MoveScroll.ts | 37 ++- .../src/library/scroll/PanScroll.ts | 23 +- .../src/library/scroll/ParallaxScroll.ts | 12 +- .../src/library/scroll/RevealScroll.ts | 19 +- .../src/library/scroll/ShapeScroll.ts | 11 +- .../src/library/scroll/ShrinkScroll.ts | 37 +-- .../src/library/scroll/ShuttersScroll.ts | 25 +- .../src/library/scroll/SkewPanScroll.ts | 17 +- .../src/library/scroll/SlideScroll.ts | 35 ++- .../src/library/scroll/Spin3dScroll.ts | 39 +-- .../src/library/scroll/SpinScroll.ts | 17 +- .../src/library/scroll/StretchScroll.ts | 60 ++-- .../src/library/scroll/TiltScroll.ts | 39 +-- .../src/library/scroll/TurnScroll.ts | 25 +- .../library/scroll/test/MoveScroll.spec.ts | 48 +++ .../src/library/scroll/test/PanScroll.spec.ts | 27 +- .../scroll/test/ParallaxScroll.spec.ts | 11 +- .../library/scroll/test/StretchScroll.spec.ts | 86 ++++-- .../motion-presets/src/test/presets.spec.ts | 96 ++++++ .../motion-presets/src/test/utils.spec.ts | 11 +- packages/motion-presets/src/utils.ts | 33 ++- 76 files changed, 1659 insertions(+), 975 deletions(-) create mode 100644 packages/motion-presets/src/library/entrance/test/ExpandIn.spec.ts create mode 100644 packages/motion-presets/src/library/mouse/test/TrackMouse.spec.ts diff --git a/packages/motion-presets/src/library/entrance/ArcIn.ts b/packages/motion-presets/src/library/entrance/ArcIn.ts index 0413133a..ceb63b70 100644 --- a/packages/motion-presets/src/library/entrance/ArcIn.ts +++ b/packages/motion-presets/src/library/entrance/ArcIn.ts @@ -1,5 +1,5 @@ import type { ArcIn, TimeAnimationOptions, EffectFourDirections, DomApi } from '../../types'; -import { toKeyframeValue, parseDirection, parseLength, getEntranceFill } from '../../utils'; +import { toKeyframeValue, parseDirection, parseLengthOrCalc, getEntranceFill } from '../../utils'; import { FOUR_DIRECTIONS } from '../../consts'; const ROTATION_ANGLE = 80; @@ -26,6 +26,7 @@ export function getNames({ suffix = '' }: TimeAnimationOptions) { } export function style(options: TimeAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as ArcIn; const direction = parseDirection( namedEffect?.from, @@ -33,22 +34,25 @@ export function style(options: TimeAnimationOptions, asWeb = false) { schema.from.default, ); - const depth = parseLength(namedEffect.depth, schema.depth.default); + const depth = parseLengthOrCalc(namedEffect.depth, schema.depth.default); const { perspective = schema.perspective.default } = namedEffect; const [fadeIn, arcIn] = getNames(options); const easing = options.easing || 'quintInOut'; const { x, y, sign } = DIRECTION_MAP[direction]; - const depthValue = `${depth.value}${depth.unit === 'percentage' ? '%' : depth.unit}`; + const depthValue = + typeof depth === 'string' + ? depth + : `${depth.value}${depth.unit === 'percentage' ? '%' : depth.unit}`; const custom = { - '--motion-perspective': `${perspective}px`, - '--motion-arc-x': `${x}`, - '--motion-arc-y': `${y}`, - '--motion-arc-sign': `${sign}`, - '--motion-depth-negative': `calc(-1 * ${depthValue})`, - '--motion-depth-positive': depthValue, + [`--motion-perspective${suffix}`]: `${perspective}px`, + [`--motion-arc-x${suffix}`]: `${x}`, + [`--motion-arc-y${suffix}`]: `${y}`, + [`--motion-arc-sign${suffix}`]: `${sign}`, + [`--motion-depth-negative${suffix}`]: `calc(-1 * ${depthValue})`, + [`--motion-depth-positive${suffix}`]: depthValue, }; return [ @@ -69,26 +73,26 @@ export function style(options: TimeAnimationOptions, asWeb = false) { custom, keyframes: [ { - transform: `perspective(${toKeyframeValue(custom, '--motion-perspective', asWeb)}) translateZ(${toKeyframeValue(custom, '--motion-depth-negative', asWeb)}) rotateX(calc(${toKeyframeValue( + transform: `perspective(${toKeyframeValue(custom, `--motion-perspective${suffix}`, asWeb)}) translateZ(${toKeyframeValue(custom, `--motion-depth-negative${suffix}`, asWeb)}) rotateX(calc(${toKeyframeValue( custom, - '--motion-arc-x', + `--motion-arc-x${suffix}`, asWeb, )} * ${toKeyframeValue( custom, - '--motion-arc-sign', + `--motion-arc-sign${suffix}`, asWeb, )} * ${ROTATION_ANGLE}deg)) rotateY(calc(${toKeyframeValue( custom, - '--motion-arc-y', + `--motion-arc-y${suffix}`, asWeb, )} * ${toKeyframeValue( custom, - '--motion-arc-sign', + `--motion-arc-sign${suffix}`, asWeb, - )} * ${ROTATION_ANGLE}deg)) translateZ(${toKeyframeValue(custom, '--motion-depth-positive', asWeb)}) rotate(var(--motion-rotate, 0deg))`, + )} * ${ROTATION_ANGLE}deg)) translateZ(${toKeyframeValue(custom, `--motion-depth-positive${suffix}`, asWeb)}) rotate(var(--motion-rotate, 0deg))`, }, { - transform: `perspective(${toKeyframeValue(custom, '--motion-perspective', asWeb)}) translateZ(${toKeyframeValue(custom, '--motion-depth-negative', asWeb)}) rotateX(0deg) rotateY(0deg) translateZ(${toKeyframeValue(custom, '--motion-depth-positive', asWeb)}) rotate(var(--motion-rotate, 0deg))`, + transform: `perspective(${toKeyframeValue(custom, `--motion-perspective${suffix}`, asWeb)}) translateZ(${toKeyframeValue(custom, `--motion-depth-negative${suffix}`, asWeb)}) rotateX(0deg) rotateY(0deg) translateZ(${toKeyframeValue(custom, `--motion-depth-positive${suffix}`, asWeb)}) rotate(var(--motion-rotate, 0deg))`, }, ], }, diff --git a/packages/motion-presets/src/library/entrance/BlurIn.ts b/packages/motion-presets/src/library/entrance/BlurIn.ts index 5e789449..62048444 100644 --- a/packages/motion-presets/src/library/entrance/BlurIn.ts +++ b/packages/motion-presets/src/library/entrance/BlurIn.ts @@ -14,13 +14,14 @@ export function web(options: TimeAnimationOptions) { } export function style(options: TimeAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const { blur = schema.blur.default } = options.namedEffect as BlurIn; const [fadeIn, blurIn] = getNames(options); const easing = options.easing || 'linear'; const custom = { - '--motion-blur': `${blur}px`, + [`--motion-blur${suffix}`]: `${blur}px`, }; return [ @@ -42,7 +43,7 @@ export function style(options: TimeAnimationOptions, asWeb = false) { custom, keyframes: [ { - filter: `blur(${toKeyframeValue(custom, '--motion-blur', asWeb)})`, + filter: `blur(${toKeyframeValue(custom, `--motion-blur${suffix}`, asWeb)})`, }, { filter: 'blur(0px)', diff --git a/packages/motion-presets/src/library/entrance/BounceIn.ts b/packages/motion-presets/src/library/entrance/BounceIn.ts index 69a232d9..124404a1 100644 --- a/packages/motion-presets/src/library/entrance/BounceIn.ts +++ b/packages/motion-presets/src/library/entrance/BounceIn.ts @@ -3,8 +3,9 @@ import { getEasing, toKeyframeValue, parseDirection, - parseLength, - getCssUnits, + parseLengthOrCalc, + scaleLength, + roundNumber, getEntranceFill, } from '../../utils'; import type { BounceIn, TimeAnimationOptions } from '../../types'; @@ -44,55 +45,55 @@ const TRANSLATE_DIRECTION_MAP = { back: { x: 0, y: 0, z: -1 }, }; +const roundUnit = (value: number) => roundNumber(value, 6); + export function web(options: TimeAnimationOptions) { return style(options, true); } export function style(options: TimeAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as BounceIn; const direction = parseDirection<(typeof DIRECTIONS)[number]>( namedEffect?.from, DIRECTIONS, schema.from.default, ); - const travel = parseLength(namedEffect?.travel, schema.travel.default); - const distanceFactor = travel.value / schema.travel.default.value; - const unit = getCssUnits(travel.unit); + const travel = parseLengthOrCalc(namedEffect?.travel, schema.travel.default); + const distanceUnit = scaleLength(travel, 1 / schema.travel.default.value, roundUnit); const { perspective = schema.perspective.default } = namedEffect || {}; const [fadeIn, bounceIn] = getNames(options); const perspectiveValue = direction === 'back' ? `perspective(${perspective}px)` : ' '; const { x, y, z } = TRANSLATE_DIRECTION_MAP[direction]; const custom = { - '--motion-direction-x': x, - '--motion-direction-y': y, - '--motion-direction-z': z, - '--motion-distance-factor': distanceFactor, - '--motion-perspective': perspectiveValue, - '--motion-ease-in': getEasing(easeOut), - '--motion-ease-out': getEasing(easeIn), + [`--motion-direction-x${suffix}`]: x, + [`--motion-direction-y${suffix}`]: y, + [`--motion-direction-z${suffix}`]: z, + [`--motion-distance-unit${suffix}`]: distanceUnit, + [`--motion-perspective${suffix}`]: perspectiveValue, + [`--motion-ease-in${suffix}`]: getEasing(easeOut), + [`--motion-ease-out${suffix}`]: getEasing(easeIn), }; - const easeIn_ = toKeyframeValue(custom, '--motion-ease-in', asWeb); - const easeOut_ = toKeyframeValue(custom, '--motion-ease-out', asWeb); - const distanceFactor_ = toKeyframeValue(custom, '--motion-distance-factor', asWeb); - const perspective_ = toKeyframeValue(custom, '--motion-perspective', asWeb, ''); - const directionX = toKeyframeValue(custom, '--motion-direction-x', asWeb); - const directionY = toKeyframeValue(custom, '--motion-direction-y', asWeb); - const directionZ = toKeyframeValue(custom, '--motion-direction-z', asWeb); + const easeIn_ = toKeyframeValue(custom, `--motion-ease-in${suffix}`, asWeb); + const easeOut_ = toKeyframeValue(custom, `--motion-ease-out${suffix}`, asWeb); + const distanceUnit_ = toKeyframeValue(custom, `--motion-distance-unit${suffix}`, asWeb); + const perspective_ = toKeyframeValue(custom, `--motion-perspective${suffix}`, asWeb, ''); + const directionX = toKeyframeValue(custom, `--motion-direction-x${suffix}`, asWeb); + const directionY = toKeyframeValue(custom, `--motion-direction-y${suffix}`, asWeb); + const directionZ = toKeyframeValue(custom, `--motion-direction-z${suffix}`, asWeb); const keyframes = BOUNCE_KEYFRAMES.map(({ offset, translate }, index) => ({ offset: offset / 100, animationTimingFunction: index % 2 ? easeIn_ : easeOut_, transform: `${( perspective_ as string - ).trim()} translate3d(calc(${directionX} * ${distanceFactor_} * ${ - translate / 2 - }${unit}), calc(${directionY} * ${distanceFactor_} * ${ + ).trim()} translate3d(calc(${directionX} * ${distanceUnit_} * ${ translate / 2 - }${unit}), calc(${directionZ} * ${distanceFactor_} * ${ + }), calc(${directionY} * ${distanceUnit_} * ${ translate / 2 - }${unit})) rotateZ(var(--motion-rotate, 0deg))`, + }), calc(${directionZ} * ${distanceUnit_} * ${translate / 2})) rotateZ(var(--motion-rotate, 0deg))`, })); return [ diff --git a/packages/motion-presets/src/library/entrance/CurveIn.ts b/packages/motion-presets/src/library/entrance/CurveIn.ts index c21d065a..35355fef 100644 --- a/packages/motion-presets/src/library/entrance/CurveIn.ts +++ b/packages/motion-presets/src/library/entrance/CurveIn.ts @@ -1,5 +1,5 @@ import type { CurveIn, TimeAnimationOptions, DomApi, EffectFourDirections } from '../../types'; -import { toKeyframeValue, parseDirection, parseLength, getEntranceFill } from '../../utils'; +import { toKeyframeValue, parseDirection, parseLengthOrCalc, getEntranceFill } from '../../utils'; import { FOUR_DIRECTIONS } from '../../consts'; export const schema = { @@ -24,25 +24,29 @@ export function web(options: TimeAnimationOptions, _dom?: DomApi) { } export function style(options: TimeAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as CurveIn; const direction = parseDirection( namedEffect?.from, FOUR_DIRECTIONS, schema.from.default, ); - const depth = parseLength(namedEffect.depth, schema.depth.default); + const depth = parseLengthOrCalc(namedEffect.depth, schema.depth.default); const { perspective = schema.perspective.default } = namedEffect; const [curveIn, fadeIn] = getNames(options); const { rotationX, rotationY } = PARAMS_MAP[direction]; - const depthValue = `${depth.value}${depth.unit === 'percentage' ? '%' : depth.unit}`; + const depthValue = + typeof depth === 'string' + ? depth + : `${depth.value}${depth.unit === 'percentage' ? '%' : depth.unit}`; const custom = { - '--motion-perspective': `${perspective}px`, - '--motion-rotate-x': `${rotationX}deg`, - '--motion-rotate-y': `${rotationY}deg`, - '--motion-depth-negative': `calc(-1 * ${depthValue})`, - '--motion-depth-positive': depthValue, + [`--motion-perspective${suffix}`]: `${perspective}px`, + [`--motion-rotate-x${suffix}`]: `${rotationX}deg`, + [`--motion-rotate-y${suffix}`]: `${rotationY}deg`, + [`--motion-depth-negative${suffix}`]: `calc(-1 * ${depthValue})`, + [`--motion-depth-positive${suffix}`]: depthValue, }; const easing = 'quadOut'; @@ -56,18 +60,18 @@ export function style(options: TimeAnimationOptions, asWeb = false) { custom, keyframes: [ { - transform: `perspective(${toKeyframeValue(custom, '--motion-perspective', asWeb)}) translateZ(${toKeyframeValue(custom, '--motion-depth-negative', asWeb)}) rotateX(${toKeyframeValue( + transform: `perspective(${toKeyframeValue(custom, `--motion-perspective${suffix}`, asWeb)}) translateZ(${toKeyframeValue(custom, `--motion-depth-negative${suffix}`, asWeb)}) rotateX(${toKeyframeValue( custom, - '--motion-rotate-x', + `--motion-rotate-x${suffix}`, asWeb, )}) rotateY(${toKeyframeValue( custom, - '--motion-rotate-y', + `--motion-rotate-y${suffix}`, asWeb, - )}) translateZ(${toKeyframeValue(custom, '--motion-depth-positive', asWeb)}) rotateZ(var(--motion-rotate, 0deg))`, + )}) translateZ(${toKeyframeValue(custom, `--motion-depth-positive${suffix}`, asWeb)}) rotateZ(var(--motion-rotate, 0deg))`, }, { - transform: `perspective(${toKeyframeValue(custom, '--motion-perspective', asWeb)}) translateZ(${toKeyframeValue(custom, '--motion-depth-negative', asWeb)}) rotateX(0deg) rotateY(0deg) translateZ(${toKeyframeValue(custom, '--motion-depth-positive', asWeb)}) rotateZ(var(--motion-rotate, 0deg))`, + transform: `perspective(${toKeyframeValue(custom, `--motion-perspective${suffix}`, asWeb)}) translateZ(${toKeyframeValue(custom, `--motion-depth-negative${suffix}`, asWeb)}) rotateX(0deg) rotateY(0deg) translateZ(${toKeyframeValue(custom, `--motion-depth-positive${suffix}`, asWeb)}) rotateZ(var(--motion-rotate, 0deg))`, }, ], }, diff --git a/packages/motion-presets/src/library/entrance/DropIn.ts b/packages/motion-presets/src/library/entrance/DropIn.ts index fb4e9ad2..c8a08e50 100644 --- a/packages/motion-presets/src/library/entrance/DropIn.ts +++ b/packages/motion-presets/src/library/entrance/DropIn.ts @@ -14,13 +14,14 @@ export function web(options: TimeAnimationOptions) { } export function style(options: TimeAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const { scale = schema.scale.default } = options.namedEffect as DropIn; const [fadeIn, dropIn] = getNames(options); const easing = options.easing || 'quintInOut'; const custom = { - '--motion-scale': `${scale}`, + [`--motion-scale${suffix}`]: `${scale}`, }; return [ @@ -41,7 +42,7 @@ export function style(options: TimeAnimationOptions, asWeb = false) { custom, keyframes: [ { - scale: toKeyframeValue(custom, '--motion-scale', asWeb), + scale: toKeyframeValue(custom, `--motion-scale${suffix}`, asWeb), }, { scale: '1', diff --git a/packages/motion-presets/src/library/entrance/ExpandIn.ts b/packages/motion-presets/src/library/entrance/ExpandIn.ts index a458268c..93f88a2b 100644 --- a/packages/motion-presets/src/library/entrance/ExpandIn.ts +++ b/packages/motion-presets/src/library/entrance/ExpandIn.ts @@ -2,7 +2,9 @@ import type { TimeAnimationOptions } from '../../types'; import { getCssUnits, toKeyframeValue, - parseLength, + parseLengthOrCalc, + parseCssAngle, + scaleLength, parseDirection, getEntranceFill, } from '../../utils'; @@ -31,6 +33,7 @@ export function web(options: TimeAnimationOptions) { } export function style(options: TimeAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as ExpandIn; const { scale = schema.scale.default } = namedEffect; @@ -45,21 +48,29 @@ export function style(options: TimeAnimationOptions, asWeb = false) { ? DIRECTION_KEYWORD_TO_ANGLE[parsedDirection] : 360 - parsedDirection; - const distance = parseLength(namedEffect.travel, schema.travel.default); + const distance = parseLengthOrCalc(namedEffect.travel, schema.travel.default); + const cssAngle = parseCssAngle(namedEffect?.from); const [fadeIn, expandIn] = getNames(options); const easing = options.easing || 'cubicInOut'; - const angleInRad = (direction * Math.PI) / 180; - const unit = getCssUnits(distance.unit); - const x = `${(Math.cos(angleInRad) * distance.value) | 0}${unit}`; - const y = `${(Math.sin(angleInRad) * distance.value * -1) | 0}${unit}`; + let x, y; + if (cssAngle || typeof distance === 'string') { + const angle = cssAngle || `${360 - direction}deg`; + x = `calc(cos(${angle}) * ${scaleLength(distance)})`; + y = `calc(sin(${angle}) * ${scaleLength(distance)})`; + } else { + const angleInRad = (direction * Math.PI) / 180; + const unit = getCssUnits(distance.unit); + x = `${(Math.cos(angleInRad) * distance.value) | 0}${unit}`; + y = `${(Math.sin(angleInRad) * distance.value * -1) | 0}${unit}`; + } const custom = { - '--motion-translate-x': `${x}`, - '--motion-translate-y': `${y}`, - '--motion-scale': `${scale}`, + [`--motion-translate-x${suffix}`]: `${x}`, + [`--motion-translate-y${suffix}`]: `${y}`, + [`--motion-scale${suffix}`]: `${scale}`, }; return [ @@ -82,15 +93,15 @@ export function style(options: TimeAnimationOptions, asWeb = false) { { transform: `translate(${toKeyframeValue( custom, - '--motion-translate-x', + `--motion-translate-x${suffix}`, asWeb, )}, ${toKeyframeValue( custom, - '--motion-translate-y', + `--motion-translate-y${suffix}`, asWeb, )}) rotate(var(--motion-rotate, 0deg)) scale(${toKeyframeValue( custom, - '--motion-scale', + `--motion-scale${suffix}`, asWeb, )})`, }, diff --git a/packages/motion-presets/src/library/entrance/FlipIn.ts b/packages/motion-presets/src/library/entrance/FlipIn.ts index 39a0b558..ff925ff8 100644 --- a/packages/motion-presets/src/library/entrance/FlipIn.ts +++ b/packages/motion-presets/src/library/entrance/FlipIn.ts @@ -41,6 +41,7 @@ export function web(options: TimeAnimationOptions) { } export function style(options: TimeAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as FlipIn; const axis = parseDirection( namedEffect?.direction, @@ -55,9 +56,9 @@ export function style(options: TimeAnimationOptions, asWeb = false) { const from = getRotateFrom(direction, angle); const custom = { - '--motion-perspective': `${perspective}px`, - '--motion-rotate-x': `${from.x}deg`, - '--motion-rotate-y': `${from.y}deg`, + [`--motion-perspective${suffix}`]: `${perspective}px`, + [`--motion-rotate-x${suffix}`]: `${from.x}deg`, + [`--motion-rotate-y${suffix}`]: `${from.y}deg`, }; return [ @@ -77,10 +78,10 @@ export function style(options: TimeAnimationOptions, asWeb = false) { custom, keyframes: [ { - transform: `perspective(${toKeyframeValue(custom, '--motion-perspective', asWeb)}) rotate(var(--motion-rotate, 0deg)) rotateX(var(--motion-rotate-x, ${custom['--motion-rotate-x']})) rotateY(var(--motion-rotate-y, ${custom['--motion-rotate-y']}))`, + transform: `perspective(${toKeyframeValue(custom, `--motion-perspective${suffix}`, asWeb)}) rotate(var(--motion-rotate, 0deg)) rotateX(var(--motion-rotate-x${suffix}, ${custom[`--motion-rotate-x${suffix}`]})) rotateY(var(--motion-rotate-y${suffix}, ${custom[`--motion-rotate-y${suffix}`]}))`, }, { - transform: `perspective(${toKeyframeValue(custom, '--motion-perspective', asWeb)}) rotate(var(--motion-rotate, 0deg)) rotateX(0deg) rotateY(0deg)`, + transform: `perspective(${toKeyframeValue(custom, `--motion-perspective${suffix}`, asWeb)}) rotate(var(--motion-rotate, 0deg)) rotateX(0deg) rotateY(0deg)`, }, ], }, diff --git a/packages/motion-presets/src/library/entrance/FloatIn.ts b/packages/motion-presets/src/library/entrance/FloatIn.ts index 02fd6cec..4932c13c 100644 --- a/packages/motion-presets/src/library/entrance/FloatIn.ts +++ b/packages/motion-presets/src/library/entrance/FloatIn.ts @@ -22,6 +22,7 @@ export function web(options: TimeAnimationOptions) { } export function style(options: TimeAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as FloatIn; const direction = parseDirection( namedEffect?.from, @@ -35,8 +36,8 @@ export function style(options: TimeAnimationOptions, asWeb = false) { const translateY = fromParams.dy * fromParams.distance; const custom = { - '--motion-translate-x': `${translateX}px`, - '--motion-translate-y': `${translateY}px`, + [`--motion-translate-x${suffix}`]: `${translateX}px`, + [`--motion-translate-y${suffix}`]: `${translateY}px`, }; const easing = 'sineInOut'; @@ -52,11 +53,11 @@ export function style(options: TimeAnimationOptions, asWeb = false) { { transform: `translate(${toKeyframeValue( custom, - '--motion-translate-x', + `--motion-translate-x${suffix}`, asWeb, )}, ${toKeyframeValue( custom, - '--motion-translate-y', + `--motion-translate-y${suffix}`, asWeb, )}) rotate(var(--motion-rotate, 0deg))`, }, diff --git a/packages/motion-presets/src/library/entrance/FoldIn.ts b/packages/motion-presets/src/library/entrance/FoldIn.ts index 92b09438..9d2e4dc0 100644 --- a/packages/motion-presets/src/library/entrance/FoldIn.ts +++ b/packages/motion-presets/src/library/entrance/FoldIn.ts @@ -34,6 +34,7 @@ export function web(options: TimeAnimationOptions) { } export function style(options: TimeAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as FoldIn; const direction = parseDirection( namedEffect?.pivot, @@ -48,11 +49,11 @@ export function style(options: TimeAnimationOptions, asWeb = false) { const from = getRotateFrom(direction, -angle); const custom = { - '--motion-perspective': `${perspective}px`, - '--motion-origin-x': `${x}%`, - '--motion-origin-y': `${y}%`, - '--motion-rotate-x': `${from.x}deg`, - '--motion-rotate-y': `${from.y}deg`, + [`--motion-perspective${suffix}`]: `${perspective}px`, + [`--motion-origin-x${suffix}`]: `${x}%`, + [`--motion-origin-y${suffix}`]: `${y}%`, + [`--motion-rotate-x${suffix}`]: `${from.x}deg`, + [`--motion-rotate-y${suffix}`]: `${from.y}deg`, }; return [ @@ -72,10 +73,10 @@ export function style(options: TimeAnimationOptions, asWeb = false) { custom, keyframes: [ { - transform: `rotate(var(--motion-rotate, 0deg)) translate(var(--motion-origin-x, ${custom['--motion-origin-x']}), var(--motion-origin-y, ${custom['--motion-origin-y']})) perspective(${toKeyframeValue(custom, '--motion-perspective', asWeb)}) rotateX(var(--motion-rotate-x, ${custom['--motion-rotate-x']})) rotateY(var(--motion-rotate-y, ${custom['--motion-rotate-y']})) translate(calc(-1 * var(--motion-origin-x, ${custom['--motion-origin-x']})), calc(-1 * var(--motion-origin-y, ${custom['--motion-origin-y']})))`, + transform: `rotate(var(--motion-rotate, 0deg)) translate(var(--motion-origin-x${suffix}, ${custom[`--motion-origin-x${suffix}`]}), var(--motion-origin-y${suffix}, ${custom[`--motion-origin-y${suffix}`]})) perspective(${toKeyframeValue(custom, `--motion-perspective${suffix}`, asWeb)}) rotateX(var(--motion-rotate-x${suffix}, ${custom[`--motion-rotate-x${suffix}`]})) rotateY(var(--motion-rotate-y${suffix}, ${custom[`--motion-rotate-y${suffix}`]})) translate(calc(-1 * var(--motion-origin-x${suffix}, ${custom[`--motion-origin-x${suffix}`]})), calc(-1 * var(--motion-origin-y${suffix}, ${custom[`--motion-origin-y${suffix}`]})))`, }, { - transform: `rotate(var(--motion-rotate, 0deg)) translate(var(--motion-origin-x, ${custom['--motion-origin-x']}), var(--motion-origin-y, ${custom['--motion-origin-y']})) perspective(${toKeyframeValue(custom, '--motion-perspective', asWeb)}) rotateX(0deg) rotateY(0deg) translate(calc(-1 * var(--motion-origin-x, ${custom['--motion-origin-x']})), calc(-1 * var(--motion-origin-y, ${custom['--motion-origin-y']})))`, + transform: `rotate(var(--motion-rotate, 0deg)) translate(var(--motion-origin-x${suffix}, ${custom[`--motion-origin-x${suffix}`]}), var(--motion-origin-y${suffix}, ${custom[`--motion-origin-y${suffix}`]})) perspective(${toKeyframeValue(custom, `--motion-perspective${suffix}`, asWeb)}) rotateX(0deg) rotateY(0deg) translate(calc(-1 * var(--motion-origin-x${suffix}, ${custom[`--motion-origin-x${suffix}`]})), calc(-1 * var(--motion-origin-y${suffix}, ${custom[`--motion-origin-y${suffix}`]})))`, }, ], }, diff --git a/packages/motion-presets/src/library/entrance/GlideIn.ts b/packages/motion-presets/src/library/entrance/GlideIn.ts index b9e8aece..1ac322f9 100644 --- a/packages/motion-presets/src/library/entrance/GlideIn.ts +++ b/packages/motion-presets/src/library/entrance/GlideIn.ts @@ -2,7 +2,9 @@ import type { TimeAnimationOptions, GlideIn } from '../../types'; import { getCssUnits, toKeyframeValue, - parseLength, + parseLengthOrCalc, + parseCssAngle, + scaleLength, parseDirection, getEntranceFill, } from '../../utils'; @@ -22,7 +24,7 @@ const DIRECTION_KEYWORD_TO_ANGLE: Record = { const ALLOW_ANGLES = true; export function getNames({ suffix = '' }: TimeAnimationOptions) { - return ['motion-glideIn', 'motion-fadeIn'].map((name) => name + suffix); + return ['motion-glideIn', 'motion-fadeInStep'].map((name) => name + suffix); } export function web(options: TimeAnimationOptions) { @@ -30,6 +32,7 @@ export function web(options: TimeAnimationOptions) { } export function style(options: TimeAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as GlideIn; const parsedDirection = parseDirection( @@ -43,19 +46,26 @@ export function style(options: TimeAnimationOptions, asWeb = false) { ? DIRECTION_KEYWORD_TO_ANGLE[parsedDirection] : parsedDirection; - const distance = parseLength(namedEffect.travel, schema.travel.default); - - const angleInRad = (direction * Math.PI) / 180; - const unit = getCssUnits(distance.unit); + const distance = parseLengthOrCalc(namedEffect.travel, schema.travel.default); + const cssAngle = parseCssAngle(namedEffect?.from); const easing = options.easing || 'quintInOut'; - const translateX = `${(Math.cos(angleInRad) * distance.value) | 0}${unit}`; - const translateY = `${(Math.sin(angleInRad) * distance.value) | 0}${unit}`; + let translateX, translateY; + if (cssAngle || typeof distance === 'string') { + const angle = cssAngle || `${direction}deg`; + translateX = `calc(cos(${angle}) * ${scaleLength(distance)})`; + translateY = `calc(sin(${angle}) * ${scaleLength(distance)})`; + } else { + const angleInRad = (direction * Math.PI) / 180; + const unit = getCssUnits(distance.unit); + translateX = `${(Math.cos(angleInRad) * distance.value) | 0}${unit}`; + translateY = `${(Math.sin(angleInRad) * distance.value) | 0}${unit}`; + } const custom = { - '--motion-translate-x': `${translateX}`, - '--motion-translate-y': `${translateY}`, + [`--motion-translate-x${suffix}`]: `${translateX}`, + [`--motion-translate-y${suffix}`]: `${translateY}`, }; const [glideIn, fadeIn] = getNames(options); @@ -71,11 +81,11 @@ export function style(options: TimeAnimationOptions, asWeb = false) { { transform: `translate(${toKeyframeValue( custom, - '--motion-translate-x', + `--motion-translate-x${suffix}`, asWeb, )}, ${toKeyframeValue( custom, - '--motion-translate-y', + `--motion-translate-y${suffix}`, asWeb, )}) rotate(var(--motion-rotate, 0deg))`, }, diff --git a/packages/motion-presets/src/library/entrance/RevealIn.ts b/packages/motion-presets/src/library/entrance/RevealIn.ts index cfd214b8..0212c83f 100644 --- a/packages/motion-presets/src/library/entrance/RevealIn.ts +++ b/packages/motion-presets/src/library/entrance/RevealIn.ts @@ -15,6 +15,7 @@ export function web(options: TimeAnimationOptions) { } export function style(options: TimeAnimationOptions) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as RevealIn; const direction = parseDirection( namedEffect?.from, @@ -28,7 +29,7 @@ export function style(options: TimeAnimationOptions) { const end = getClipPolygonParams({ direction: 'initial' }); const custom = { - '--motion-clip-start': start, + [`--motion-clip-start${suffix}`]: start, }; return [ @@ -40,7 +41,7 @@ export function style(options: TimeAnimationOptions) { custom, keyframes: [ { - clipPath: `var(--motion-clip-start, ${start})`, + clipPath: `var(--motion-clip-start${suffix}, ${start})`, }, { clipPath: end, diff --git a/packages/motion-presets/src/library/entrance/ShapeIn.ts b/packages/motion-presets/src/library/entrance/ShapeIn.ts index ff93215a..4b377458 100644 --- a/packages/motion-presets/src/library/entrance/ShapeIn.ts +++ b/packages/motion-presets/src/library/entrance/ShapeIn.ts @@ -29,6 +29,7 @@ export function web(options: TimeAnimationOptions) { } export function style(options: TimeAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const { shape = schema.shape.default } = options.namedEffect as ShapeIn; const [fadeIn, shapeIn] = getNames(options); const easing = options.easing || 'cubicInOut'; @@ -36,8 +37,8 @@ export function style(options: TimeAnimationOptions, asWeb = false) { const { start, end } = shapes[shape]; const custom = { - '--motion-shape-start': start, - '--motion-shape-end': end, + [`--motion-shape-start${suffix}`]: start, + [`--motion-shape-end${suffix}`]: end, }; return [ @@ -58,10 +59,10 @@ export function style(options: TimeAnimationOptions, asWeb = false) { custom, keyframes: [ { - clipPath: toKeyframeValue(custom, '--motion-shape-start', asWeb), + clipPath: toKeyframeValue(custom, `--motion-shape-start${suffix}`, asWeb), }, { - clipPath: toKeyframeValue(custom, '--motion-shape-end', asWeb), + clipPath: toKeyframeValue(custom, `--motion-shape-end${suffix}`, asWeb), }, ], }, diff --git a/packages/motion-presets/src/library/entrance/ShuttersIn.ts b/packages/motion-presets/src/library/entrance/ShuttersIn.ts index 7d7ee82c..0634f56e 100644 --- a/packages/motion-presets/src/library/entrance/ShuttersIn.ts +++ b/packages/motion-presets/src/library/entrance/ShuttersIn.ts @@ -16,7 +16,7 @@ export const schema = { } as const; export function getNames({ suffix = '' }: TimeAnimationOptions) { - return ['motion-shuttersIn', 'motion-fadeIn'].map((name) => name + suffix); + return ['motion-shuttersIn', 'motion-fadeInStep'].map((name) => name + suffix); } export function web(options: TimeAnimationOptions) { @@ -24,6 +24,7 @@ export function web(options: TimeAnimationOptions) { } export function style(options: TimeAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as ShuttersIn; const from = parseDirection( namedEffect?.from, @@ -37,8 +38,8 @@ export function style(options: TimeAnimationOptions, asWeb = false) { const { clipStart, clipEnd } = getShuttersClipPaths(direction, shutters, staggered); const custom = { - '--motion-shutters-start': clipStart, - '--motion-shutters-end': clipEnd, + [`--motion-shutters-start${suffix}`]: clipStart, + [`--motion-shutters-end${suffix}`]: clipEnd, }; const easing = getEasing(options.easing || 'sineIn'); @@ -52,10 +53,10 @@ export function style(options: TimeAnimationOptions, asWeb = false) { custom, keyframes: [ { - clipPath: toKeyframeValue(custom, '--motion-shutters-start', asWeb), + clipPath: toKeyframeValue(custom, `--motion-shutters-start${suffix}`, asWeb), }, { - clipPath: toKeyframeValue(custom, '--motion-shutters-end', asWeb), + clipPath: toKeyframeValue(custom, `--motion-shutters-end${suffix}`, asWeb), }, ], }, diff --git a/packages/motion-presets/src/library/entrance/SlideIn.ts b/packages/motion-presets/src/library/entrance/SlideIn.ts index e01f8953..f3afb2a5 100644 --- a/packages/motion-presets/src/library/entrance/SlideIn.ts +++ b/packages/motion-presets/src/library/entrance/SlideIn.ts @@ -26,6 +26,7 @@ export function web(options: TimeAnimationOptions) { } export function style(options: TimeAnimationOptions) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as SlideIn; const direction = parseDirection( namedEffect?.from, @@ -45,9 +46,9 @@ export function style(options: TimeAnimationOptions) { const clipEnd = getClipPolygonParams({ direction: 'initial' }); const custom = { - '--motion-clip-start': start, - '--motion-translate-x': `${PARAM_MAP[direction].dx * 100}%`, - '--motion-translate-y': `${PARAM_MAP[direction].dy * 100}%`, + [`--motion-clip-start${suffix}`]: start, + [`--motion-translate-x${suffix}`]: `${PARAM_MAP[direction].dx * 100}%`, + [`--motion-translate-y${suffix}`]: `${PARAM_MAP[direction].dy * 100}%`, }; return [ @@ -59,8 +60,8 @@ export function style(options: TimeAnimationOptions) { custom, keyframes: [ { - transform: `rotate(var(--motion-rotate, 0deg)) translate(var(--motion-translate-x, ${custom['--motion-translate-x']}), var(--motion-translate-y, ${custom['--motion-translate-y']}))`, - clipPath: `var(--motion-clip-start, ${custom['--motion-clip-start']})`, + transform: `rotate(var(--motion-rotate, 0deg)) translate(var(--motion-translate-x${suffix}, ${custom[`--motion-translate-x${suffix}`]}), var(--motion-translate-y${suffix}, ${custom[`--motion-translate-y${suffix}`]}))`, + clipPath: `var(--motion-clip-start${suffix}, ${custom[`--motion-clip-start${suffix}`]})`, }, { transform: 'rotate(var(--motion-rotate, 0deg)) translate(0px, 0px)', diff --git a/packages/motion-presets/src/library/entrance/SpinIn.ts b/packages/motion-presets/src/library/entrance/SpinIn.ts index f30bfc0d..830d0d73 100644 --- a/packages/motion-presets/src/library/entrance/SpinIn.ts +++ b/packages/motion-presets/src/library/entrance/SpinIn.ts @@ -22,6 +22,7 @@ export function web(options: TimeAnimationOptions) { } export function style(options: TimeAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as SpinIn; const direction = parseDirection<(typeof SPIN_DIRECTIONS)[number]>( namedEffect?.direction, @@ -35,8 +36,8 @@ export function style(options: TimeAnimationOptions, asWeb = false) { const transformRotate = (DIRECTION_MAP[direction] > 0 ? 1 : -1) * 360 * spins; const custom = { - '--motion-scale': `${initialScale}`, - '--motion-rotate': `${transformRotate}deg`, + [`--motion-scale${suffix}`]: `${initialScale}`, + [`--motion-rotate${suffix}`]: `${transformRotate}deg`, }; return [ @@ -45,7 +46,7 @@ export function style(options: TimeAnimationOptions, asWeb = false) { name: fadeIn, fill: getEntranceFill(options), easing: 'cubicIn', - duration: options.duration! * initialScale, + duration: options.duration! * Math.min(initialScale, 1), custom: {}, keyframes: [{ offset: 0, opacity: 0 }], }, @@ -57,8 +58,8 @@ export function style(options: TimeAnimationOptions, asWeb = false) { custom, keyframes: [ { - scale: toKeyframeValue(custom, '--motion-scale', asWeb), - rotate: toKeyframeValue(custom, '--motion-rotate', asWeb), + scale: toKeyframeValue(custom, `--motion-scale${suffix}`, asWeb), + rotate: toKeyframeValue(custom, `--motion-rotate${suffix}`, asWeb), }, { scale: '1', diff --git a/packages/motion-presets/src/library/entrance/TiltIn.ts b/packages/motion-presets/src/library/entrance/TiltIn.ts index 7dfcd2d7..1b71907d 100644 --- a/packages/motion-presets/src/library/entrance/TiltIn.ts +++ b/packages/motion-presets/src/library/entrance/TiltIn.ts @@ -2,7 +2,7 @@ import { getClipPolygonParams, toKeyframeValue, parseDirection, - parseLength, + parseLengthOrCalc, getEntranceFill, } from '../../utils'; import type { TiltIn, TimeAnimationOptions, EffectTwoSides } from '../../types'; @@ -28,13 +28,14 @@ export function web(options: TimeAnimationOptions) { } export function style(options: TimeAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as TiltIn; const direction = parseDirection( namedEffect?.from, TWO_SIDES_DIRECTIONS, schema.from.default, ); - const depth = parseLength(namedEffect.depth, schema.depth.default); + const depth = parseLengthOrCalc(namedEffect.depth, schema.depth.default); const { perspective = schema.perspective.default } = namedEffect; const [fadeIn, tiltInRotate, tiltInClip] = getNames(options); @@ -42,17 +43,20 @@ export function style(options: TimeAnimationOptions, asWeb = false) { const clipStart = getClipPolygonParams({ direction: 'top', minimum: 0 }); const rotationZ = ROTATION_MAP[direction]; const clipEnd = getClipPolygonParams({ direction: 'initial' }); - const depthValue = `${depth.value}${depth.unit === 'percentage' ? '%' : depth.unit}`; + const depthValue = + typeof depth === 'string' + ? depth + : `${depth.value}${depth.unit === 'percentage' ? '%' : depth.unit}`; const rotateCustom = { - '--motion-perspective': `${perspective}px`, - '--motion-depth-negative': `calc(${depthValue} * -1)`, - '--motion-depth-positive': depthValue, + [`--motion-perspective${suffix}`]: `${perspective}px`, + [`--motion-depth-negative${suffix}`]: `calc(${depthValue} * -1)`, + [`--motion-depth-positive${suffix}`]: depthValue, }; const clipCustom = { - '--motion-rotate-z': `${rotationZ}deg`, - '--motion-clip-start': clipStart, + [`--motion-rotate-z${suffix}`]: `${rotationZ}deg`, + [`--motion-clip-start${suffix}`]: clipStart, }; return [ @@ -73,10 +77,10 @@ export function style(options: TimeAnimationOptions, asWeb = false) { custom: rotateCustom, keyframes: [ { - transform: `perspective(${toKeyframeValue(rotateCustom, '--motion-perspective', asWeb)}) translateZ(${toKeyframeValue(rotateCustom, '--motion-depth-negative', asWeb)}) rotateX(-90deg) translateZ(${toKeyframeValue(rotateCustom, '--motion-depth-positive', asWeb)}) rotate(var(--motion-rotate, 0deg))`, + transform: `perspective(${toKeyframeValue(rotateCustom, `--motion-perspective${suffix}`, asWeb)}) translateZ(${toKeyframeValue(rotateCustom, `--motion-depth-negative${suffix}`, asWeb)}) rotateX(-90deg) translateZ(${toKeyframeValue(rotateCustom, `--motion-depth-positive${suffix}`, asWeb)}) rotate(var(--motion-rotate, 0deg))`, }, { - transform: `perspective(${toKeyframeValue(rotateCustom, '--motion-perspective', asWeb)}) translateZ(${toKeyframeValue(rotateCustom, '--motion-depth-negative', asWeb)}) rotateX(0deg) translateZ(${toKeyframeValue(rotateCustom, '--motion-depth-positive', asWeb)}) rotate(var(--motion-rotate, 0deg))`, + transform: `perspective(${toKeyframeValue(rotateCustom, `--motion-perspective${suffix}`, asWeb)}) translateZ(${toKeyframeValue(rotateCustom, `--motion-depth-negative${suffix}`, asWeb)}) rotateX(0deg) translateZ(${toKeyframeValue(rotateCustom, `--motion-depth-positive${suffix}`, asWeb)}) rotate(var(--motion-rotate, 0deg))`, }, ], }, @@ -90,8 +94,8 @@ export function style(options: TimeAnimationOptions, asWeb = false) { custom: clipCustom, keyframes: [ { - clipPath: `var(--motion-clip-start, ${clipCustom['--motion-clip-start']})`, - transform: `rotateZ(${toKeyframeValue(clipCustom, '--motion-rotate-z', asWeb)})`, + clipPath: `var(--motion-clip-start${suffix}, ${clipCustom[`--motion-clip-start${suffix}`]})`, + transform: `rotateZ(${toKeyframeValue(clipCustom, `--motion-rotate-z${suffix}`, asWeb)})`, }, { clipPath: clipEnd, diff --git a/packages/motion-presets/src/library/entrance/TurnIn.ts b/packages/motion-presets/src/library/entrance/TurnIn.ts index b35f3cdd..cb7dd298 100644 --- a/packages/motion-presets/src/library/entrance/TurnIn.ts +++ b/packages/motion-presets/src/library/entrance/TurnIn.ts @@ -25,6 +25,7 @@ export function web(options: TimeAnimationOptions) { } export function style(options: TimeAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as TurnIn; const direction = parseDirection( namedEffect?.pivot, @@ -37,13 +38,13 @@ export function style(options: TimeAnimationOptions, asWeb = false) { const { x, y, angle } = DIRECTION_TO_TRANSFORM_MAP[direction]; const custom = { - '--motion-origin': `${x}%, ${y}%`, - '--motion-origin-invert': `${-x}%, ${-y}%`, - '--motion-rotate-z': `${angle}deg`, + [`--motion-origin${suffix}`]: `${x}%, ${y}%`, + [`--motion-origin-invert${suffix}`]: `${-x}%, ${-y}%`, + [`--motion-rotate-z${suffix}`]: `${angle}deg`, }; - const origin = toKeyframeValue(custom, '--motion-origin', asWeb); - const invertedOrigin = toKeyframeValue(custom, '--motion-origin-invert', asWeb); + const origin = toKeyframeValue(custom, `--motion-origin${suffix}`, asWeb); + const invertedOrigin = toKeyframeValue(custom, `--motion-origin-invert${suffix}`, asWeb); return [ { @@ -65,7 +66,7 @@ export function style(options: TimeAnimationOptions, asWeb = false) { { transform: `translate(${origin}) rotate(${toKeyframeValue( custom, - '--motion-rotate-z', + `--motion-rotate-z${suffix}`, asWeb, )}) translate(${invertedOrigin}) rotate(var(--motion-rotate, 0deg))`, }, diff --git a/packages/motion-presets/src/library/entrance/WinkIn.ts b/packages/motion-presets/src/library/entrance/WinkIn.ts index 65511a93..37dd6818 100644 --- a/packages/motion-presets/src/library/entrance/WinkIn.ts +++ b/packages/motion-presets/src/library/entrance/WinkIn.ts @@ -22,6 +22,7 @@ export function web(options: TimeAnimationOptions) { } export function style(options: TimeAnimationOptions) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as WinkIn; const direction = parseDirection<(typeof DIRECTIONS)[number]>( namedEffect?.direction, @@ -37,9 +38,9 @@ export function style(options: TimeAnimationOptions) { const end = getClipPolygonParams({ direction: 'initial' }); const custom = { - '--motion-scale-x': scaleX, - '--motion-scale-y': scaleY, - '--motion-clip-start': start, + [`--motion-scale-x${suffix}`]: scaleX, + [`--motion-scale-y${suffix}`]: scaleY, + [`--motion-clip-start${suffix}`]: start, }; return [ @@ -59,7 +60,7 @@ export function style(options: TimeAnimationOptions) { custom, keyframes: [ { - clipPath: `var(--motion-clip-start, ${custom['--motion-clip-start']})`, + clipPath: `var(--motion-clip-start${suffix}, ${custom[`--motion-clip-start${suffix}`]})`, }, { clipPath: end, @@ -75,7 +76,7 @@ export function style(options: TimeAnimationOptions) { custom, keyframes: [ { - transform: `rotate(var(--motion-rotate, 0deg)) scale(var(--motion-scale-x, ${custom['--motion-scale-x']}), var(--motion-scale-y, ${custom['--motion-scale-y']}))`, + transform: `rotate(var(--motion-rotate, 0deg)) scale(var(--motion-scale-x${suffix}, ${custom[`--motion-scale-x${suffix}`]}), var(--motion-scale-y${suffix}, ${custom[`--motion-scale-y${suffix}`]}))`, }, { transform: 'rotate(var(--motion-rotate, 0deg)) scale(1, 1)', diff --git a/packages/motion-presets/src/library/entrance/test/ArcIn.spec.ts b/packages/motion-presets/src/library/entrance/test/ArcIn.spec.ts index d1cf5ed8..34ec14f6 100644 --- a/packages/motion-presets/src/library/entrance/test/ArcIn.spec.ts +++ b/packages/motion-presets/src/library/entrance/test/ArcIn.spec.ts @@ -306,4 +306,19 @@ describe('ArcIn', () => { expect(result).toMatchObject(expectedResult); }); + + test('ArcIn animation with calc() depth', () => { + const mockOptions = { + ...baseMockOptions, + duration: 1000, + namedEffect: { depth: 'calc(10vh + 20px)' } as ArcInType, + }; + + const result = ArcIn.style?.(mockOptions); + + expect(result?.[1].custom).toMatchObject({ + '--motion-depth-negative': 'calc(-1 * calc(10vh + 20px))', + '--motion-depth-positive': 'calc(10vh + 20px)', + }); + }); }); diff --git a/packages/motion-presets/src/library/entrance/test/BounceIn.spec.ts b/packages/motion-presets/src/library/entrance/test/BounceIn.spec.ts index a42b2736..487ff7ee 100644 --- a/packages/motion-presets/src/library/entrance/test/BounceIn.spec.ts +++ b/packages/motion-presets/src/library/entrance/test/BounceIn.spec.ts @@ -23,61 +23,61 @@ describe('BounceIn', () => { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0, transform: - ' translate3d(calc(0 * 1 * 50px), calc(1 * 1 * 50px), calc(0 * 1 * 50px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 1px * 50), calc(1 * 1px * 50), calc(0 * 1px * 50)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.3, transform: - ' translate3d(calc(0 * 1 * 0px), calc(1 * 1 * 0px), calc(0 * 1 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 1px * 0), calc(1 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.42, transform: - ' translate3d(calc(0 * 1 * 17.5px), calc(1 * 1 * 17.5px), calc(0 * 1 * 17.5px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 1px * 17.5), calc(1 * 1px * 17.5), calc(0 * 1px * 17.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.54, transform: - ' translate3d(calc(0 * 1 * 0px), calc(1 * 1 * 0px), calc(0 * 1 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 1px * 0), calc(1 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.62, transform: - ' translate3d(calc(0 * 1 * 10.5px), calc(1 * 1 * 10.5px), calc(0 * 1 * 10.5px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 1px * 10.5), calc(1 * 1px * 10.5), calc(0 * 1px * 10.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.74, transform: - ' translate3d(calc(0 * 1 * 0px), calc(1 * 1 * 0px), calc(0 * 1 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 1px * 0), calc(1 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.82, transform: - ' translate3d(calc(0 * 1 * 4.5px), calc(1 * 1 * 4.5px), calc(0 * 1 * 4.5px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 1px * 4.5), calc(1 * 1px * 4.5), calc(0 * 1px * 4.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.9, transform: - ' translate3d(calc(0 * 1 * 0px), calc(1 * 1 * 0px), calc(0 * 1 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 1px * 0), calc(1 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.95, transform: - ' translate3d(calc(0 * 1 * 1px), calc(1 * 1 * 1px), calc(0 * 1 * 1px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 1px * 1), calc(1 * 1px * 1), calc(0 * 1px * 1)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 1, transform: - ' translate3d(calc(0 * 1 * 0px), calc(1 * 1 * 0px), calc(0 * 1 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 1px * 0), calc(1 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, ], }, @@ -104,61 +104,61 @@ describe('BounceIn', () => { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0, transform: - ' translate3d(calc(0 * 2 * 50px), calc(1 * 2 * 50px), calc(0 * 2 * 50px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 2px * 50), calc(1 * 2px * 50), calc(0 * 2px * 50)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.3, transform: - ' translate3d(calc(0 * 2 * 0px), calc(1 * 2 * 0px), calc(0 * 2 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 2px * 0), calc(1 * 2px * 0), calc(0 * 2px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.42, transform: - ' translate3d(calc(0 * 2 * 17.5px), calc(1 * 2 * 17.5px), calc(0 * 2 * 17.5px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 2px * 17.5), calc(1 * 2px * 17.5), calc(0 * 2px * 17.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.54, transform: - ' translate3d(calc(0 * 2 * 0px), calc(1 * 2 * 0px), calc(0 * 2 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 2px * 0), calc(1 * 2px * 0), calc(0 * 2px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.62, transform: - ' translate3d(calc(0 * 2 * 10.5px), calc(1 * 2 * 10.5px), calc(0 * 2 * 10.5px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 2px * 10.5), calc(1 * 2px * 10.5), calc(0 * 2px * 10.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.74, transform: - ' translate3d(calc(0 * 2 * 0px), calc(1 * 2 * 0px), calc(0 * 2 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 2px * 0), calc(1 * 2px * 0), calc(0 * 2px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.82, transform: - ' translate3d(calc(0 * 2 * 4.5px), calc(1 * 2 * 4.5px), calc(0 * 2 * 4.5px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 2px * 4.5), calc(1 * 2px * 4.5), calc(0 * 2px * 4.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.9, transform: - ' translate3d(calc(0 * 2 * 0px), calc(1 * 2 * 0px), calc(0 * 2 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 2px * 0), calc(1 * 2px * 0), calc(0 * 2px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.95, transform: - ' translate3d(calc(0 * 2 * 1px), calc(1 * 2 * 1px), calc(0 * 2 * 1px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 2px * 1), calc(1 * 2px * 1), calc(0 * 2px * 1)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 1, transform: - ' translate3d(calc(0 * 2 * 0px), calc(1 * 2 * 0px), calc(0 * 2 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 2px * 0), calc(1 * 2px * 0), calc(0 * 2px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, ], }, @@ -185,61 +185,61 @@ describe('BounceIn', () => { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0, transform: - ' translate3d(calc(0 * 1 * 50px), calc(-1 * 1 * 50px), calc(0 * 1 * 50px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 1px * 50), calc(-1 * 1px * 50), calc(0 * 1px * 50)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.3, transform: - ' translate3d(calc(0 * 1 * 0px), calc(-1 * 1 * 0px), calc(0 * 1 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 1px * 0), calc(-1 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.42, transform: - ' translate3d(calc(0 * 1 * 17.5px), calc(-1 * 1 * 17.5px), calc(0 * 1 * 17.5px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 1px * 17.5), calc(-1 * 1px * 17.5), calc(0 * 1px * 17.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.54, transform: - ' translate3d(calc(0 * 1 * 0px), calc(-1 * 1 * 0px), calc(0 * 1 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 1px * 0), calc(-1 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.62, transform: - ' translate3d(calc(0 * 1 * 10.5px), calc(-1 * 1 * 10.5px), calc(0 * 1 * 10.5px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 1px * 10.5), calc(-1 * 1px * 10.5), calc(0 * 1px * 10.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.74, transform: - ' translate3d(calc(0 * 1 * 0px), calc(-1 * 1 * 0px), calc(0 * 1 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 1px * 0), calc(-1 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.82, transform: - ' translate3d(calc(0 * 1 * 4.5px), calc(-1 * 1 * 4.5px), calc(0 * 1 * 4.5px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 1px * 4.5), calc(-1 * 1px * 4.5), calc(0 * 1px * 4.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.9, transform: - ' translate3d(calc(0 * 1 * 0px), calc(-1 * 1 * 0px), calc(0 * 1 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 1px * 0), calc(-1 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.95, transform: - ' translate3d(calc(0 * 1 * 1px), calc(-1 * 1 * 1px), calc(0 * 1 * 1px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 1px * 1), calc(-1 * 1px * 1), calc(0 * 1px * 1)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 1, transform: - ' translate3d(calc(0 * 1 * 0px), calc(-1 * 1 * 0px), calc(0 * 1 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 1px * 0), calc(-1 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, ], }, @@ -266,61 +266,61 @@ describe('BounceIn', () => { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0, transform: - ' translate3d(calc(1 * 1 * 50px), calc(0 * 1 * 50px), calc(0 * 1 * 50px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(1 * 1px * 50), calc(0 * 1px * 50), calc(0 * 1px * 50)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.3, transform: - ' translate3d(calc(1 * 1 * 0px), calc(0 * 1 * 0px), calc(0 * 1 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(1 * 1px * 0), calc(0 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.42, transform: - ' translate3d(calc(1 * 1 * 17.5px), calc(0 * 1 * 17.5px), calc(0 * 1 * 17.5px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(1 * 1px * 17.5), calc(0 * 1px * 17.5), calc(0 * 1px * 17.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.54, transform: - ' translate3d(calc(1 * 1 * 0px), calc(0 * 1 * 0px), calc(0 * 1 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(1 * 1px * 0), calc(0 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.62, transform: - ' translate3d(calc(1 * 1 * 10.5px), calc(0 * 1 * 10.5px), calc(0 * 1 * 10.5px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(1 * 1px * 10.5), calc(0 * 1px * 10.5), calc(0 * 1px * 10.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.74, transform: - ' translate3d(calc(1 * 1 * 0px), calc(0 * 1 * 0px), calc(0 * 1 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(1 * 1px * 0), calc(0 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.82, transform: - ' translate3d(calc(1 * 1 * 4.5px), calc(0 * 1 * 4.5px), calc(0 * 1 * 4.5px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(1 * 1px * 4.5), calc(0 * 1px * 4.5), calc(0 * 1px * 4.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.9, transform: - ' translate3d(calc(1 * 1 * 0px), calc(0 * 1 * 0px), calc(0 * 1 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(1 * 1px * 0), calc(0 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.95, transform: - ' translate3d(calc(1 * 1 * 1px), calc(0 * 1 * 1px), calc(0 * 1 * 1px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(1 * 1px * 1), calc(0 * 1px * 1), calc(0 * 1px * 1)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 1, transform: - ' translate3d(calc(1 * 1 * 0px), calc(0 * 1 * 0px), calc(0 * 1 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(1 * 1px * 0), calc(0 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, ], }, @@ -347,61 +347,61 @@ describe('BounceIn', () => { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0, transform: - ' translate3d(calc(-1 * 1 * 50px), calc(0 * 1 * 50px), calc(0 * 1 * 50px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(-1 * 1px * 50), calc(0 * 1px * 50), calc(0 * 1px * 50)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.3, transform: - ' translate3d(calc(-1 * 1 * 0px), calc(0 * 1 * 0px), calc(0 * 1 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(-1 * 1px * 0), calc(0 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.42, transform: - ' translate3d(calc(-1 * 1 * 17.5px), calc(0 * 1 * 17.5px), calc(0 * 1 * 17.5px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(-1 * 1px * 17.5), calc(0 * 1px * 17.5), calc(0 * 1px * 17.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.54, transform: - ' translate3d(calc(-1 * 1 * 0px), calc(0 * 1 * 0px), calc(0 * 1 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(-1 * 1px * 0), calc(0 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.62, transform: - ' translate3d(calc(-1 * 1 * 10.5px), calc(0 * 1 * 10.5px), calc(0 * 1 * 10.5px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(-1 * 1px * 10.5), calc(0 * 1px * 10.5), calc(0 * 1px * 10.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.74, transform: - ' translate3d(calc(-1 * 1 * 0px), calc(0 * 1 * 0px), calc(0 * 1 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(-1 * 1px * 0), calc(0 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.82, transform: - ' translate3d(calc(-1 * 1 * 4.5px), calc(0 * 1 * 4.5px), calc(0 * 1 * 4.5px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(-1 * 1px * 4.5), calc(0 * 1px * 4.5), calc(0 * 1px * 4.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.9, transform: - ' translate3d(calc(-1 * 1 * 0px), calc(0 * 1 * 0px), calc(0 * 1 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(-1 * 1px * 0), calc(0 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.95, transform: - ' translate3d(calc(-1 * 1 * 1px), calc(0 * 1 * 1px), calc(0 * 1 * 1px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(-1 * 1px * 1), calc(0 * 1px * 1), calc(0 * 1px * 1)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 1, transform: - ' translate3d(calc(-1 * 1 * 0px), calc(0 * 1 * 0px), calc(0 * 1 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(-1 * 1px * 0), calc(0 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, ], }, @@ -428,61 +428,61 @@ describe('BounceIn', () => { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0, transform: - 'perspective(800px) translate3d(calc(0 * 1 * 50px), calc(0 * 1 * 50px), calc(-1 * 1 * 50px)) rotateZ(var(--motion-rotate, 0deg))', + 'perspective(800px) translate3d(calc(0 * 1px * 50), calc(0 * 1px * 50), calc(-1 * 1px * 50)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.3, transform: - 'perspective(800px) translate3d(calc(0 * 1 * 0px), calc(0 * 1 * 0px), calc(-1 * 1 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'perspective(800px) translate3d(calc(0 * 1px * 0), calc(0 * 1px * 0), calc(-1 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.42, transform: - 'perspective(800px) translate3d(calc(0 * 1 * 17.5px), calc(0 * 1 * 17.5px), calc(-1 * 1 * 17.5px)) rotateZ(var(--motion-rotate, 0deg))', + 'perspective(800px) translate3d(calc(0 * 1px * 17.5), calc(0 * 1px * 17.5), calc(-1 * 1px * 17.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.54, transform: - 'perspective(800px) translate3d(calc(0 * 1 * 0px), calc(0 * 1 * 0px), calc(-1 * 1 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'perspective(800px) translate3d(calc(0 * 1px * 0), calc(0 * 1px * 0), calc(-1 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.62, transform: - 'perspective(800px) translate3d(calc(0 * 1 * 10.5px), calc(0 * 1 * 10.5px), calc(-1 * 1 * 10.5px)) rotateZ(var(--motion-rotate, 0deg))', + 'perspective(800px) translate3d(calc(0 * 1px * 10.5), calc(0 * 1px * 10.5), calc(-1 * 1px * 10.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.74, transform: - 'perspective(800px) translate3d(calc(0 * 1 * 0px), calc(0 * 1 * 0px), calc(-1 * 1 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'perspective(800px) translate3d(calc(0 * 1px * 0), calc(0 * 1px * 0), calc(-1 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.82, transform: - 'perspective(800px) translate3d(calc(0 * 1 * 4.5px), calc(0 * 1 * 4.5px), calc(-1 * 1 * 4.5px)) rotateZ(var(--motion-rotate, 0deg))', + 'perspective(800px) translate3d(calc(0 * 1px * 4.5), calc(0 * 1px * 4.5), calc(-1 * 1px * 4.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.9, transform: - 'perspective(800px) translate3d(calc(0 * 1 * 0px), calc(0 * 1 * 0px), calc(-1 * 1 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'perspective(800px) translate3d(calc(0 * 1px * 0), calc(0 * 1px * 0), calc(-1 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.95, transform: - 'perspective(800px) translate3d(calc(0 * 1 * 1px), calc(0 * 1 * 1px), calc(-1 * 1 * 1px)) rotateZ(var(--motion-rotate, 0deg))', + 'perspective(800px) translate3d(calc(0 * 1px * 1), calc(0 * 1px * 1), calc(-1 * 1px * 1)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 1, transform: - 'perspective(800px) translate3d(calc(0 * 1 * 0px), calc(0 * 1 * 0px), calc(-1 * 1 * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'perspective(800px) translate3d(calc(0 * 1px * 0), calc(0 * 1px * 0), calc(-1 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, ], }, @@ -518,7 +518,7 @@ describe('BounceIn style method', () => { '--motion-direction-x': 0, '--motion-direction-y': 1, '--motion-direction-z': 0, - '--motion-distance-factor': 1, + '--motion-distance-unit': '1px', '--motion-perspective': ' ', '--motion-ease-in': 'cubic-bezier(0.39, 0.575, 0.565, 1)', '--motion-ease-out': 'cubic-bezier(0.47, 0, 0.745, 0.715)', @@ -528,61 +528,61 @@ describe('BounceIn style method', () => { animationTimingFunction: 'var(--motion-ease-out)', offset: 0, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 50px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 50px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 50px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 50), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 50), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 50)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.3, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.42, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 17.5px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 17.5px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 17.5px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 17.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 17.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 17.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.54, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.62, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 10.5px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 10.5px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 10.5px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 10.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 10.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 10.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.74, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.82, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 4.5px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 4.5px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 4.5px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 4.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 4.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 4.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.9, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.95, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 1px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 1px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 1px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 1), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 1), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 1)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 1, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, ], }, @@ -612,7 +612,7 @@ describe('BounceIn style method', () => { '--motion-direction-x': 0, '--motion-direction-y': 1, '--motion-direction-z': 0, - '--motion-distance-factor': 2, + '--motion-distance-unit': '2px', '--motion-perspective': ' ', '--motion-ease-in': 'cubic-bezier(0.39, 0.575, 0.565, 1)', '--motion-ease-out': 'cubic-bezier(0.47, 0, 0.745, 0.715)', @@ -622,61 +622,61 @@ describe('BounceIn style method', () => { animationTimingFunction: 'var(--motion-ease-out)', offset: 0, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 50px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 50px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 50px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 50), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 50), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 50)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.3, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.42, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 17.5px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 17.5px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 17.5px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 17.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 17.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 17.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.54, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.62, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 10.5px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 10.5px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 10.5px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 10.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 10.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 10.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.74, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.82, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 4.5px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 4.5px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 4.5px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 4.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 4.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 4.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.9, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.95, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 1px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 1px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 1px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 1), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 1), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 1)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 1, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, ], }, @@ -706,7 +706,7 @@ describe('BounceIn style method', () => { '--motion-direction-x': 0, '--motion-direction-y': -1, '--motion-direction-z': 0, - '--motion-distance-factor': 1, + '--motion-distance-unit': '1px', '--motion-perspective': ' ', '--motion-ease-in': 'cubic-bezier(0.39, 0.575, 0.565, 1)', '--motion-ease-out': 'cubic-bezier(0.47, 0, 0.745, 0.715)', @@ -716,61 +716,61 @@ describe('BounceIn style method', () => { animationTimingFunction: 'var(--motion-ease-out)', offset: 0, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 50px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 50px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 50px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 50), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 50), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 50)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.3, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.42, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 17.5px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 17.5px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 17.5px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 17.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 17.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 17.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.54, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.62, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 10.5px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 10.5px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 10.5px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 10.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 10.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 10.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.74, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.82, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 4.5px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 4.5px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 4.5px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 4.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 4.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 4.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.9, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.95, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 1px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 1px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 1px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 1), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 1), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 1)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 1, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, ], }, @@ -800,7 +800,7 @@ describe('BounceIn style method', () => { '--motion-direction-x': 1, '--motion-direction-y': 0, '--motion-direction-z': 0, - '--motion-distance-factor': 1, + '--motion-distance-unit': '1px', '--motion-perspective': ' ', '--motion-ease-in': 'cubic-bezier(0.39, 0.575, 0.565, 1)', '--motion-ease-out': 'cubic-bezier(0.47, 0, 0.745, 0.715)', @@ -810,61 +810,61 @@ describe('BounceIn style method', () => { animationTimingFunction: 'var(--motion-ease-out)', offset: 0, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 50px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 50px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 50px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 50), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 50), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 50)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.3, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.42, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 17.5px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 17.5px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 17.5px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 17.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 17.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 17.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.54, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.62, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 10.5px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 10.5px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 10.5px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 10.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 10.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 10.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.74, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.82, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 4.5px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 4.5px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 4.5px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 4.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 4.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 4.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.9, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.95, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 1px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 1px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 1px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 1), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 1), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 1)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 1, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, ], }, @@ -894,7 +894,7 @@ describe('BounceIn style method', () => { '--motion-direction-x': -1, '--motion-direction-y': 0, '--motion-direction-z': 0, - '--motion-distance-factor': 1, + '--motion-distance-unit': '1px', '--motion-perspective': ' ', '--motion-ease-in': 'cubic-bezier(0.39, 0.575, 0.565, 1)', '--motion-ease-out': 'cubic-bezier(0.47, 0, 0.745, 0.715)', @@ -904,61 +904,61 @@ describe('BounceIn style method', () => { animationTimingFunction: 'var(--motion-ease-out)', offset: 0, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 50px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 50px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 50px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 50), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 50), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 50)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.3, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.42, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 17.5px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 17.5px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 17.5px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 17.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 17.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 17.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.54, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.62, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 10.5px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 10.5px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 10.5px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 10.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 10.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 10.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.74, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.82, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 4.5px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 4.5px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 4.5px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 4.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 4.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 4.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.9, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.95, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 1px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 1px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 1px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 1), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 1), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 1)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 1, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, ], }, @@ -988,7 +988,7 @@ describe('BounceIn style method', () => { '--motion-direction-x': 0, '--motion-direction-y': 0, '--motion-direction-z': -1, - '--motion-distance-factor': 1, + '--motion-distance-unit': '1px', '--motion-perspective': 'perspective(800px)', '--motion-ease-in': 'cubic-bezier(0.39, 0.575, 0.565, 1)', '--motion-ease-out': 'cubic-bezier(0.47, 0, 0.745, 0.715)', @@ -998,61 +998,61 @@ describe('BounceIn style method', () => { animationTimingFunction: 'var(--motion-ease-out)', offset: 0, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 50px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 50px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 50px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 50), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 50), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 50)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.3, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.42, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 17.5px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 17.5px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 17.5px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 17.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 17.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 17.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.54, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.62, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 10.5px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 10.5px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 10.5px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 10.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 10.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 10.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.74, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.82, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 4.5px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 4.5px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 4.5px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 4.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 4.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 4.5)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.9, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.95, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 1px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 1px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 1px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 1), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 1), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 1)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 1, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-y) * var(--motion-distance-factor) * 0px), calc(var(--motion-direction-z) * var(--motion-distance-factor) * 0px)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, ], }, @@ -1062,4 +1062,25 @@ describe('BounceIn style method', () => { expect(result).toMatchObject(expectedResult); }); + + test('BounceIn animation with calc() travel', () => { + const travel = 'calc(10vh + 20px)'; + const mockOptions = { + ...baseMockOptions, + duration: 1000, + namedEffect: { travel } as BounceInType, + }; + + const result = BounceIn.web(mockOptions); + + expect(result[1].keyframes.slice(0, 3)).toMatchObject([ + { + transform: ` translate3d(calc(0 * calc(0.02 * ${travel}) * 50), calc(1 * calc(0.02 * ${travel}) * 50), calc(0 * calc(0.02 * ${travel}) * 50)) rotateZ(var(--motion-rotate, 0deg))`, + }, + {}, + { + transform: ` translate3d(calc(0 * calc(0.02 * ${travel}) * 17.5), calc(1 * calc(0.02 * ${travel}) * 17.5), calc(0 * calc(0.02 * ${travel}) * 17.5)) rotateZ(var(--motion-rotate, 0deg))`, + }, + ]); + }); }); diff --git a/packages/motion-presets/src/library/entrance/test/CurveIn.spec.ts b/packages/motion-presets/src/library/entrance/test/CurveIn.spec.ts index 8e182784..90f1badd 100644 --- a/packages/motion-presets/src/library/entrance/test/CurveIn.spec.ts +++ b/packages/motion-presets/src/library/entrance/test/CurveIn.spec.ts @@ -156,4 +156,17 @@ describe('CurveIn', () => { expect(result).toMatchObject(expectedResult); }); }); + + test.each([ + ['top', 'rotateX(180deg) rotateY(0deg)'], + ['bottom', 'rotateX(-180deg) rotateY(0deg)'], + ] as const)('CurveIn curves vertically from %s', (from, rotation) => { + const [curveIn] = CurveIn.web({ + ...baseMockOptions, + duration: 1000, + namedEffect: { from } as CurveInType, + }); + + expect((curveIn.keyframes[0] as { transform: string }).transform).toContain(rotation); + }); }); diff --git a/packages/motion-presets/src/library/entrance/test/ExpandIn.spec.ts b/packages/motion-presets/src/library/entrance/test/ExpandIn.spec.ts new file mode 100644 index 00000000..242f00a1 --- /dev/null +++ b/packages/motion-presets/src/library/entrance/test/ExpandIn.spec.ts @@ -0,0 +1,67 @@ +import { describe, expect, test } from 'vitest'; + +import * as ExpandIn from '../ExpandIn'; +import { baseMockOptions } from './testUtils'; +import type { ExpandIn as ExpandInType } from '../../../types'; + +describe('ExpandIn', () => { + test('ExpandIn style animation with default options', () => { + const result = ExpandIn.style?.({ + ...baseMockOptions, + namedEffect: {} as ExpandInType, + }); + + expect(result?.[1].custom).toEqual({ + '--motion-translate-x': '0%', + '--motion-translate-y': '-120%', + '--motion-scale': '0', + }); + }); + + test('ExpandIn with calc() travel uses CSS cos()/sin() of the from angle', () => { + const result = ExpandIn.style?.({ + ...baseMockOptions, + namedEffect: { from: 'top', travel: 'calc(10vh + 20px)' } as ExpandInType, + }); + + expect(result?.[1].custom).toEqual({ + '--motion-translate-x': 'calc(cos(270deg) * calc(10vh + 20px))', + '--motion-translate-y': 'calc(sin(270deg) * calc(10vh + 20px))', + '--motion-scale': '0', + }); + }); + + test.each([ + ['0.5turn', 'calc(cos(0.5turn) * 120%)', 'calc(sin(0.5turn) * 120%)'], + [ + 'calc(90deg + 90deg)', + 'calc(cos(calc(90deg + 90deg)) * 120%)', + 'calc(sin(calc(90deg + 90deg)) * 120%)', + ], + ])('ExpandIn with from %s uses CSS cos()/sin()', (from, x, y) => { + const result = ExpandIn.style?.({ + ...baseMockOptions, + namedEffect: { from } as ExpandInType, + }); + + expect(result?.[1].custom).toMatchObject({ + '--motion-translate-x': x, + '--motion-translate-y': y, + }); + }); + + test.each([180, '180deg', 'left'])( + 'ExpandIn with numeric-equivalent from %s keeps numeric output', + (from) => { + const result = ExpandIn.style?.({ + ...baseMockOptions, + namedEffect: { from } as ExpandInType, + }); + + expect(result?.[1].custom).toMatchObject({ + '--motion-translate-x': '-120%', + '--motion-translate-y': '0%', + }); + }, + ); +}); diff --git a/packages/motion-presets/src/library/entrance/test/GlideIn.spec.ts b/packages/motion-presets/src/library/entrance/test/GlideIn.spec.ts index 97692b3e..de204d24 100644 --- a/packages/motion-presets/src/library/entrance/test/GlideIn.spec.ts +++ b/packages/motion-presets/src/library/entrance/test/GlideIn.spec.ts @@ -87,7 +87,7 @@ describe('GlideIn', () => { ], }, { - name: 'motion-fadeIn', + name: 'motion-fadeInStep', custom: {}, keyframes: [{ opacity: 0, offset: 0, easing: 'step-start' }], }, @@ -124,7 +124,7 @@ describe('GlideIn', () => { ], }, { - name: 'motion-fadeIn', + name: 'motion-fadeInStep', custom: {}, keyframes: [{ opacity: 0, offset: 0, easing: 'step-start' }], }, @@ -134,4 +134,47 @@ describe('GlideIn', () => { expect(result).toMatchObject(expectedResult); }); + + test('GlideIn with calc() travel uses CSS cos()/sin() of the numeric angle', () => { + const result = GlideIn.style?.({ + ...baseMockOptions, + namedEffect: { from: 'bottom', travel: 'calc(10vh + 20px)' } as GlideInType, + }); + + expect(result?.[0].custom).toEqual({ + '--motion-translate-x': 'calc(cos(90deg) * calc(10vh + 20px))', + '--motion-translate-y': 'calc(sin(90deg) * calc(10vh + 20px))', + }); + }); + + test.each([ + ['0.5turn', 'calc(cos(0.5turn) * 100%)', 'calc(sin(0.5turn) * 100%)'], + [ + 'calc(90deg + 90deg)', + 'calc(cos(calc(90deg + 90deg)) * 100%)', + 'calc(sin(calc(90deg + 90deg)) * 100%)', + ], + ])('GlideIn with from %s uses CSS cos()/sin()', (from, x, y) => { + const result = GlideIn.style?.({ + ...baseMockOptions, + namedEffect: { from } as GlideInType, + }); + + expect(result?.[0].custom).toEqual({ '--motion-translate-x': x, '--motion-translate-y': y }); + }); + + test.each([180, '180deg', 'left'])( + 'GlideIn with numeric-equivalent from %s keeps numeric output', + (from) => { + const result = GlideIn.style?.({ + ...baseMockOptions, + namedEffect: { from } as GlideInType, + }); + + expect(result?.[0].custom).toEqual({ + '--motion-translate-x': '-100%', + '--motion-translate-y': '0%', + }); + }, + ); }); diff --git a/packages/motion-presets/src/library/entrance/test/ShuttersIn.spec.ts b/packages/motion-presets/src/library/entrance/test/ShuttersIn.spec.ts index eae033f6..aba3787e 100644 --- a/packages/motion-presets/src/library/entrance/test/ShuttersIn.spec.ts +++ b/packages/motion-presets/src/library/entrance/test/ShuttersIn.spec.ts @@ -196,7 +196,7 @@ describe('ShuttersIn.style()', () => { ], }, { - name: 'motion-fadeIn', + name: 'motion-fadeInStep', custom: {}, keyframes: [{ opacity: 0, offset: 0, easing: 'step-start' }], }, @@ -232,7 +232,7 @@ describe('ShuttersIn.style()', () => { ], }, { - name: 'motion-fadeIn', + name: 'motion-fadeInStep', custom: {}, keyframes: [{ opacity: 0, offset: 0, easing: 'step-start' }], }, @@ -272,7 +272,7 @@ describe('ShuttersIn.style()', () => { ], }, { - name: 'motion-fadeIn', + name: 'motion-fadeInStep', custom: {}, keyframes: [{ opacity: 0, offset: 0, easing: 'step-start' }], }, @@ -312,7 +312,7 @@ describe('ShuttersIn.style()', () => { ], }, { - name: 'motion-fadeIn', + name: 'motion-fadeInStep', custom: {}, keyframes: [{ opacity: 0, offset: 0, easing: 'step-start' }], }, @@ -351,7 +351,7 @@ describe('ShuttersIn.style()', () => { }, { easing: 'easeInOutQuad', - name: 'motion-fadeIn', + name: 'motion-fadeInStep', custom: {}, keyframes: [{ opacity: 0, offset: 0, easing: 'step-start' }], }, diff --git a/packages/motion-presets/src/library/entrance/test/SpinIn.spec.ts b/packages/motion-presets/src/library/entrance/test/SpinIn.spec.ts index e67476e9..fbabfd00 100644 --- a/packages/motion-presets/src/library/entrance/test/SpinIn.spec.ts +++ b/packages/motion-presets/src/library/entrance/test/SpinIn.spec.ts @@ -156,4 +156,14 @@ describe('SpinIn', () => { expect(result).toMatchObject(expectedResult); }); + + test('SpinIn fade lasts at most the animation duration', () => { + const result = SpinIn.style?.({ + ...baseMockOptions, + duration: 1000, + namedEffect: { scale: 2 } as SpinInType, + }); + + expect(result?.[0].duration).toBe(1000); + }); }); diff --git a/packages/motion-presets/src/library/mouse/AiryMouse.ts b/packages/motion-presets/src/library/mouse/AiryMouse.ts index c177b633..cba1a119 100644 --- a/packages/motion-presets/src/library/mouse/AiryMouse.ts +++ b/packages/motion-presets/src/library/mouse/AiryMouse.ts @@ -2,7 +2,7 @@ import { getCssUnits, getMouseTransitionEasing, mapRange, - parseLength, + parseLengthOrCalc, parseDirection, } from '../../utils'; import type { @@ -28,19 +28,23 @@ class AiryMouseAnimation extends CustomMouse { let translateX = 0; let translateY = 0; const { distance, invert, angle, axis } = this.options; + const distanceValue = typeof distance === 'string' ? 1 : distance.value; if (axis !== 'vertical') { - translateX = mapRange(0, 1, -distance.value, distance.value, progressX) * invert; + translateX = mapRange(0, 1, -distanceValue, distanceValue, progressX) * invert; } if (axis !== 'horizontal') { - translateY = mapRange(0, 1, -distance.value, distance.value, progressY) * invert; + translateY = mapRange(0, 1, -distanceValue, distanceValue, progressY) * invert; } // if progress === 0, rotate === angle, if progress === 0.5, rotate === 0, if progress === 1, rotate === angle const rotate = mapRange(0, 1, -angle, angle, progressX) * invert; - const units = getCssUnits(distance.unit); + const toLength = (value: number) => + typeof distance === 'string' + ? `calc(${value} * ${distance})` + : `${value}${getCssUnits(distance.unit)}`; - this.target.style.transform = `translateX(${translateX}${units}) translateY(${translateY}${units}) rotate(calc(${rotate}deg + var(--motion-rotate, 0deg)))`; + this.target.style.transform = `translateX(${toLength(translateX)}) translateY(${toLength(translateY)}) rotate(calc(${rotate}deg + var(--motion-rotate, 0deg)))`; } cancel() { @@ -53,7 +57,7 @@ export default function create(options: ScrubAnimationOptions & AnimationExtraOp const { transitionDuration, transitionEasing } = options; const namedEffect = options.namedEffect as AiryMouse; const inverted = namedEffect.inverted ?? schema.inverted.default; - const distance = parseLength(namedEffect.distance, schema.distance.default); + const distance = parseLengthOrCalc(namedEffect.distance, schema.distance.default); const angle = parseDirection(namedEffect.angle, [], schema.angle.default, true) as number; const axis = parseDirection(namedEffect.axis, AXES, schema.axis.default) as MouseEffectAxis; const invert = inverted ? -1 : 1; diff --git a/packages/motion-presets/src/library/mouse/BlobMouse.ts b/packages/motion-presets/src/library/mouse/BlobMouse.ts index 94ea9194..65e4de6c 100644 --- a/packages/motion-presets/src/library/mouse/BlobMouse.ts +++ b/packages/motion-presets/src/library/mouse/BlobMouse.ts @@ -1,4 +1,4 @@ -import { getCssUnits, getMouseTransitionEasing, mapRange, parseLength } from '../../utils'; +import { getCssUnits, getMouseTransitionEasing, mapRange, parseLengthOrCalc } from '../../utils'; import { CustomMouse } from './CustomMouse'; import { ScrubAnimationOptions, AnimationExtraOptions, BlobMouse, Progress } from '../../types'; @@ -11,8 +11,9 @@ export const schema = { class BlobMouseAnimation extends CustomMouse { progress({ x: progressX, y: progressY }: Progress) { const { distance, scale, invert } = this.options; - const translateX = mapRange(0, 1, -distance.value, distance.value, progressX) * invert; - const translateY = mapRange(0, 1, -distance.value, distance.value, progressY) * invert; + const distanceValue = typeof distance === 'string' ? 1 : distance.value; + const translateX = mapRange(0, 1, -distanceValue, distanceValue, progressX) * invert; + const translateY = mapRange(0, 1, -distanceValue, distanceValue, progressY) * invert; // if progressX === 0 || progressX === 1, scaleX === scale, if progressX === 0.5, scaleX === 1 const scaleX = @@ -26,9 +27,12 @@ class BlobMouseAnimation extends CustomMouse { ? mapRange(0, 0.5, scale, 1, progressY) : mapRange(0.5, 1, 1, scale, progressY); - const units = getCssUnits(distance.unit); + const toLength = (value: number) => + typeof distance === 'string' + ? `calc(${value} * ${distance})` + : `${value}${getCssUnits(distance.unit)}`; - this.target.style.transform = `translateX(${translateX}${units}) translateY(${translateY}${units}) scale(${scaleX}, ${scaleY}) rotate(var(--motion-rotate, 0deg))`; + this.target.style.transform = `translateX(${toLength(translateX)}) translateY(${toLength(translateY)}) scale(${scaleX}, ${scaleY}) rotate(var(--motion-rotate, 0deg))`; } cancel() { @@ -41,7 +45,7 @@ export default function create(options: ScrubAnimationOptions & AnimationExtraOp const { transitionDuration, transitionEasing } = options; const namedEffect = options.namedEffect as BlobMouse; const { inverted = schema.inverted.default, scale = schema.scale.default } = namedEffect; - const distance = parseLength(namedEffect.distance, schema.distance.default); + const distance = parseLengthOrCalc(namedEffect.distance, schema.distance.default); const invert = inverted ? -1 : 1; const animationOptions = { diff --git a/packages/motion-presets/src/library/mouse/BlurMouse.ts b/packages/motion-presets/src/library/mouse/BlurMouse.ts index ab6fbb70..6fe69ceb 100644 --- a/packages/motion-presets/src/library/mouse/BlurMouse.ts +++ b/packages/motion-presets/src/library/mouse/BlurMouse.ts @@ -3,7 +3,7 @@ import { getMouseTransitionEasing, distance2d, mapRange, - parseLength, + parseLengthOrCalc, parseDirection, } from '../../utils'; import { quadInOut } from '@wix/motion'; @@ -22,9 +22,10 @@ export const schema = { class BlurMouseAnimation extends CustomMouse { progress({ x: progressX, y: progressY }: Progress) { const { distance, angle, scale, invert, blur, perspective } = this.options; + const distanceValue = typeof distance === 'string' ? 1 : distance.value; - const translateX = mapRange(0, 1, -distance.value, distance.value, progressX) * invert; - const translateY = mapRange(0, 1, -distance.value, distance.value, progressY) * invert; + const translateX = mapRange(0, 1, -distanceValue, distanceValue, progressX) * invert; + const translateY = mapRange(0, 1, -distanceValue, distanceValue, progressY) * invert; // if progressX === 0 || progressX === 1, scaleX === scale, if progressX === 0.5, scaleX === 1 const scaleX = @@ -44,9 +45,12 @@ class BlurMouseAnimation extends CustomMouse { const rotateX = mapRange(0, 1, -angle, angle, progressY) * invert; const rotateY = mapRange(0, 1, angle, -angle, progressX) * invert; - const units = getCssUnits(distance.unit); + const toLength = (value: number) => + typeof distance === 'string' + ? `calc(${value} * ${distance})` + : `${value}${getCssUnits(distance.unit)}`; - const transform = `perspective(${perspective}px) translateX(${translateX}${units}) translateY(${translateY}${units}) scale(${maxScale}, ${maxScale}) rotateX(${rotateX}deg) rotateY(${rotateY}deg) rotate(var(--motion-rotate, 0deg))`; + const transform = `perspective(${perspective}px) translateX(${toLength(translateX)}) translateY(${toLength(translateY)}) scale(${maxScale}, ${maxScale}) rotateX(${rotateX}deg) rotateY(${rotateY}deg) rotate(var(--motion-rotate, 0deg))`; const progressDistance = distance2d([0.5, 0.5], [progressX, progressY]); const blurFilter = Math.round(mapRange(0, 1, 0, blur, quadInOut(progressDistance))); @@ -68,7 +72,7 @@ export default function create(options: ScrubAnimationOptions & AnimationExtraOp const { transitionDuration, transitionEasing } = options; const namedEffect = options.namedEffect as BlurMouse; const inverted = namedEffect.inverted ?? schema.inverted.default; - const distance = parseLength(namedEffect.distance, schema.distance.default); + const distance = parseLengthOrCalc(namedEffect.distance, schema.distance.default); const angle = parseDirection(namedEffect.angle, [], schema.angle.default, true) as number; const { scale = schema.scale.default, diff --git a/packages/motion-presets/src/library/mouse/BounceMouse.ts b/packages/motion-presets/src/library/mouse/BounceMouse.ts index 81cd339c..2c9f011e 100644 --- a/packages/motion-presets/src/library/mouse/BounceMouse.ts +++ b/packages/motion-presets/src/library/mouse/BounceMouse.ts @@ -1,5 +1,5 @@ import { ScrubAnimationOptions, AnimationExtraOptions, BounceMouse, TrackMouse } from '../../types'; -import { parseLength } from '../../utils'; +import { parseLengthOrCalc } from '../../utils'; import createTrack, { AXES } from './TrackMouse'; export const schema = { @@ -10,7 +10,7 @@ export const schema = { export default function create(options: ScrubAnimationOptions & AnimationExtraOptions) { const namedEffect = options.namedEffect as BounceMouse; - const distance = parseLength(namedEffect.distance, schema.distance.default); + const distance = parseLengthOrCalc(namedEffect.distance, schema.distance.default); const { transitionEasing = 'elastic' } = options; return createTrack({ ...options, diff --git a/packages/motion-presets/src/library/mouse/ScaleMouse.ts b/packages/motion-presets/src/library/mouse/ScaleMouse.ts index 2c41dac0..276db80a 100644 --- a/packages/motion-presets/src/library/mouse/ScaleMouse.ts +++ b/packages/motion-presets/src/library/mouse/ScaleMouse.ts @@ -2,7 +2,7 @@ import { getCssUnits, getMouseTransitionEasing, mapRange, - parseLength, + parseLengthOrCalc, parseDirection, } from '../../utils'; import { CustomMouse } from './CustomMouse'; @@ -26,6 +26,7 @@ export const schema = { class ScaleMouseAnimation extends CustomMouse { progress({ x: progressX, y: progressY }: Progress) { const { distance, scale, invert, axis } = this.options; + const distanceValue = typeof distance === 'string' ? 1 : distance.value; let translateX = 0; let translateY = 0; @@ -33,7 +34,7 @@ class ScaleMouseAnimation extends CustomMouse { let scaleY = 1; if (axis === 'both' || axis === 'horizontal') { - translateX = mapRange(0, 1, -distance.value, distance.value, progressX) * invert; + translateX = mapRange(0, 1, -distanceValue, distanceValue, progressX) * invert; scaleX = progressX < 0.5 ? mapRange(0, 0.5, scale, 1, progressX) @@ -41,7 +42,7 @@ class ScaleMouseAnimation extends CustomMouse { } if (axis === 'both' || axis === 'vertical') { - translateY = mapRange(0, 1, -distance.value, distance.value, progressY) * invert; + translateY = mapRange(0, 1, -distanceValue, distanceValue, progressY) * invert; scaleY = progressY < 0.5 ? mapRange(0, 0.5, scale, 1, progressY) @@ -52,9 +53,12 @@ class ScaleMouseAnimation extends CustomMouse { // and the bigger scale value if scale > 1 const scaleBoth = scale < 1 ? Math.min(scaleX, scaleY) : Math.max(scaleX, scaleY); - const units = getCssUnits(distance.unit); + const toLength = (value: number) => + typeof distance === 'string' + ? `calc(${value} * ${distance})` + : `${value}${getCssUnits(distance.unit)}`; - this.target.style.transform = `translateX(${translateX}${units}) translateY(${translateY}${units}) scale(${scaleBoth}) rotate(var(--motion-rotate, 0deg))`; + this.target.style.transform = `translateX(${toLength(translateX)}) translateY(${toLength(translateY)}) scale(${scaleBoth}) rotate(var(--motion-rotate, 0deg))`; } cancel() { @@ -67,7 +71,7 @@ export default function create(options: ScrubAnimationOptions & AnimationExtraOp const { transitionDuration, transitionEasing } = options; const namedEffect = options.namedEffect as ScaleMouse; const inverted = namedEffect.inverted ?? schema.inverted.default; - const distance = parseLength(namedEffect.distance, schema.distance.default); + const distance = parseLengthOrCalc(namedEffect.distance, schema.distance.default); const axis = parseDirection(namedEffect.axis, AXES, schema.axis.default) as MouseEffectAxis; const { scale = schema.scale.default } = namedEffect; const invert = inverted ? -1 : 1; diff --git a/packages/motion-presets/src/library/mouse/SkewMouse.ts b/packages/motion-presets/src/library/mouse/SkewMouse.ts index 04b6e6ec..624a0eb2 100644 --- a/packages/motion-presets/src/library/mouse/SkewMouse.ts +++ b/packages/motion-presets/src/library/mouse/SkewMouse.ts @@ -9,7 +9,7 @@ import { getCssUnits, getMouseTransitionEasing, mapRange, - parseLength, + parseLengthOrCalc, parseDirection, } from '../../utils'; import { circInOut } from '@wix/motion'; @@ -31,14 +31,15 @@ class SkewMouseAnimation extends CustomMouse { let skewX = 0; let skewY = 0; const { distance, angle, axis, invert } = this.options; + const distanceValue = typeof distance === 'string' ? 1 : distance.value; // distance if (axis !== 'vertical') { - translateX = mapRange(0, 1, -distance.value, distance.value, progressX) * invert; + translateX = mapRange(0, 1, -distanceValue, distanceValue, progressX) * invert; skewX = mapRange(0, 1, angle, -angle, progressX) * invert; } if (axis !== 'horizontal') { - translateY = mapRange(0, 1, -distance.value, distance.value, progressY) * invert; + translateY = mapRange(0, 1, -distanceValue, distanceValue, progressY) * invert; skewY = mapRange(0, 1, angle, -angle, progressY) * invert; } if (axis === 'both') { @@ -53,9 +54,12 @@ class SkewMouseAnimation extends CustomMouse { skewY *= mapRange(0, 1, 1, -1, circInOut(progressX)); } - const units = getCssUnits(distance.unit); + const toLength = (value: number) => + typeof distance === 'string' + ? `calc(${value} * ${distance})` + : `${value}${getCssUnits(distance.unit)}`; - const transform = `translateX(${translateX}${units}) translateY(${translateY}${units}) skew(${skewX}deg, ${skewY}deg) rotate(var(--motion-rotate, 0deg))`; + const transform = `translateX(${toLength(translateX)}) translateY(${toLength(translateY)}) skew(${skewX}deg, ${skewY}deg) rotate(var(--motion-rotate, 0deg))`; this.target.style.transform = transform; } @@ -70,7 +74,7 @@ export default function create(options: ScrubAnimationOptions & AnimationExtraOp const { transitionDuration, transitionEasing } = options; const namedEffect = options.namedEffect as SkewMouse; const inverted = namedEffect.inverted ?? schema.inverted.default; - const distance = parseLength(namedEffect.distance, schema.distance.default); + const distance = parseLengthOrCalc(namedEffect.distance, schema.distance.default); const angle = parseDirection(namedEffect.angle, [], schema.angle.default, true) as number; const axis = parseDirection(namedEffect.axis, AXES, schema.axis.default) as MouseEffectAxis; const invert = inverted ? -1 : 1; diff --git a/packages/motion-presets/src/library/mouse/Track3DMouse.ts b/packages/motion-presets/src/library/mouse/Track3DMouse.ts index b87b0aa2..610462da 100644 --- a/packages/motion-presets/src/library/mouse/Track3DMouse.ts +++ b/packages/motion-presets/src/library/mouse/Track3DMouse.ts @@ -9,7 +9,7 @@ import { getCssUnits, getMouseTransitionEasing, mapRange, - parseLength, + parseLengthOrCalc, parseDirection, } from '../../utils'; import { CustomMouse } from './CustomMouse'; @@ -27,6 +27,7 @@ export const schema = { class Track3DMouseAnimation extends CustomMouse { progress({ x: progressX, y: progressY }: Progress) { const { invert, distance, angle, axis, perspective } = this.options; + const distanceValue = typeof distance === 'string' ? 1 : distance.value; let translateX = 0; let translateY = 0; @@ -37,16 +38,19 @@ class Track3DMouseAnimation extends CustomMouse { // if progressX === 1, translateX === distance // if progressX === 0, rotateX === -angle, if progressX === 0.5, rotateX === 0, if progressX === 1, rotateX === angle if (axis === 'both' || axis === 'horizontal') { - translateX = mapRange(0, 1, -distance.value, distance.value, progressX); + translateX = mapRange(0, 1, -distanceValue, distanceValue, progressX); rotateY = mapRange(0, 1, -angle, angle, progressX) * invert; } if (axis === 'both' || axis === 'vertical') { - translateY = mapRange(0, 1, -distance.value, distance.value, progressY); + translateY = mapRange(0, 1, -distanceValue, distanceValue, progressY); rotateX = mapRange(0, 1, angle, -angle, progressY) * invert; } - const units = getCssUnits(distance.unit); + const toLength = (value: number) => + typeof distance === 'string' + ? `calc(${value} * ${distance})` + : `${value}${getCssUnits(distance.unit)}`; - this.target.style.transform = `perspective(${perspective}px) translateX(${translateX}${units}) translateY(${translateY}${units}) rotateX(${rotateX}deg) rotateY(${rotateY}deg) rotate(var(--motion-rotate, 0deg))`; + this.target.style.transform = `perspective(${perspective}px) translateX(${toLength(translateX)}) translateY(${toLength(translateY)}) rotateX(${rotateX}deg) rotateY(${rotateY}deg) rotate(var(--motion-rotate, 0deg))`; } cancel() { @@ -59,7 +63,7 @@ export default function create(options: ScrubAnimationOptions & AnimationExtraOp const { transitionDuration, transitionEasing } = options; const namedEffect = options.namedEffect as Track3DMouse; const inverted = namedEffect.inverted ?? schema.inverted.default; - const distance = parseLength(namedEffect.distance, schema.distance.default); + const distance = parseLengthOrCalc(namedEffect.distance, schema.distance.default); const angle = parseDirection(namedEffect.angle, [], schema.angle.default, true) as number; const axis = parseDirection(namedEffect.axis, AXES, schema.axis.default) as MouseEffectAxis; const { perspective = schema.perspective.default } = namedEffect; diff --git a/packages/motion-presets/src/library/mouse/TrackMouse.ts b/packages/motion-presets/src/library/mouse/TrackMouse.ts index 30564c1c..99b48cfc 100644 --- a/packages/motion-presets/src/library/mouse/TrackMouse.ts +++ b/packages/motion-presets/src/library/mouse/TrackMouse.ts @@ -9,7 +9,7 @@ import { getCssUnits, getMouseTransitionEasing, mapRange, - parseLength, + parseLengthOrCalc, parseDirection, } from '../../utils'; import { CustomMouse } from './CustomMouse'; @@ -25,6 +25,7 @@ export const schema = { class TrackMouseAnimation extends CustomMouse { progress({ x: progressX, y: progressY }: Progress) { const { invert, distance, axis } = this.options; + const distanceValue = typeof distance === 'string' ? 1 : distance.value; let translateX = 0; let translateY = 0; @@ -33,16 +34,19 @@ class TrackMouseAnimation extends CustomMouse { // if progressX === 0.5, translateX === 0 // if progressX === 1, translateX === distance if (axis === 'both' || axis === 'horizontal') { - translateX = mapRange(0, 1, -distance.value, distance.value, progressX) * invert; + translateX = mapRange(0, 1, -distanceValue, distanceValue, progressX) * invert; } if (axis === 'both' || axis === 'vertical') { - translateY = mapRange(0, 1, -distance.value, distance.value, progressY) * invert; + translateY = mapRange(0, 1, -distanceValue, distanceValue, progressY) * invert; } - const units = getCssUnits(distance.unit); + const toLength = (value: number) => + typeof distance === 'string' + ? `calc(${value} * ${distance})` + : `${value}${getCssUnits(distance.unit)}`; - this.target.style.transform = `translateX(${translateX}${units}) translateY(${translateY}${units}) rotate(var(--motion-rotate, 0deg))`; + this.target.style.transform = `translateX(${toLength(translateX)}) translateY(${toLength(translateY)}) rotate(var(--motion-rotate, 0deg))`; } cancel() { this.target.style.transform = ''; @@ -54,7 +58,7 @@ export default function create(options: ScrubAnimationOptions & AnimationExtraOp const { transitionDuration, transitionEasing } = options; const namedEffect = options.namedEffect as TrackMouse; const inverted = namedEffect.inverted ?? schema.inverted.default; - const distance = parseLength(namedEffect.distance, schema.distance.default); + const distance = parseLengthOrCalc(namedEffect.distance, schema.distance.default); const axis = parseDirection(namedEffect.axis, AXES, schema.axis.default) as MouseEffectAxis; const invert = inverted ? -1 : 1; const animationOptions = { diff --git a/packages/motion-presets/src/library/mouse/test/TrackMouse.spec.ts b/packages/motion-presets/src/library/mouse/test/TrackMouse.spec.ts new file mode 100644 index 00000000..8b7bc271 --- /dev/null +++ b/packages/motion-presets/src/library/mouse/test/TrackMouse.spec.ts @@ -0,0 +1,40 @@ +import { describe, expect, test } from 'vitest'; + +import createTrackMouse from '../TrackMouse'; +import type { TrackMouse as TrackMouseType } from '../../../types'; + +function getTransform(namedEffect: Record, x: number, y: number) { + const target = { style: {} } as HTMLElement; + const animation = createTrackMouse({ + id: 'test-id', + namedEffect: { type: 'TrackMouse', ...namedEffect } as TrackMouseType, + })(target); + animation.progress({ x, y }); + return target.style.transform; +} + +describe('TrackMouse', () => { + test('numeric distance', () => { + expect(getTransform({ distance: { value: 100, unit: 'px' } }, 0, 0.75)).toBe( + 'translateX(-100px) translateY(50px) rotate(var(--motion-rotate, 0deg))', + ); + }); + + test('default distance', () => { + expect(getTransform({ inverted: true }, 0.25, 1)).toBe( + 'translateX(100px) translateY(-200px) rotate(var(--motion-rotate, 0deg))', + ); + }); + + test('calc() distance', () => { + expect(getTransform({ distance: 'calc(10vh + 20px)', inverted: true }, 0, 0.75)).toBe( + 'translateX(calc(1 * calc(10vh + 20px))) translateY(calc(-0.5 * calc(10vh + 20px))) rotate(var(--motion-rotate, 0deg))', + ); + }); + + test('calc() distance with axis', () => { + expect(getTransform({ distance: 'calc(10vh + 20px)', axis: 'horizontal' }, 0.75, 0)).toBe( + 'translateX(calc(0.5 * calc(10vh + 20px))) translateY(calc(0 * calc(10vh + 20px))) rotate(var(--motion-rotate, 0deg))', + ); + }); +}); diff --git a/packages/motion-presets/src/library/ongoing/Bounce.ts b/packages/motion-presets/src/library/ongoing/Bounce.ts index ab67c4b3..fa331c7c 100644 --- a/packages/motion-presets/src/library/ongoing/Bounce.ts +++ b/packages/motion-presets/src/library/ongoing/Bounce.ts @@ -1,5 +1,12 @@ import type { TimeAnimationOptions, Bounce, AnimationExtraOptions, DomApi } from '../../types'; -import { getEasing, getTimingFactor, toKeyframeValue, parseLength, getCssUnits } from '../../utils'; +import { + getEasing, + getTimingFactor, + toKeyframeValue, + parseLengthOrCalc, + scaleLength, + roundNumber, +} from '../../utils'; const PEAK_TRANSLATE = 49; @@ -24,35 +31,31 @@ const TRANSLATE_Y_KEYFRAMES = [ { keyframe: 100, translateY: 0 }, ]; +const roundUnit = (value: number) => roundNumber(value, 6); + export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom?: DomApi) { return style(options, true); } export function style(options: TimeAnimationOptions & AnimationExtraOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as Bounce; - const travel = parseLength(namedEffect.travel, schema.travel.default); - const unit = getCssUnits(travel.unit); + const travel = parseLengthOrCalc(namedEffect.travel, schema.travel.default); const duration = options.duration || 1; const iterationDelay = namedEffect?.iterationDelay || schema.iterationDelay.default; const timingFactor = getTimingFactor(duration, iterationDelay) as number; const [name] = getNames(options); - const bounceFactor = travel.value / PEAK_TRANSLATE; - const easing = getEasing('sineOut'); const custom = { - '--motion-bounce-factor': bounceFactor, + [`--motion-bounce-unit${suffix}`]: scaleLength(travel, 1 / PEAK_TRANSLATE, roundUnit), }; const keyframes = TRANSLATE_Y_KEYFRAMES.map(({ keyframe, translateY }) => ({ offset: (keyframe / 100) * timingFactor, - translate: `0px calc(${translateY / 2}${unit} * ${toKeyframeValue( - custom, - '--motion-bounce-factor', - asWeb, - )})`, + translate: `0px calc(${translateY / 2} * ${toKeyframeValue(custom, `--motion-bounce-unit${suffix}`, asWeb)})`, easing, })); diff --git a/packages/motion-presets/src/library/ongoing/Breathe.ts b/packages/motion-presets/src/library/ongoing/Breathe.ts index aeb87e0c..a29ae628 100644 --- a/packages/motion-presets/src/library/ongoing/Breathe.ts +++ b/packages/motion-presets/src/library/ongoing/Breathe.ts @@ -6,7 +6,7 @@ import { getTimingFactor, toKeyframeValue, parseDirection, - parseLength, + parseLengthOrCalc, } from '../../utils'; import { AXIS_DIRECTIONS } from '../../consts'; @@ -38,13 +38,14 @@ export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom? } export function style(options: TimeAnimationOptions & AnimationExtraOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as Breathe; const direction = parseDirection<(typeof DIRECTIONS)[number]>( namedEffect?.direction, DIRECTIONS, schema.direction.default, ); - const distance = parseLength(namedEffect.travel, schema.travel.default); + const distance = parseLengthOrCalc(namedEffect.travel, schema.travel.default); const { perspective = schema.perspective.default } = namedEffect; const easing = options.easing || 'sineInOut'; @@ -60,47 +61,51 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW // Create CSS custom properties for the Breathe configuration const custom: Record = { - '--motion-breathe-perspective': perspectiveTransform, - '--motion-breathe-distance': `${distance.value}${getCssUnits(distance.unit || 'px')}`, - '--motion-breathe-x': x, - '--motion-breathe-y': y, - '--motion-breathe-z': z, + [`--motion-breathe-perspective${suffix}`]: perspectiveTransform, + [`--motion-breathe-distance${suffix}`]: + typeof distance === 'string' + ? distance + : `${distance.value}${getCssUnits(distance.unit || 'px')}`, + [`--motion-breathe-x${suffix}`]: x, + [`--motion-breathe-y${suffix}`]: y, + [`--motion-breathe-z${suffix}`]: z, }; - const breatheX = `${toKeyframeValue(custom, '--motion-breathe-x', asWeb)}`; - const breatheY = `${toKeyframeValue(custom, '--motion-breathe-y', asWeb)}`; - const breatheZ = `${toKeyframeValue(custom, '--motion-breathe-z', asWeb)}`; + const breatheX = `${toKeyframeValue(custom, `--motion-breathe-x${suffix}`, asWeb)}`; + const breatheY = `${toKeyframeValue(custom, `--motion-breathe-y${suffix}`, asWeb)}`; + const breatheZ = `${toKeyframeValue(custom, `--motion-breathe-z${suffix}`, asWeb)}`; const breathePerspective = `${toKeyframeValue( custom, - '--motion-breathe-perspective', + `--motion-breathe-perspective${suffix}`, asWeb, '', )}`; - const breatheDistance = `${toKeyframeValue(custom, '--motion-breathe-distance', asWeb)}`; + const breatheDistance = `${toKeyframeValue(custom, `--motion-breathe-distance${suffix}`, asWeb)}`; - const keyframes = iterationDelay - ? FACTORS_SEQUENCE.map(({ translateFactor, timeFactor }) => { - const keyframeOffset = timeFactor * timingFactor; - const distancePart = `${breatheDistance} * ${translateFactor}`; + const keyframes = + timingFactor < 1 + ? FACTORS_SEQUENCE.map(({ translateFactor, timeFactor }) => { + const keyframeOffset = timeFactor * timingFactor; + const distancePart = `${breatheDistance} * ${translateFactor}`; - return { - offset: keyframeOffset, - easing: getEasing(ease.inOut), - transform: `${breathePerspective} translate3d(calc(${breatheX} * ${distancePart}), calc(${breatheY} * ${distancePart}), calc(${breatheZ} * ${distancePart})) rotateZ(var(--motion-rotate, 0deg))`, - }; - }) - : [ - { - offset: 0.25, - easing: getEasing(ease.inOut), - transform: `${breathePerspective} translate3d(calc(${breatheX} * ${breatheDistance}), calc(${breatheY} * ${breatheDistance}), calc(${breatheZ} * ${breatheDistance})) rotateZ(var(--motion-rotate, 0deg))`, - }, - { - offset: 0.75, - easing: getEasing(ease.in), - transform: `${breathePerspective} translate3d(calc(${breatheX} * -1 * ${breatheDistance}), calc(${breatheY} * -1 * ${breatheDistance}), calc(${breatheZ} * -1 * ${breatheDistance})) rotateZ(var(--motion-rotate, 0deg))`, - }, - ]; + return { + offset: keyframeOffset, + easing: getEasing(ease.inOut), + transform: `${breathePerspective} translate3d(calc(${breatheX} * ${distancePart}), calc(${breatheY} * ${distancePart}), calc(${breatheZ} * ${distancePart})) rotateZ(var(--motion-rotate, 0deg))`, + }; + }) + : [ + { + offset: 0.25, + easing: getEasing(ease.inOut), + transform: `${breathePerspective} translate3d(calc(${breatheX} * ${breatheDistance}), calc(${breatheY} * ${breatheDistance}), calc(${breatheZ} * ${breatheDistance})) rotateZ(var(--motion-rotate, 0deg))`, + }, + { + offset: 0.75, + easing: getEasing(ease.in), + transform: `${breathePerspective} translate3d(calc(${breatheX} * -1 * ${breatheDistance}), calc(${breatheY} * -1 * ${breatheDistance}), calc(${breatheZ} * -1 * ${breatheDistance})) rotateZ(var(--motion-rotate, 0deg))`, + }, + ]; return [ { diff --git a/packages/motion-presets/src/library/ongoing/Cross.ts b/packages/motion-presets/src/library/ongoing/Cross.ts index aa9e6983..ee7a54fb 100644 --- a/packages/motion-presets/src/library/ongoing/Cross.ts +++ b/packages/motion-presets/src/library/ongoing/Cross.ts @@ -4,10 +4,9 @@ import type { Cross, DomApi, EffectEightDirections, - HorizontalOffsetByDirectionParams, + EffectFourDirections, OffsetByDirectionParams, TimeAnimationOptions, - VerticalOffsetByDirectionParams, } from '../../types'; import { getElementOffset, getTimingFactor, parseDirection } from '../../utils'; @@ -74,84 +73,34 @@ const TRANSLATE_BY_DIRECTION_MAP = { }, }; -const GET_OFFSET_BY_DIRECTION_MAP = { - // (width + left) / (100cqw + width) - left: ({ left, width, parentWidth }: HorizontalOffsetByDirectionParams) => - (width + left) / (parentWidth + width || 1), - // (100cqw - left) / (100cqw + width) - right: ({ left, width, parentWidth }: HorizontalOffsetByDirectionParams) => - (parentWidth - left) / (parentWidth + width || 1), - // (100cqh - top) / (100cqh + height) - bottom: ({ top, height, parentHeight }: VerticalOffsetByDirectionParams) => - (parentHeight - top) / (parentHeight + height || 1), - // (height + top) / (100cqh + height) - top: ({ top, height, parentHeight }: VerticalOffsetByDirectionParams) => - (height + top) / (parentHeight + height || 1), - // min(, ) - 'bottom-right': ({ - left, - top, - width, - height, - parentWidth, - parentHeight, - }: OffsetByDirectionParams) => { - const leftDistance = width + left; - const topDistance = parentHeight - top; +const SIDE_DISTANCE: Record number> = { + left: ({ left, width }) => left + width, + right: ({ left, parentWidth }) => parentWidth - left, + top: ({ top, height }) => top + height, + bottom: ({ top, parentHeight }) => parentHeight - top, +}; - return leftDistance < topDistance - ? leftDistance / (parentWidth + width || 1) - : topDistance / (parentHeight + height || 1); - }, - // min(, ) - 'bottom-left': ({ - left, - top, - width, - height, - parentWidth, - parentHeight, - }: OffsetByDirectionParams) => { - const rightDistance = parentWidth - left; - const topDistance = parentHeight - top; +const OPPOSITE_SIDE: Record = { + left: 'right', + right: 'left', + top: 'bottom', + bottom: 'top', +}; - return rightDistance < topDistance - ? rightDistance / (parentWidth + width || 1) - : topDistance / (parentHeight + height || 1); - }, - // min(, ) - 'top-right': ({ - left, - top, - width, - height, - parentWidth, - parentHeight, - }: OffsetByDirectionParams) => { - const leftDistance = parentWidth - left; - const bottomDistance = height + top; +function getDistance(sides: EffectFourDirections[], params: OffsetByDirectionParams) { + return Math.min(...sides.map((side) => SIDE_DISTANCE[side](params))); +} - return leftDistance < bottomDistance - ? leftDistance / (parentWidth + width || 1) - : bottomDistance / (parentHeight + height || 1); - }, - // min(, ) - 'top-left': ({ - left, - top, - width, - height, - parentWidth, - parentHeight, - }: OffsetByDirectionParams) => { - const rightDistance = parentWidth - left; - const bottomDistance = height + top; +function getOffsetByDirection(direction: EffectEightDirections, params: OffsetByDirectionParams) { + const sides = direction.split('-') as EffectFourDirections[]; + const toDistance = getDistance(sides, params); + const fromDistance = getDistance( + sides.map((side) => OPPOSITE_SIDE[side]), + params, + ); - return rightDistance < bottomDistance - ? rightDistance / (parentWidth + width || 1) - : bottomDistance / (parentHeight + height || 1); - }, -}; + return toDistance / (toDistance + fromDistance || 1); +} function generateTranslate(direction: keyof typeof FOUR_CORNERS_TRANSLATIONS) { const _from = FOUR_CORNERS_TRANSLATIONS[direction].from; @@ -189,9 +138,40 @@ export function web(options: TimeAnimationOptions & AnimationExtraOptions, dom?: '--motion-parent-height': '100vh', }; - /* - * Prepare function - */ + let from, to; + if (direction in TRANSLATE_BY_DIRECTION_MAP) { + from = TRANSLATE_BY_DIRECTION_MAP[direction as keyof typeof TRANSLATE_BY_DIRECTION_MAP].from; + to = TRANSLATE_BY_DIRECTION_MAP[direction as keyof typeof TRANSLATE_BY_DIRECTION_MAP].to; + } else { + const cornerFromTo = generateTranslate(direction as keyof typeof FOUR_CORNERS_TRANSLATIONS); + from = cornerFromTo.from; + to = cornerFromTo.to; + } + + const keyframes = [ + { + offset: 0, + translate: '0 0', + }, + { + offset: 0, + translate: to, + easing: 'step-start', + }, + { + offset: 0, + translate: from, + }, + { + offset: timingFactor, + translate: '0 0', + }, + { + offset: 1, + translate: '0 0', + }, + ]; + let left = 0; let top = 0; let width = 0; @@ -200,6 +180,7 @@ export function web(options: TimeAnimationOptions & AnimationExtraOptions, dom?: let parentHeight = 0; if (dom) { + // @wix/motion reads the keyframes before measuring, so the wrap offset is updated in place dom.measure((target) => { if (!target) { return; @@ -216,6 +197,18 @@ export function web(options: TimeAnimationOptions & AnimationExtraOptions, dom?: height = targetHeight; parentWidth = parentRect.width; parentHeight = parentRect.height; + + const toDurationOffset = + getOffsetByDirection(direction, { + left, + top, + width, + height, + parentWidth, + parentHeight, + }) * timingFactor; + keyframes[1].offset = toDurationOffset; + keyframes[2].offset = toDurationOffset; }); dom.mutate((target) => { @@ -235,55 +228,7 @@ export function web(options: TimeAnimationOptions & AnimationExtraOptions, dom?: easing: 'linear', duration: duration + iterationDelay, custom, - get keyframes() { - // Calculate the timing offset for keyframes - const toDurationOffset = - GET_OFFSET_BY_DIRECTION_MAP[direction]({ - left, - top, - width, - height, - parentWidth, - parentHeight, - }) * timingFactor; - - let from, to; - if (direction in TRANSLATE_BY_DIRECTION_MAP) { - from = - TRANSLATE_BY_DIRECTION_MAP[direction as keyof typeof TRANSLATE_BY_DIRECTION_MAP].from; - to = TRANSLATE_BY_DIRECTION_MAP[direction as keyof typeof TRANSLATE_BY_DIRECTION_MAP].to; - } else { - const cornerFromTo = generateTranslate( - direction as keyof typeof FOUR_CORNERS_TRANSLATIONS, - ); - from = cornerFromTo.from; - to = cornerFromTo.to; - } - - return [ - { - offset: 0, - translate: '0 0', - }, - { - offset: toDurationOffset, - translate: to, - easing: 'step-start', - }, - { - offset: toDurationOffset, - translate: from, - }, - { - offset: timingFactor, - translate: '0 0', - }, - { - offset: 1, - translate: '0 0', - }, - ]; - }, + keyframes, }, ]; } diff --git a/packages/motion-presets/src/library/ongoing/Flip.ts b/packages/motion-presets/src/library/ongoing/Flip.ts index 90133b58..063079ae 100644 --- a/packages/motion-presets/src/library/ongoing/Flip.ts +++ b/packages/motion-presets/src/library/ongoing/Flip.ts @@ -18,6 +18,7 @@ export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom? } export function style(options: TimeAnimationOptions & AnimationExtraOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as Flip; const direction = parseDirection<(typeof AXIS_DIRECTIONS)[number]>( namedEffect?.direction, @@ -35,22 +36,22 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW const easing = options.easing || 'linear'; const custom = { - '--motion-perspective': `${perspective}px`, - '--motion-rotate-x': rotationAxes.x, - '--motion-rotate-y': rotationAxes.y, + [`--motion-perspective${suffix}`]: `${perspective}px`, + [`--motion-rotate-x${suffix}`]: rotationAxes.x, + [`--motion-rotate-y${suffix}`]: rotationAxes.y, }; const rotateStart = `rotate3d(${toKeyframeValue( custom, - '--motion-rotate-x', + `--motion-rotate-x${suffix}`, asWeb, - )}, ${toKeyframeValue(custom, '--motion-rotate-y', asWeb)}, 0, 0deg)`; + )}, ${toKeyframeValue(custom, `--motion-rotate-y${suffix}`, asWeb)}, 0, 0deg)`; const rotateEnd = `rotate3d(${toKeyframeValue( custom, - '--motion-rotate-x', + `--motion-rotate-x${suffix}`, asWeb, - )}, ${toKeyframeValue(custom, '--motion-rotate-y', asWeb)}, 0, 360deg)`; + )}, ${toKeyframeValue(custom, `--motion-rotate-y${suffix}`, asWeb)}, 0, 360deg)`; return [ { @@ -62,16 +63,16 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW keyframes: [ { offset: 0, - transform: `perspective(${toKeyframeValue(custom, '--motion-perspective', asWeb)}) rotateZ(var(--motion-rotate, 0deg)) ${rotateStart}`, + transform: `perspective(${toKeyframeValue(custom, `--motion-perspective${suffix}`, asWeb)}) rotateZ(var(--motion-rotate, 0deg)) ${rotateStart}`, easing: getEasing(easing), }, { offset, - transform: `perspective(${toKeyframeValue(custom, '--motion-perspective', asWeb)}) rotateZ(var(--motion-rotate, 0deg)) ${rotateEnd}`, + transform: `perspective(${toKeyframeValue(custom, `--motion-perspective${suffix}`, asWeb)}) rotateZ(var(--motion-rotate, 0deg)) ${rotateEnd}`, }, { offset: 1, - transform: `perspective(${toKeyframeValue(custom, '--motion-perspective', asWeb)}) rotateZ(var(--motion-rotate, 0deg)) ${rotateEnd}`, + transform: `perspective(${toKeyframeValue(custom, `--motion-perspective${suffix}`, asWeb)}) rotateZ(var(--motion-rotate, 0deg)) ${rotateEnd}`, }, ], }, diff --git a/packages/motion-presets/src/library/ongoing/Fold.ts b/packages/motion-presets/src/library/ongoing/Fold.ts index 29622822..2536752e 100644 --- a/packages/motion-presets/src/library/ongoing/Fold.ts +++ b/packages/motion-presets/src/library/ongoing/Fold.ts @@ -55,6 +55,7 @@ export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom? } export function style(options: TimeAnimationOptions & AnimationExtraOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as Fold; const direction = parseDirection( namedEffect.pivot, @@ -77,56 +78,59 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW // Create CSS custom properties for the fold configuration const custom: Record = { - '--motion-origin-x': `${x}%`, - '--motion-origin-y': `${y}%`, - '--motion-rotate-angle': `${angle}deg`, - '--motion-rotate-x': `${rotation.x}`, - '--motion-rotate-y': `${rotation.y}`, + [`--motion-origin-x${suffix}`]: `${x}%`, + [`--motion-origin-y${suffix}`]: `${y}%`, + [`--motion-rotate-angle${suffix}`]: `${angle}deg`, + [`--motion-rotate-x${suffix}`]: `${rotation.x}`, + [`--motion-rotate-y${suffix}`]: `${rotation.y}`, + [`--motion-perspective${suffix}`]: `${perspective}px`, }; const transformLeft = `rotateZ(var(--motion-rotate, 0deg)) translateX(${toKeyframeValue( custom, - '--motion-origin-x', + `--motion-origin-x${suffix}`, asWeb, - )}) translateY(${toKeyframeValue(custom, '--motion-origin-y', asWeb)}) perspective(${perspective}px)`; + )}) translateY(${toKeyframeValue(custom, `--motion-origin-y${suffix}`, asWeb)}) perspective(${toKeyframeValue(custom, `--motion-perspective${suffix}`, asWeb)})`; const transformRight = `translateX(calc(-1 * ${toKeyframeValue( custom, - '--motion-origin-x', + `--motion-origin-x${suffix}`, asWeb, - )})) translateY(calc(-1 * ${toKeyframeValue(custom, '--motion-origin-y', asWeb)}))`; + )})) translateY(calc(-1 * ${toKeyframeValue(custom, `--motion-origin-y${suffix}`, asWeb)}))`; + const angle_ = toKeyframeValue(custom, `--motion-rotate-angle${suffix}`, asWeb); const getTransform = (value: number) => `${transformLeft} rotateX(calc(${toKeyframeValue( custom, - '--motion-rotate-x', + `--motion-rotate-x${suffix}`, asWeb, - )} * ${value} * ${angle}deg)) rotateY(calc(${toKeyframeValue( + )} * ${value} * ${angle_})) rotateY(calc(${toKeyframeValue( custom, - '--motion-rotate-y', + `--motion-rotate-y${suffix}`, asWeb, - )} * ${value} * ${angle}deg)) ${transformRight}`; - - const keyframes = iterationDelay - ? KEYFRAME_FACTORS.map(({ fold, frameFactor }) => { - const keyframeOffset = frameFactor * timingFactor; - return { - offset: keyframeOffset, - easing: getEasing('sineInOut'), - transform: getTransform(fold), - }; - }) - : [ - { - offset: 0.25, - easing: getEasing(ease.inOut), - transform: getTransform(1), - }, - { - offset: 0.75, - easing: getEasing(ease.in), - transform: getTransform(-1), - }, - ]; + )} * ${value} * ${angle_})) ${transformRight}`; + + const keyframes = + timingFactor < 1 + ? KEYFRAME_FACTORS.map(({ fold, frameFactor }) => { + const keyframeOffset = frameFactor * timingFactor; + return { + offset: keyframeOffset, + easing: getEasing('sineInOut'), + transform: getTransform(fold), + }; + }) + : [ + { + offset: 0.25, + easing: getEasing(ease.inOut), + transform: getTransform(1), + }, + { + offset: 0.75, + easing: getEasing(ease.in), + transform: getTransform(-1), + }, + ]; const transform_0 = getTransform(0); diff --git a/packages/motion-presets/src/library/ongoing/Jello.ts b/packages/motion-presets/src/library/ongoing/Jello.ts index 3938ef18..af81de9c 100644 --- a/packages/motion-presets/src/library/ongoing/Jello.ts +++ b/packages/motion-presets/src/library/ongoing/Jello.ts @@ -21,6 +21,7 @@ export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom? } export function style(options: TimeAnimationOptions & AnimationExtraOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as Jello; const { skew = schema.skew.default } = namedEffect; @@ -33,7 +34,7 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW // Create CSS custom properties for the jello configuration const custom: Record = { - '--motion-skew-y': jelloFactor, + [`--motion-skew-y${suffix}`]: jelloFactor, }; const keyframes = SKEW_Y_KEYFRAMES.map(({ keyframe, skewY }) => { @@ -43,7 +44,7 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW offset, transform: `rotateZ(var(--motion-rotate, 0deg)) skewY(calc(${toKeyframeValue( custom, - '--motion-skew-y', + `--motion-skew-y${suffix}`, asWeb, )} * ${skewY}deg))`, }; diff --git a/packages/motion-presets/src/library/ongoing/Poke.ts b/packages/motion-presets/src/library/ongoing/Poke.ts index 70528088..513b6716 100644 --- a/packages/motion-presets/src/library/ongoing/Poke.ts +++ b/packages/motion-presets/src/library/ongoing/Poke.ts @@ -6,11 +6,12 @@ import type { EffectFourDirections, } from '../../types'; import { - getCssUnits, + scaleLength, + roundNumber, getTimingFactor, toKeyframeValue, parseDirection, - parseLength, + parseLengthOrCalc, } from '../../utils'; import { FOUR_DIRECTIONS } from '../../consts'; @@ -39,19 +40,21 @@ const DIRECTION_MAP = { left: { x: -1, y: 0 }, }; +const roundUnit = (value: number) => roundNumber(value, 6); + export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom?: DomApi) { return style(options, true); } export function style(options: TimeAnimationOptions & AnimationExtraOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as Poke; const direction = parseDirection( namedEffect?.direction, FOUR_DIRECTIONS, schema.direction.default, ); - const travel = parseLength(namedEffect.travel, schema.travel.default); - const unit = getCssUnits(travel.unit); + const travel = parseLengthOrCalc(namedEffect.travel, schema.travel.default); const duration = options.duration || 1; const iterationDelay = +(namedEffect?.iterationDelay || schema.iterationDelay.default); @@ -59,24 +62,26 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW const timingFactor = getTimingFactor(duration, iterationDelay) as number; const [name] = getNames(options); - const pokeFactor = travel.value / PEAK_TRANSLATE; + const pokeUnit = scaleLength(travel, 1 / PEAK_TRANSLATE, roundUnit); // Create CSS custom properties for the poke configuration const custom: Record = { - '--motion-translate-x': x * pokeFactor, - '--motion-translate-y': y * pokeFactor, + [`--motion-translate-x${suffix}`]: x, + [`--motion-translate-y${suffix}`]: y, + [`--motion-poke-unit${suffix}`]: pokeUnit, }; + const pokeUnit_ = toKeyframeValue(custom, `--motion-poke-unit${suffix}`, asWeb); const keyframes = TRANSLATE_KEYFRAMES.map(({ keyframe, translate }) => { const translateValue = `calc(${toKeyframeValue( custom, - '--motion-translate-x', + `--motion-translate-x${suffix}`, asWeb, - )} * ${translate}${unit}) calc(${toKeyframeValue( + )} * ${translate} * ${pokeUnit_}) calc(${toKeyframeValue( custom, - '--motion-translate-y', + `--motion-translate-y${suffix}`, asWeb, - )} * ${translate}${unit})`; + )} * ${translate} * ${pokeUnit_})`; return { offset: (keyframe / 100) * timingFactor, diff --git a/packages/motion-presets/src/library/ongoing/Pulse.ts b/packages/motion-presets/src/library/ongoing/Pulse.ts index a0e23da6..8148f13d 100644 --- a/packages/motion-presets/src/library/ongoing/Pulse.ts +++ b/packages/motion-presets/src/library/ongoing/Pulse.ts @@ -18,6 +18,7 @@ export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom? } export function style(options: TimeAnimationOptions & AnimationExtraOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as Pulse; const { scale: minScale = schema.scale.default } = namedEffect; @@ -30,7 +31,7 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW // Create CSS custom properties for the pulse configuration const custom: Record = { - '--motion-pulse-offset': pulseOffset, + [`--motion-pulse-offset${suffix}`]: pulseOffset, }; const keyframes = SCALE_KEYFRAMES.map(({ keyframe, scale }) => { @@ -40,7 +41,7 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW offset, transform: `scale(${ scale < 1 - ? `calc(${scale} - ${toKeyframeValue(custom, '--motion-pulse-offset', asWeb)})` + ? `calc(${scale} - ${toKeyframeValue(custom, `--motion-pulse-offset${suffix}`, asWeb)})` : '1' })`, }; diff --git a/packages/motion-presets/src/library/ongoing/Rubber.ts b/packages/motion-presets/src/library/ongoing/Rubber.ts index 06922484..f8fa496a 100644 --- a/packages/motion-presets/src/library/ongoing/Rubber.ts +++ b/packages/motion-presets/src/library/ongoing/Rubber.ts @@ -22,6 +22,7 @@ export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom? } export function style(options: TimeAnimationOptions & AnimationExtraOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as Rubber; const { stretch = schema.stretch.default } = namedEffect; @@ -44,8 +45,8 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW const adjustedScaleY = roundNumber(scaleY - offset, 4); // Create custom property keys for this keyframe - const scaleXKey = `--motion-scale-x-${keyframe}`; - const scaleYKey = `--motion-scale-y-${keyframe}`; + const scaleXKey = `--motion-scale-x${suffix}-${keyframe}`; + const scaleYKey = `--motion-scale-y${suffix}-${keyframe}`; // Add the scale values to custom properties custom[scaleXKey] = adjustedScaleX; diff --git a/packages/motion-presets/src/library/ongoing/Spin.ts b/packages/motion-presets/src/library/ongoing/Spin.ts index cc20cac0..0d4134a0 100644 --- a/packages/motion-presets/src/library/ongoing/Spin.ts +++ b/packages/motion-presets/src/library/ongoing/Spin.ts @@ -17,6 +17,7 @@ export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom? } export function style(options: TimeAnimationOptions & AnimationExtraOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as Spin; const direction = parseDirection<(typeof SPIN_DIRECTIONS)[number]>( namedEffect?.direction, @@ -34,7 +35,7 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW const transformRotate = (DIRECTION_MAP[direction] > 0 ? 1 : -1) * 360; const custom = { - '--motion-rotate-start': `calc(var(--motion-rotate, 0deg) + ${transformRotate}deg)`, + [`--motion-rotate-start${suffix}`]: `calc(var(--motion-rotate, 0deg) + ${transformRotate}deg)`, }; return [ @@ -48,7 +49,7 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW { offset: 0, easing: getEasing(easing), - rotate: toKeyframeValue(custom, '--motion-rotate-start', asWeb), + rotate: toKeyframeValue(custom, `--motion-rotate-start${suffix}`, asWeb), }, { offset: timingFactor, diff --git a/packages/motion-presets/src/library/ongoing/Swing.ts b/packages/motion-presets/src/library/ongoing/Swing.ts index 945355bf..bd8d7662 100644 --- a/packages/motion-presets/src/library/ongoing/Swing.ts +++ b/packages/motion-presets/src/library/ongoing/Swing.ts @@ -44,6 +44,7 @@ export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom? } export function style(options: TimeAnimationOptions & AnimationExtraOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as Swing; const direction = parseDirection( namedEffect?.pivot, @@ -64,60 +65,61 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW // Create CSS custom properties for the swing configuration const custom: Record = { - '--motion-swing-deg': `${swing}deg`, - '--motion-trans-x': `${x * TRANSLATE_DISTANCE}%`, - '--motion-trans-y': `${y * TRANSLATE_DISTANCE}%`, - '--motion-ease-in': getEasing(ease.in), - '--motion-ease-inout': getEasing(ease.inOut), - '--motion-ease-out': getEasing(ease.out), + [`--motion-swing-deg${suffix}`]: `${swing}deg`, + [`--motion-trans-x${suffix}`]: `${x * TRANSLATE_DISTANCE}%`, + [`--motion-trans-y${suffix}`]: `${y * TRANSLATE_DISTANCE}%`, + [`--motion-ease-in${suffix}`]: getEasing(ease.in), + [`--motion-ease-inout${suffix}`]: getEasing(ease.inOut), + [`--motion-ease-out${suffix}`]: getEasing(ease.out), }; const translateBefore = `translate(${toKeyframeValue( custom, - '--motion-trans-x', + `--motion-trans-x${suffix}`, asWeb, - )}, ${toKeyframeValue(custom, '--motion-trans-y', asWeb)})`; + )}, ${toKeyframeValue(custom, `--motion-trans-y${suffix}`, asWeb)})`; const translateAfter = `translate(calc(${toKeyframeValue( custom, - '--motion-trans-x', + `--motion-trans-x${suffix}`, asWeb, - )} * -1), calc(${toKeyframeValue(custom, '--motion-trans-y', asWeb)} * -1))`; - - const keyframes = iterationDelay - ? FACTORS_SEQUENCE.map(({ factor, timeFactor }) => { - const keyframeOffset = timeFactor * timingFactor; - - return { - offset: keyframeOffset, - easing: toKeyframeValue(custom, '--motion-ease-inout', asWeb), - transform: `rotate(var(--motion-rotate, 0deg)) ${translateBefore} rotate(calc(${toKeyframeValue( - custom, - '--motion-swing-deg', - asWeb, - )} * ${factor})) ${translateAfter}`, - }; - }) - : [ - { - offset: 0.25, - easing: toKeyframeValue(custom, '--motion-ease-inout', asWeb), - transform: `rotate(var(--motion-rotate, 0deg)) ${translateBefore} rotate(${toKeyframeValue( - custom, - '--motion-swing-deg', - asWeb, - )}) ${translateAfter}`, - }, - { - offset: 0.75, - easing: toKeyframeValue(custom, '--motion-ease-in', asWeb), - transform: `rotate(var(--motion-rotate, 0deg)) ${translateBefore} rotate(calc(${toKeyframeValue( - custom, - '--motion-swing-deg', - asWeb, - )} * -1)) ${translateAfter}`, - }, - ]; + )} * -1), calc(${toKeyframeValue(custom, `--motion-trans-y${suffix}`, asWeb)} * -1))`; + + const keyframes = + timingFactor < 1 + ? FACTORS_SEQUENCE.map(({ factor, timeFactor }) => { + const keyframeOffset = timeFactor * timingFactor; + + return { + offset: keyframeOffset, + easing: toKeyframeValue(custom, `--motion-ease-inout${suffix}`, asWeb), + transform: `rotate(var(--motion-rotate, 0deg)) ${translateBefore} rotate(calc(${toKeyframeValue( + custom, + `--motion-swing-deg${suffix}`, + asWeb, + )} * ${factor})) ${translateAfter}`, + }; + }) + : [ + { + offset: 0.25, + easing: toKeyframeValue(custom, `--motion-ease-inout${suffix}`, asWeb), + transform: `rotate(var(--motion-rotate, 0deg)) ${translateBefore} rotate(${toKeyframeValue( + custom, + `--motion-swing-deg${suffix}`, + asWeb, + )}) ${translateAfter}`, + }, + { + offset: 0.75, + easing: toKeyframeValue(custom, `--motion-ease-in${suffix}`, asWeb), + transform: `rotate(var(--motion-rotate, 0deg)) ${translateBefore} rotate(calc(${toKeyframeValue( + custom, + `--motion-swing-deg${suffix}`, + asWeb, + )} * -1)) ${translateAfter}`, + }, + ]; return [ { @@ -129,7 +131,7 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW keyframes: [ { offset: 0, - easing: toKeyframeValue(custom, '--motion-ease-out', asWeb), + easing: toKeyframeValue(custom, `--motion-ease-out${suffix}`, asWeb), transform: `rotateZ(var(--motion-rotate, 0deg)) ${translateBefore} rotate(0deg) ${translateAfter}`, }, ...keyframes, diff --git a/packages/motion-presets/src/library/ongoing/Wiggle.ts b/packages/motion-presets/src/library/ongoing/Wiggle.ts index 6948d0de..5bb7f765 100644 --- a/packages/motion-presets/src/library/ongoing/Wiggle.ts +++ b/packages/motion-presets/src/library/ongoing/Wiggle.ts @@ -4,7 +4,8 @@ import { getTimingFactor, roundNumber, toKeyframeValue, - parseLength, + parseLengthOrCalc, + scaleLength, } from '../../utils'; const PEAK_ROTATE = 10; @@ -29,22 +30,22 @@ export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom? } export function style(options: TimeAnimationOptions & AnimationExtraOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as Wiggle; const { angle = schema.angle.default } = namedEffect; - const travel = parseLength(namedEffect.travel, schema.travel.default); + const travel = parseLengthOrCalc(namedEffect.travel, schema.travel.default); const duration = options.duration || 1; const iterationDelay = namedEffect?.iterationDelay || schema.iterationDelay.default; const timingFactor = getTimingFactor(duration, iterationDelay) as number; const [name] = getNames(options); const wiggleFactor = angle / PEAK_ROTATE; - const translateFactor = travel.value / PEAK_TRANSLATE; let currentRotation = 0; // Create CSS custom properties for the wiggle configuration const custom: Record = { - '--motion-wiggle-factor': wiggleFactor, + [`--motion-wiggle-factor${suffix}`]: wiggleFactor, }; const keyframes = TRANSFORM_KEYFRAMES.map(({ keyframe, transY, accRotate }) => { @@ -52,10 +53,13 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW const rotateValue = `calc(var(--motion-rotate, 0deg) + ${roundNumber( currentRotation + accRotate * wiggleFactor, )}deg)`; - const translateYValue = `${transY * translateFactor}${getCssUnits(travel.unit)}`; + const translateYValue = + typeof travel === 'string' + ? scaleLength(travel, transY / PEAK_TRANSLATE) + : `${transY * (travel.value / PEAK_TRANSLATE)}${getCssUnits(travel.unit)}`; - const rotateKey = `--motion-rotate-${keyframe}`; - const translateYKey = `--motion-translate-y-${keyframe}`; + const rotateKey = `--motion-rotate${suffix}-${keyframe}`; + const translateYKey = `--motion-translate-y${suffix}-${keyframe}`; // For non-web usage, add the values to custom properties custom[rotateKey] = rotateValue; diff --git a/packages/motion-presets/src/library/ongoing/test/Bounce.spec.ts b/packages/motion-presets/src/library/ongoing/test/Bounce.spec.ts index 660a71f9..30b55591 100644 --- a/packages/motion-presets/src/library/ongoing/test/Bounce.spec.ts +++ b/packages/motion-presets/src/library/ongoing/test/Bounce.spec.ts @@ -20,72 +20,72 @@ describe('Bounce', () => { easing: 'linear', duration: 1, custom: { - '--motion-bounce-factor': 1, + '--motion-bounce-unit': '1px', }, keyframes: [ { offset: 0, - translate: '0px calc(0px * var(--motion-bounce-factor))', + translate: '0px calc(0 * var(--motion-bounce-unit))', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.08800000000000001, - translate: '0px calc(-27.5px * var(--motion-bounce-factor))', + translate: '0px calc(-27.5 * var(--motion-bounce-unit))', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.17600000000000002, - translate: '0px calc(-43.5px * var(--motion-bounce-factor))', + translate: '0px calc(-43.5 * var(--motion-bounce-unit))', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.265, - translate: '0px calc(-49px * var(--motion-bounce-factor))', + translate: '0px calc(-49 * var(--motion-bounce-unit))', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.353, - translate: '0px calc(-43.5px * var(--motion-bounce-factor))', + translate: '0px calc(-43.5 * var(--motion-bounce-unit))', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.441, - translate: '0px calc(-27.5px * var(--motion-bounce-factor))', + translate: '0px calc(-27.5 * var(--motion-bounce-unit))', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.531, - translate: '0px calc(0px * var(--motion-bounce-factor))', + translate: '0px calc(0 * var(--motion-bounce-unit))', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.662, - translate: '0px calc(-11.5px * var(--motion-bounce-factor))', + translate: '0px calc(-11.5 * var(--motion-bounce-unit))', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.81, - translate: '0px calc(0px * var(--motion-bounce-factor))', + translate: '0px calc(0 * var(--motion-bounce-unit))', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.868, - translate: '0px calc(-2.5px * var(--motion-bounce-factor))', + translate: '0px calc(-2.5 * var(--motion-bounce-unit))', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.941, - translate: '0px calc(0px * var(--motion-bounce-factor))', + translate: '0px calc(0 * var(--motion-bounce-unit))', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.971, - translate: '0px calc(-1px * var(--motion-bounce-factor))', + translate: '0px calc(-1 * var(--motion-bounce-unit))', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 1, - translate: '0px calc(0px * var(--motion-bounce-factor))', + translate: '0px calc(0 * var(--motion-bounce-unit))', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, ], @@ -112,72 +112,72 @@ describe('Bounce', () => { name: 'motion-bounce-067', easing: 'linear', custom: { - '--motion-bounce-factor': 2.6, + '--motion-bounce-unit': '2.6px', }, keyframes: [ { offset: 0, - translate: '0px calc(0px * var(--motion-bounce-factor))', + translate: '0px calc(0 * var(--motion-bounce-unit))', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.05896000000000001, - translate: '0px calc(-27.5px * var(--motion-bounce-factor))', + translate: '0px calc(-27.5 * var(--motion-bounce-unit))', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.11792000000000002, - translate: '0px calc(-43.5px * var(--motion-bounce-factor))', + translate: '0px calc(-43.5 * var(--motion-bounce-unit))', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.17755, - translate: '0px calc(-49px * var(--motion-bounce-factor))', + translate: '0px calc(-49 * var(--motion-bounce-unit))', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.23651, - translate: '0px calc(-43.5px * var(--motion-bounce-factor))', + translate: '0px calc(-43.5 * var(--motion-bounce-unit))', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.29547, - translate: '0px calc(-27.5px * var(--motion-bounce-factor))', + translate: '0px calc(-27.5 * var(--motion-bounce-unit))', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.35577000000000003, - translate: '0px calc(0px * var(--motion-bounce-factor))', + translate: '0px calc(0 * var(--motion-bounce-unit))', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.44354000000000005, - translate: '0px calc(-11.5px * var(--motion-bounce-factor))', + translate: '0px calc(-11.5 * var(--motion-bounce-unit))', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.5427000000000001, - translate: '0px calc(0px * var(--motion-bounce-factor))', + translate: '0px calc(0 * var(--motion-bounce-unit))', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.5815600000000001, - translate: '0px calc(-2.5px * var(--motion-bounce-factor))', + translate: '0px calc(-2.5 * var(--motion-bounce-unit))', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.63047, - translate: '0px calc(0px * var(--motion-bounce-factor))', + translate: '0px calc(0 * var(--motion-bounce-unit))', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.65057, - translate: '0px calc(-1px * var(--motion-bounce-factor))', + translate: '0px calc(-1 * var(--motion-bounce-unit))', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.67, - translate: '0px calc(0px * var(--motion-bounce-factor))', + translate: '0px calc(0 * var(--motion-bounce-unit))', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, ], @@ -205,72 +205,72 @@ describe('Bounce', () => { easing: 'linear', duration: 1, custom: { - '--motion-bounce-factor': 1, + '--motion-bounce-unit': '1px', }, keyframes: [ { offset: 0, - translate: '0px calc(0px * 1)', + translate: '0px calc(0 * 1px)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.08800000000000001, - translate: '0px calc(-27.5px * 1)', + translate: '0px calc(-27.5 * 1px)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.17600000000000002, - translate: '0px calc(-43.5px * 1)', + translate: '0px calc(-43.5 * 1px)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.265, - translate: '0px calc(-49px * 1)', + translate: '0px calc(-49 * 1px)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.353, - translate: '0px calc(-43.5px * 1)', + translate: '0px calc(-43.5 * 1px)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.441, - translate: '0px calc(-27.5px * 1)', + translate: '0px calc(-27.5 * 1px)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.531, - translate: '0px calc(0px * 1)', + translate: '0px calc(0 * 1px)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.662, - translate: '0px calc(-11.5px * 1)', + translate: '0px calc(-11.5 * 1px)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.81, - translate: '0px calc(0px * 1)', + translate: '0px calc(0 * 1px)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.868, - translate: '0px calc(-2.5px * 1)', + translate: '0px calc(-2.5 * 1px)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.941, - translate: '0px calc(0px * 1)', + translate: '0px calc(0 * 1px)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.971, - translate: '0px calc(-1px * 1)', + translate: '0px calc(-1 * 1px)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 1, - translate: '0px calc(0px * 1)', + translate: '0px calc(0 * 1px)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, ], @@ -297,72 +297,72 @@ describe('Bounce', () => { name: 'motion-bounce-067', easing: 'linear', custom: { - '--motion-bounce-factor': 2.6, + '--motion-bounce-unit': '2.6px', }, keyframes: [ { offset: 0, - translate: '0px calc(0px * 2.6)', + translate: '0px calc(0 * 2.6px)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.05896000000000001, - translate: '0px calc(-27.5px * 2.6)', + translate: '0px calc(-27.5 * 2.6px)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.11792000000000002, - translate: '0px calc(-43.5px * 2.6)', + translate: '0px calc(-43.5 * 2.6px)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.17755, - translate: '0px calc(-49px * 2.6)', + translate: '0px calc(-49 * 2.6px)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.23651, - translate: '0px calc(-43.5px * 2.6)', + translate: '0px calc(-43.5 * 2.6px)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.29547, - translate: '0px calc(-27.5px * 2.6)', + translate: '0px calc(-27.5 * 2.6px)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.35577000000000003, - translate: '0px calc(0px * 2.6)', + translate: '0px calc(0 * 2.6px)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.44354000000000005, - translate: '0px calc(-11.5px * 2.6)', + translate: '0px calc(-11.5 * 2.6px)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.5427000000000001, - translate: '0px calc(0px * 2.6)', + translate: '0px calc(0 * 2.6px)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.5815600000000001, - translate: '0px calc(-2.5px * 2.6)', + translate: '0px calc(-2.5 * 2.6px)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.63047, - translate: '0px calc(0px * 2.6)', + translate: '0px calc(0 * 2.6px)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.65057, - translate: '0px calc(-1px * 2.6)', + translate: '0px calc(-1 * 2.6px)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.67, - translate: '0px calc(0px * 2.6)', + translate: '0px calc(0 * 2.6px)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, ], diff --git a/packages/motion-presets/src/library/ongoing/test/Cross.spec.ts b/packages/motion-presets/src/library/ongoing/test/Cross.spec.ts index 0f5922d6..dec1485c 100644 --- a/packages/motion-presets/src/library/ongoing/test/Cross.spec.ts +++ b/packages/motion-presets/src/library/ongoing/test/Cross.spec.ts @@ -1,7 +1,12 @@ import { describe, expect, test } from 'vitest'; import * as Cross from '../Cross'; -import { Cross as CrossType, TimeAnimationOptions } from '../../../types'; +import { + Cross as CrossType, + DomApi, + EffectEightDirections, + TimeAnimationOptions, +} from '../../../types'; import { baseMockOptions } from './testUtils'; describe('Cross', () => { @@ -619,4 +624,109 @@ describe('Cross', () => { expect(result).toMatchObject(expectedResult); }); }); + + describe('measured', () => { + const layout = { left: 200, top: 100, width: 100, height: 50 }; + const parentSize = { width: 1000, height: 800 }; + + function createDom() { + const properties: Record = {}; + const parent = { offsetParent: null, getBoundingClientRect: () => parentSize }; + const target = { + offsetLeft: layout.left, + offsetTop: layout.top, + offsetParent: parent, + getBoundingClientRect: () => ({ width: layout.width, height: layout.height }), + style: { setProperty: (name: string, value: string) => (properties[name] = value) }, + } as unknown as HTMLElement; + const measures: ((target: HTMLElement | null) => void)[] = []; + const mutations: ((target: HTMLElement | null) => void)[] = []; + const dom: DomApi = { + measure: (fn) => measures.push(fn), + mutate: (fn) => mutations.push(fn), + }; + const flush = () => { + measures.forEach((fn) => fn(target)); + mutations.forEach((fn) => fn(target)); + }; + return { dom, flush, properties }; + } + + function evaluateLength(value: string, properties: Record) { + const expression = value + .replace(/var\((--[\w-]+), [^()]+\)/g, (_, name) => `${parseFloat(properties[name])}`) + .replace(/min\(/g, 'Math.min(') + .replace(/calc\(/g, '('); + return new Function(`return ${expression}`)() as number; + } + + function evaluateTranslate(translate: string, properties: Record) { + let depth = 0; + const split = [...translate].findIndex((char) => { + depth += char === '(' ? 1 : char === ')' ? -1 : 0; + return char === ' ' && depth === 0; + }); + return [translate.slice(0, split), translate.slice(split + 1)].map((value) => + evaluateLength(value, properties), + ); + } + + // the element's distances (px) to fully leave its 1000x800 parent through each side + const right = 800; + const left = 300; + const bottom = 700; + const top = 150; + + const cases: [EffectEightDirections, [number, number], [number, number]][] = [ + ['right', [right, 0], [-left, 0]], + ['left', [-left, 0], [right, 0]], + ['bottom', [0, bottom], [0, -top]], + ['top', [0, -top], [0, bottom]], + ['bottom-right', [bottom, bottom], [-top, -top]], + ['bottom-left', [-left, left], [top, -top]], + ['top-right', [top, -top], [-left, left]], + ['top-left', [-top, -top], [bottom, bottom]], + ]; + + test.each(cases)( + 'moves towards %s before wrapping around from the opposite side', + (direction, expectedTo, expectedFrom) => { + const { dom, flush, properties } = createDom(); + const [effect] = Cross.web( + { ...baseMockOptions, duration: 1000, namedEffect: { direction } as CrossType }, + dom, + ); + const { keyframes } = { ...effect }; + + flush(); + + const [, to, from] = keyframes; + const toDistance = Math.max(...expectedTo.map(Math.abs)); + const fromDistance = Math.max(...expectedFrom.map(Math.abs)); + + expect(to.offset).toBeCloseTo(toDistance / (toDistance + fromDistance)); + expect(from.offset).toBe(to.offset); + expect(evaluateTranslate(to.translate as string, properties)).toEqual(expectedTo); + expect(evaluateTranslate(from.translate as string, properties)).toEqual(expectedFrom); + }, + ); + + test('scales the wrap point by the iteration delay', () => { + const { dom, flush } = createDom(); + const [effect] = Cross.web( + { ...baseMockOptions, duration: 1000, namedEffect: { iterationDelay: 1000 } as CrossType }, + dom, + ); + + flush(); + + expect(effect.keyframes.map(({ offset }) => offset)).toEqual([ + 0, + (right / (right + left)) * 0.5, + (right / (right + left)) * 0.5, + 0.5, + 1, + ]); + }); + }); }); diff --git a/packages/motion-presets/src/library/ongoing/test/Fold.spec.ts b/packages/motion-presets/src/library/ongoing/test/Fold.spec.ts index 17058650..fddf70f7 100644 --- a/packages/motion-presets/src/library/ongoing/test/Fold.spec.ts +++ b/packages/motion-presets/src/library/ongoing/test/Fold.spec.ts @@ -360,24 +360,24 @@ describe('Fold', () => { easing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', offset: 0, transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(800px) rotateX(calc(var(--motion-rotate-x) * 0 * 15deg)) rotateY(calc(var(--motion-rotate-y) * 0 * 15deg)) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', + 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * 0 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * 0 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', }, { easing: 'cubic-bezier(0.645, 0.045, 0.355, 1)', offset: 0.25, transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(800px) rotateX(calc(var(--motion-rotate-x) * 1 * 15deg)) rotateY(calc(var(--motion-rotate-y) * 1 * 15deg)) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', + 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * 1 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * 1 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', }, { easing: 'cubic-bezier(0.55, 0.055, 0.675, 0.19)', offset: 0.75, transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(800px) rotateX(calc(var(--motion-rotate-x) * -1 * 15deg)) rotateY(calc(var(--motion-rotate-y) * -1 * 15deg)) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', + 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * -1 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * -1 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', }, { offset: 1, transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(800px) rotateX(calc(var(--motion-rotate-x) * 0 * 15deg)) rotateY(calc(var(--motion-rotate-y) * 0 * 15deg)) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', + 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * 0 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * 0 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', }, ], }, @@ -409,24 +409,24 @@ describe('Fold', () => { easing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', offset: 0, transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(800px) rotateX(calc(var(--motion-rotate-x) * 0 * 15deg)) rotateY(calc(var(--motion-rotate-y) * 0 * 15deg)) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', + 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * 0 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * 0 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', }, { easing: 'cubic-bezier(0.645, 0.045, 0.355, 1)', offset: 0.25, transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(800px) rotateX(calc(var(--motion-rotate-x) * 1 * 15deg)) rotateY(calc(var(--motion-rotate-y) * 1 * 15deg)) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', + 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * 1 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * 1 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', }, { easing: 'cubic-bezier(0.55, 0.055, 0.675, 0.19)', offset: 0.75, transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(800px) rotateX(calc(var(--motion-rotate-x) * -1 * 15deg)) rotateY(calc(var(--motion-rotate-y) * -1 * 15deg)) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', + 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * -1 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * -1 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', }, { offset: 1, transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(800px) rotateX(calc(var(--motion-rotate-x) * 0 * 15deg)) rotateY(calc(var(--motion-rotate-y) * 0 * 15deg)) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', + 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * 0 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * 0 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', }, ], }, @@ -463,54 +463,54 @@ describe('Fold', () => { easing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', offset: 0, transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(800px) rotateX(calc(var(--motion-rotate-x) * 0 * 15deg)) rotateY(calc(var(--motion-rotate-y) * 0 * 15deg)) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', + 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * 0 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * 0 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', }, { easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', offset: 0.067, transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(800px) rotateX(calc(var(--motion-rotate-x) * 1 * 15deg)) rotateY(calc(var(--motion-rotate-y) * 1 * 15deg)) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', + 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * 1 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * 1 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', }, { easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', offset: 0.20234, transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(800px) rotateX(calc(var(--motion-rotate-x) * -0.7 * 15deg)) rotateY(calc(var(--motion-rotate-y) * -0.7 * 15deg)) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', + 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * -0.7 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * -0.7 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', }, { easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', offset: 0.33768000000000004, transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(800px) rotateX(calc(var(--motion-rotate-x) * 0.6 * 15deg)) rotateY(calc(var(--motion-rotate-y) * 0.6 * 15deg)) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', + 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * 0.6 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * 0.6 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', }, { easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', offset: 0.4596200000000001, transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(800px) rotateX(calc(var(--motion-rotate-x) * -0.3 * 15deg)) rotateY(calc(var(--motion-rotate-y) * -0.3 * 15deg)) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', + 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * -0.3 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * -0.3 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', }, { easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', offset: 0.56749, transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(800px) rotateX(calc(var(--motion-rotate-x) * 0.2 * 15deg)) rotateY(calc(var(--motion-rotate-y) * 0.2 * 15deg)) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', + 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * 0.2 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * 0.2 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', }, { easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', offset: 0.70283, transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(800px) rotateX(calc(var(--motion-rotate-x) * -0.05 * 15deg)) rotateY(calc(var(--motion-rotate-y) * -0.05 * 15deg)) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', + 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * -0.05 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * -0.05 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', }, { easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', offset: 0.7966300000000001, transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(800px) rotateX(calc(var(--motion-rotate-x) * 0 * 15deg)) rotateY(calc(var(--motion-rotate-y) * 0 * 15deg)) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', + 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * 0 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * 0 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', }, { offset: 1, transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(800px) rotateX(calc(var(--motion-rotate-x) * 0 * 15deg)) rotateY(calc(var(--motion-rotate-y) * 0 * 15deg)) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', + 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * 0 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * 0 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', }, ], }, diff --git a/packages/motion-presets/src/library/ongoing/test/Poke.spec.ts b/packages/motion-presets/src/library/ongoing/test/Poke.spec.ts index b89e0d9b..9336da83 100644 --- a/packages/motion-presets/src/library/ongoing/test/Poke.spec.ts +++ b/packages/motion-presets/src/library/ongoing/test/Poke.spec.ts @@ -19,37 +19,38 @@ describe('Poke', () => { name: 'motion-poke-1', duration: 1000, custom: { - '--motion-translate-x': 2.5, + '--motion-translate-x': 1, '--motion-translate-y': 0, + '--motion-poke-unit': '2.5px', }, keyframes: [ { offset: 0.17, - translate: 'calc(2.5 * 7px) calc(0 * 7px)', + translate: 'calc(1 * 7 * 2.5px) calc(0 * 7 * 2.5px)', }, { offset: 0.32, - translate: 'calc(2.5 * 25px) calc(0 * 25px)', + translate: 'calc(1 * 25 * 2.5px) calc(0 * 25 * 2.5px)', }, { offset: 0.48, - translate: 'calc(2.5 * 8px) calc(0 * 8px)', + translate: 'calc(1 * 8 * 2.5px) calc(0 * 8 * 2.5px)', }, { offset: 0.56, - translate: 'calc(2.5 * 11px) calc(0 * 11px)', + translate: 'calc(1 * 11 * 2.5px) calc(0 * 11 * 2.5px)', }, { offset: 0.66, - translate: 'calc(2.5 * 25px) calc(0 * 25px)', + translate: 'calc(1 * 25 * 2.5px) calc(0 * 25 * 2.5px)', }, { offset: 0.83, - translate: 'calc(2.5 * 4px) calc(0 * 4px)', + translate: 'calc(1 * 4 * 2.5px) calc(0 * 4 * 2.5px)', }, { offset: 1, - translate: 'calc(2.5 * 0px) calc(0 * 0px)', + translate: 'calc(1 * 0 * 2.5px) calc(0 * 0 * 2.5px)', }, ], }, @@ -73,8 +74,9 @@ describe('Poke', () => { expect(result[0].name).toBe('motion-poke-067'); expect(result[0].duration).toBe(1500); expect(result[0].custom).toEqual({ - '--motion-translate-x': 2.5, + '--motion-translate-x': 1, '--motion-translate-y': 0, + '--motion-poke-unit': '2.5px', }); expect(result[0].keyframes).toHaveLength(7); // Check approximate offset values due to floating point precision @@ -93,8 +95,7 @@ describe('Poke', () => { expect(result[0].name).toBe('motion-poke-1'); // Check the intensity calculation with tolerance for floating point precision - expect(result[0].custom!['--motion-translate-x']).toBeCloseTo(3.4, 1); - expect(result[0].custom!['--motion-translate-y']).toBe(0); + expect(result[0].custom!['--motion-poke-unit']).toBe('3.4px'); expect(result[0].keyframes[0].translate).toContain('3.4'); }); @@ -110,36 +111,37 @@ describe('Poke', () => { name: 'motion-poke-1', custom: { '--motion-translate-x': 0, - '--motion-translate-y': -2.5, + '--motion-translate-y': -1, + '--motion-poke-unit': '2.5px', }, keyframes: [ { offset: 0.17, - translate: 'calc(0 * 7px) calc(-2.5 * 7px)', + translate: 'calc(0 * 7 * 2.5px) calc(-1 * 7 * 2.5px)', }, { offset: 0.32, - translate: 'calc(0 * 25px) calc(-2.5 * 25px)', + translate: 'calc(0 * 25 * 2.5px) calc(-1 * 25 * 2.5px)', }, { offset: 0.48, - translate: 'calc(0 * 8px) calc(-2.5 * 8px)', + translate: 'calc(0 * 8 * 2.5px) calc(-1 * 8 * 2.5px)', }, { offset: 0.56, - translate: 'calc(0 * 11px) calc(-2.5 * 11px)', + translate: 'calc(0 * 11 * 2.5px) calc(-1 * 11 * 2.5px)', }, { offset: 0.66, - translate: 'calc(0 * 25px) calc(-2.5 * 25px)', + translate: 'calc(0 * 25 * 2.5px) calc(-1 * 25 * 2.5px)', }, { offset: 0.83, - translate: 'calc(0 * 4px) calc(-2.5 * 4px)', + translate: 'calc(0 * 4 * 2.5px) calc(-1 * 4 * 2.5px)', }, { offset: 1, - translate: 'calc(0 * 0px) calc(-2.5 * 0px)', + translate: 'calc(0 * 0 * 2.5px) calc(-1 * 0 * 2.5px)', }, ], }, @@ -162,36 +164,37 @@ describe('Poke', () => { name: 'motion-poke-1', custom: { '--motion-translate-x': 0, - '--motion-translate-y': 2.5, + '--motion-translate-y': 1, + '--motion-poke-unit': '2.5px', }, keyframes: [ { offset: 0.17, - translate: 'calc(0 * 7px) calc(2.5 * 7px)', + translate: 'calc(0 * 7 * 2.5px) calc(1 * 7 * 2.5px)', }, { offset: 0.32, - translate: 'calc(0 * 25px) calc(2.5 * 25px)', + translate: 'calc(0 * 25 * 2.5px) calc(1 * 25 * 2.5px)', }, { offset: 0.48, - translate: 'calc(0 * 8px) calc(2.5 * 8px)', + translate: 'calc(0 * 8 * 2.5px) calc(1 * 8 * 2.5px)', }, { offset: 0.56, - translate: 'calc(0 * 11px) calc(2.5 * 11px)', + translate: 'calc(0 * 11 * 2.5px) calc(1 * 11 * 2.5px)', }, { offset: 0.66, - translate: 'calc(0 * 25px) calc(2.5 * 25px)', + translate: 'calc(0 * 25 * 2.5px) calc(1 * 25 * 2.5px)', }, { offset: 0.83, - translate: 'calc(0 * 4px) calc(2.5 * 4px)', + translate: 'calc(0 * 4 * 2.5px) calc(1 * 4 * 2.5px)', }, { offset: 1, - translate: 'calc(0 * 0px) calc(2.5 * 0px)', + translate: 'calc(0 * 0 * 2.5px) calc(1 * 0 * 2.5px)', }, ], }, @@ -213,37 +216,38 @@ describe('Poke', () => { { name: 'motion-poke-1', custom: { - '--motion-translate-x': -2.5, + '--motion-translate-x': -1, '--motion-translate-y': 0, + '--motion-poke-unit': '2.5px', }, keyframes: [ { offset: 0.17, - translate: 'calc(-2.5 * 7px) calc(0 * 7px)', + translate: 'calc(-1 * 7 * 2.5px) calc(0 * 7 * 2.5px)', }, { offset: 0.32, - translate: 'calc(-2.5 * 25px) calc(0 * 25px)', + translate: 'calc(-1 * 25 * 2.5px) calc(0 * 25 * 2.5px)', }, { offset: 0.48, - translate: 'calc(-2.5 * 8px) calc(0 * 8px)', + translate: 'calc(-1 * 8 * 2.5px) calc(0 * 8 * 2.5px)', }, { offset: 0.56, - translate: 'calc(-2.5 * 11px) calc(0 * 11px)', + translate: 'calc(-1 * 11 * 2.5px) calc(0 * 11 * 2.5px)', }, { offset: 0.66, - translate: 'calc(-2.5 * 25px) calc(0 * 25px)', + translate: 'calc(-1 * 25 * 2.5px) calc(0 * 25 * 2.5px)', }, { offset: 0.83, - translate: 'calc(-2.5 * 4px) calc(0 * 4px)', + translate: 'calc(-1 * 4 * 2.5px) calc(0 * 4 * 2.5px)', }, { offset: 1, - translate: 'calc(-2.5 * 0px) calc(0 * 0px)', + translate: 'calc(-1 * 0 * 2.5px) calc(0 * 0 * 2.5px)', }, ], }, @@ -269,44 +273,45 @@ describe('Poke', () => { name: 'motion-poke-1', duration: 1000, custom: { - '--motion-translate-x': 2.5, + '--motion-translate-x': 1, '--motion-translate-y': 0, + '--motion-poke-unit': '2.5px', }, keyframes: [ { offset: 0.17, translate: - 'calc(var(--motion-translate-x) * 7px) calc(var(--motion-translate-y) * 7px)', + 'calc(var(--motion-translate-x) * 7 * var(--motion-poke-unit)) calc(var(--motion-translate-y) * 7 * var(--motion-poke-unit))', }, { offset: 0.32, translate: - 'calc(var(--motion-translate-x) * 25px) calc(var(--motion-translate-y) * 25px)', + 'calc(var(--motion-translate-x) * 25 * var(--motion-poke-unit)) calc(var(--motion-translate-y) * 25 * var(--motion-poke-unit))', }, { offset: 0.48, translate: - 'calc(var(--motion-translate-x) * 8px) calc(var(--motion-translate-y) * 8px)', + 'calc(var(--motion-translate-x) * 8 * var(--motion-poke-unit)) calc(var(--motion-translate-y) * 8 * var(--motion-poke-unit))', }, { offset: 0.56, translate: - 'calc(var(--motion-translate-x) * 11px) calc(var(--motion-translate-y) * 11px)', + 'calc(var(--motion-translate-x) * 11 * var(--motion-poke-unit)) calc(var(--motion-translate-y) * 11 * var(--motion-poke-unit))', }, { offset: 0.66, translate: - 'calc(var(--motion-translate-x) * 25px) calc(var(--motion-translate-y) * 25px)', + 'calc(var(--motion-translate-x) * 25 * var(--motion-poke-unit)) calc(var(--motion-translate-y) * 25 * var(--motion-poke-unit))', }, { offset: 0.83, translate: - 'calc(var(--motion-translate-x) * 4px) calc(var(--motion-translate-y) * 4px)', + 'calc(var(--motion-translate-x) * 4 * var(--motion-poke-unit)) calc(var(--motion-translate-y) * 4 * var(--motion-poke-unit))', }, { offset: 1, translate: - 'calc(var(--motion-translate-x) * 0px) calc(var(--motion-translate-y) * 0px)', + 'calc(var(--motion-translate-x) * 0 * var(--motion-poke-unit)) calc(var(--motion-translate-y) * 0 * var(--motion-poke-unit))', }, ], }, @@ -330,8 +335,9 @@ describe('Poke', () => { expect(result[0].name).toBe('motion-poke-08'); expect(result[0].duration).toBe(1000); expect(result[0].custom).toEqual({ - '--motion-translate-x': 2.5, + '--motion-translate-x': 1, '--motion-translate-y': 0, + '--motion-poke-unit': '2.5px', }); expect(result[0].keyframes).toHaveLength(7); // Check approximate offset values due to floating point precision @@ -352,43 +358,44 @@ describe('Poke', () => { name: 'motion-poke-1', custom: { '--motion-translate-x': 0, - '--motion-translate-y': -2.5, + '--motion-translate-y': -1, + '--motion-poke-unit': '2.5px', }, keyframes: [ { offset: 0.17, translate: - 'calc(var(--motion-translate-x) * 7px) calc(var(--motion-translate-y) * 7px)', + 'calc(var(--motion-translate-x) * 7 * var(--motion-poke-unit)) calc(var(--motion-translate-y) * 7 * var(--motion-poke-unit))', }, { offset: 0.32, translate: - 'calc(var(--motion-translate-x) * 25px) calc(var(--motion-translate-y) * 25px)', + 'calc(var(--motion-translate-x) * 25 * var(--motion-poke-unit)) calc(var(--motion-translate-y) * 25 * var(--motion-poke-unit))', }, { offset: 0.48, translate: - 'calc(var(--motion-translate-x) * 8px) calc(var(--motion-translate-y) * 8px)', + 'calc(var(--motion-translate-x) * 8 * var(--motion-poke-unit)) calc(var(--motion-translate-y) * 8 * var(--motion-poke-unit))', }, { offset: 0.56, translate: - 'calc(var(--motion-translate-x) * 11px) calc(var(--motion-translate-y) * 11px)', + 'calc(var(--motion-translate-x) * 11 * var(--motion-poke-unit)) calc(var(--motion-translate-y) * 11 * var(--motion-poke-unit))', }, { offset: 0.66, translate: - 'calc(var(--motion-translate-x) * 25px) calc(var(--motion-translate-y) * 25px)', + 'calc(var(--motion-translate-x) * 25 * var(--motion-poke-unit)) calc(var(--motion-translate-y) * 25 * var(--motion-poke-unit))', }, { offset: 0.83, translate: - 'calc(var(--motion-translate-x) * 4px) calc(var(--motion-translate-y) * 4px)', + 'calc(var(--motion-translate-x) * 4 * var(--motion-poke-unit)) calc(var(--motion-translate-y) * 4 * var(--motion-poke-unit))', }, { offset: 1, translate: - 'calc(var(--motion-translate-x) * 0px) calc(var(--motion-translate-y) * 0px)', + 'calc(var(--motion-translate-x) * 0 * var(--motion-poke-unit)) calc(var(--motion-translate-y) * 0 * var(--motion-poke-unit))', }, ], }, diff --git a/packages/motion-presets/src/library/scroll/ArcScroll.ts b/packages/motion-presets/src/library/scroll/ArcScroll.ts index e9ff4642..7cf6e92f 100644 --- a/packages/motion-presets/src/library/scroll/ArcScroll.ts +++ b/packages/motion-presets/src/library/scroll/ArcScroll.ts @@ -24,6 +24,7 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { } export function style(options: ScrubAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as ArcScroll; const direction = parseDirection<(typeof AXIS_DIRECTIONS)[number]>( namedEffect?.direction, @@ -43,9 +44,9 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { const [arcScroll] = getNames(options); const custom = { - '--motion-perspective': `${perspective}px`, - '--motion-arc-from': `${rotateAxis}(${fromValue}deg)`, - '--motion-arc-to': `${rotateAxis}(${toValue}deg)`, + [`--motion-perspective${suffix}`]: `${perspective}px`, + [`--motion-arc-from${suffix}`]: `${rotateAxis}(${fromValue}deg)`, + [`--motion-arc-to${suffix}`]: `${rotateAxis}(${toValue}deg)`, }; return [ @@ -57,16 +58,16 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { custom, keyframes: [ { - transform: `perspective(${toKeyframeValue(custom, '--motion-perspective', asWeb)}) translateZ(-300px) ${toKeyframeValue( + transform: `perspective(${toKeyframeValue(custom, `--motion-perspective${suffix}`, asWeb)}) translateZ(-300px) ${toKeyframeValue( custom, - '--motion-arc-from', + `--motion-arc-from${suffix}`, asWeb, )} translateZ(300px) rotate(${toKeyframeValue({}, '--motion-rotate', false, '0deg')})`, }, { - transform: `perspective(${toKeyframeValue(custom, '--motion-perspective', asWeb)}) translateZ(-300px) ${toKeyframeValue( + transform: `perspective(${toKeyframeValue(custom, `--motion-perspective${suffix}`, asWeb)}) translateZ(-300px) ${toKeyframeValue( custom, - '--motion-arc-to', + `--motion-arc-to${suffix}`, asWeb, )} translateZ(300px) rotate(${toKeyframeValue({}, '--motion-rotate', false, '0deg')})`, }, diff --git a/packages/motion-presets/src/library/scroll/BlurScroll.ts b/packages/motion-presets/src/library/scroll/BlurScroll.ts index 76051884..343ff6d3 100644 --- a/packages/motion-presets/src/library/scroll/BlurScroll.ts +++ b/packages/motion-presets/src/library/scroll/BlurScroll.ts @@ -16,6 +16,7 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { } export function style(options: ScrubAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const { blur = schema.blur.default, range = schema.range.default } = options.namedEffect as BlurScroll; @@ -29,8 +30,8 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { const [blurScroll] = getNames(options); const custom = { - '--motion-blur-from': `${fromValue}px`, - '--motion-blur-to': `${toValue}px`, + [`--motion-blur-from${suffix}`]: `${fromValue}px`, + [`--motion-blur-to${suffix}`]: `${toValue}px`, }; return [ @@ -43,10 +44,10 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { custom, keyframes: [ { - filter: `blur(${toKeyframeValue(custom, '--motion-blur-from', asWeb)})`, + filter: `blur(${toKeyframeValue(custom, `--motion-blur-from${suffix}`, asWeb)})`, }, { - filter: `blur(${toKeyframeValue(custom, '--motion-blur-to', asWeb)})`, + filter: `blur(${toKeyframeValue(custom, `--motion-blur-to${suffix}`, asWeb)})`, }, ], }, diff --git a/packages/motion-presets/src/library/scroll/FadeScroll.ts b/packages/motion-presets/src/library/scroll/FadeScroll.ts index 0f274274..5bbd51ab 100644 --- a/packages/motion-presets/src/library/scroll/FadeScroll.ts +++ b/packages/motion-presets/src/library/scroll/FadeScroll.ts @@ -16,6 +16,7 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { } export function style(options: ScrubAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const { opacity = schema.opacity.default, range = schema.range.default } = options.namedEffect as FadeScroll; const isOut = range === 'out'; @@ -29,8 +30,8 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { const [fadeScroll] = getNames(options); const custom = { - '--motion-fade-from': fromValue, - '--motion-fade-to': toValue, + [`--motion-fade-from${suffix}`]: fromValue, + [`--motion-fade-to${suffix}`]: toValue, }; return [ @@ -42,10 +43,10 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { custom, keyframes: [ { - opacity: toKeyframeValue(custom, '--motion-fade-from', asWeb), + opacity: toKeyframeValue(custom, `--motion-fade-from${suffix}`, asWeb), }, { - opacity: toKeyframeValue(custom, '--motion-fade-to', asWeb), + opacity: toKeyframeValue(custom, `--motion-fade-to${suffix}`, asWeb), }, ], }, diff --git a/packages/motion-presets/src/library/scroll/FlipScroll.ts b/packages/motion-presets/src/library/scroll/FlipScroll.ts index de99128a..add77c94 100644 --- a/packages/motion-presets/src/library/scroll/FlipScroll.ts +++ b/packages/motion-presets/src/library/scroll/FlipScroll.ts @@ -23,6 +23,7 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { } export function style(options: ScrubAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as FlipScroll; const direction = parseDirection<(typeof AXIS_DIRECTIONS)[number]>( namedEffect?.direction, @@ -47,9 +48,9 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { const [flipScroll] = getNames(options); const custom = { - '--motion-perspective': `${perspective}px`, - '--motion-flip-from': `${rotationAxis}(${fromValue}deg)`, - '--motion-flip-to': `${rotationAxis}(${toValue}deg)`, + [`--motion-perspective${suffix}`]: `${perspective}px`, + [`--motion-flip-from${suffix}`]: `${rotationAxis}(${fromValue}deg)`, + [`--motion-flip-to${suffix}`]: `${rotationAxis}(${toValue}deg)`, }; return [ @@ -61,16 +62,16 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { custom, keyframes: [ { - transform: `perspective(${toKeyframeValue(custom, '--motion-perspective', asWeb)}) ${toKeyframeValue( + transform: `perspective(${toKeyframeValue(custom, `--motion-perspective${suffix}`, asWeb)}) ${toKeyframeValue( custom, - '--motion-flip-from', + `--motion-flip-from${suffix}`, asWeb, )} rotate(${toKeyframeValue({}, '--motion-rotate', false, '0deg')})`, }, { - transform: `perspective(${toKeyframeValue(custom, '--motion-perspective', asWeb)}) ${toKeyframeValue( + transform: `perspective(${toKeyframeValue(custom, `--motion-perspective${suffix}`, asWeb)}) ${toKeyframeValue( custom, - '--motion-flip-to', + `--motion-flip-to${suffix}`, asWeb, )} rotate(${toKeyframeValue({}, '--motion-rotate', false, '0deg')})`, }, diff --git a/packages/motion-presets/src/library/scroll/GrowScroll.ts b/packages/motion-presets/src/library/scroll/GrowScroll.ts index 242afade..51613d55 100644 --- a/packages/motion-presets/src/library/scroll/GrowScroll.ts +++ b/packages/motion-presets/src/library/scroll/GrowScroll.ts @@ -39,6 +39,7 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { } export function style(options: ScrubAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as GrowScroll; const { range = schema.range.default, @@ -80,12 +81,12 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { const [growScroll] = getNames(options); const custom = { - '--motion-travel-from': `${fromValues.travel}vh`, - '--motion-travel-to': `${toValues.travel}vh`, - '--motion-grow-from': fromValues.scale, - '--motion-grow-to': toValues.scale, - '--motion-trans-x': `${trnsX}%`, - '--motion-trans-y': `${trnsY}%`, + [`--motion-travel-from${suffix}`]: `${fromValues.travel}vh`, + [`--motion-travel-to${suffix}`]: `${toValues.travel}vh`, + [`--motion-grow-from${suffix}`]: fromValues.scale, + [`--motion-grow-to${suffix}`]: toValues.scale, + [`--motion-trans-x${suffix}`]: `${trnsX}%`, + [`--motion-trans-y${suffix}`]: `${trnsY}%`, }; return [ @@ -101,46 +102,46 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { { transform: `translateY(${toKeyframeValue( custom, - '--motion-travel-from', + `--motion-travel-from${suffix}`, asWeb, - )}) translate(${toKeyframeValue(custom, '--motion-trans-x', asWeb)}, ${toKeyframeValue( + )}) translate(${toKeyframeValue(custom, `--motion-trans-x${suffix}`, asWeb)}, ${toKeyframeValue( custom, - '--motion-trans-y', + `--motion-trans-y${suffix}`, asWeb, )}) scale(${toKeyframeValue( custom, - '--motion-grow-from', + `--motion-grow-from${suffix}`, asWeb, )}) translate(calc(-1 * ${toKeyframeValue( custom, - '--motion-trans-x', + `--motion-trans-x${suffix}`, asWeb, )}), calc(-1 * ${toKeyframeValue( custom, - '--motion-trans-y', + `--motion-trans-y${suffix}`, asWeb, )})) rotate(${toKeyframeValue({}, '--motion-rotate', false, '0')})`, }, { transform: `translateY(${toKeyframeValue( custom, - '--motion-travel-to', + `--motion-travel-to${suffix}`, asWeb, - )}) translate(${toKeyframeValue(custom, '--motion-trans-x', asWeb)}, ${toKeyframeValue( + )}) translate(${toKeyframeValue(custom, `--motion-trans-x${suffix}`, asWeb)}, ${toKeyframeValue( custom, - '--motion-trans-y', + `--motion-trans-y${suffix}`, asWeb, )}) scale(${toKeyframeValue( custom, - '--motion-grow-to', + `--motion-grow-to${suffix}`, asWeb, )}) translate(calc(-1 * ${toKeyframeValue( custom, - '--motion-trans-x', + `--motion-trans-x${suffix}`, asWeb, )}), calc(-1 * ${toKeyframeValue( custom, - '--motion-trans-y', + `--motion-trans-y${suffix}`, asWeb, )})) rotate(${toKeyframeValue({}, '--motion-rotate', false, '0')})`, }, diff --git a/packages/motion-presets/src/library/scroll/MoveScroll.ts b/packages/motion-presets/src/library/scroll/MoveScroll.ts index 32f2a68b..d9275b1c 100644 --- a/packages/motion-presets/src/library/scroll/MoveScroll.ts +++ b/packages/motion-presets/src/library/scroll/MoveScroll.ts @@ -3,7 +3,7 @@ import { getCssUnits, transformPolarToXY, toKeyframeValue, - parseLength, + parseLengthOrCalc, parseDirection, } from '../../utils'; import { SCROLL_RANGES } from '../../consts'; @@ -23,6 +23,7 @@ export function web(options: ScrubAnimationOptions, _?: DomApi, config?: Record< } export function style(options: ScrubAnimationOptions, config?: Record, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as MoveScroll; const direction = parseDirection( namedEffect?.direction, @@ -38,26 +39,30 @@ export function style(options: ScrubAnimationOptions, config?: Record (isCalc ? `calc(${value} * ${distance})` : `${value}${unit}`); let startOffsetAdd = '', endOffsetAdd = ''; if (!config?.ignoreScrollMoveOffsets) { if (travelY < 0 && range !== 'out') { - startOffsetAdd = `${travelY}${unit}`; + startOffsetAdd = toLength(travelY); if (range !== 'in') { - endOffsetAdd = `${Math.abs(travelY)}${unit}`; + endOffsetAdd = toLength(Math.abs(travelY)); } } if (travelY > 0 && range === 'out') { - endOffsetAdd = `${Math.abs(travelY)}${unit}`; + endOffsetAdd = toLength(Math.abs(travelY)); } } - [travelX, travelY] = [travelX, travelY].map(Math.round); + if (!isCalc) { + [travelX, travelY] = [travelX, travelY].map(Math.round); + } const fromValue = { x: range === 'out' ? 0 : travelX, y: range === 'out' ? 0 : travelY, @@ -70,10 +75,10 @@ export function style(options: ScrubAnimationOptions, config?: Record) and not translate: because of WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=276281 @@ -90,22 +95,22 @@ export function style(options: ScrubAnimationOptions, config?: Record { if (!target) { @@ -41,6 +43,7 @@ export function web(options: ScrubAnimationOptions, dom?: DomApi) { } export function style(options: ScrubAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as PanScroll; const toDirection = parseDirection( namedEffect?.direction, @@ -50,10 +53,10 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { const { startFromOffScreen = schema.startFromOffScreen.default, range = schema.range.default } = namedEffect; const direction = range === 'out' ? toDirection : toDirection === 'left' ? 'right' : 'left'; - const distance = parseLength(namedEffect.distance, schema.distance.default); - const travel = distance.value * (direction === 'left' ? 1 : -1); - let startX = `${-travel}${getCssUnits(distance.unit)}`; - let endX = `${travel}${getCssUnits(distance.unit)}`; + const distance = parseLengthOrCalc(namedEffect.distance, schema.distance.default); + const sign = direction === 'left' ? 1 : -1; + let startX = scaleLength(distance, -sign); + let endX = scaleLength(distance, sign); if (startFromOffScreen) { const startXLeft = `calc(${toKeyframeValue( {}, @@ -75,8 +78,8 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { const [panScroll] = getNames(options); const custom = { - '--motion-pan-from': fromValue, - '--motion-pan-to': toValue, + [`--motion-pan-from${suffix}`]: fromValue, + [`--motion-pan-to${suffix}`]: toValue, }; // use transform: translateX() and not translate: because of WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=276281 @@ -91,14 +94,14 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { { transform: `translateX(${toKeyframeValue( custom, - '--motion-pan-from', + `--motion-pan-from${suffix}`, asWeb, )}) rotate(${toKeyframeValue({}, '--motion-rotate', false, '0')})`, }, { transform: `translateX(${toKeyframeValue( custom, - '--motion-pan-to', + `--motion-pan-to${suffix}`, asWeb, )}) rotate(${toKeyframeValue({}, '--motion-rotate', false, '0')})`, }, diff --git a/packages/motion-presets/src/library/scroll/ParallaxScroll.ts b/packages/motion-presets/src/library/scroll/ParallaxScroll.ts index c20007bb..b4b35fe3 100644 --- a/packages/motion-presets/src/library/scroll/ParallaxScroll.ts +++ b/packages/motion-presets/src/library/scroll/ParallaxScroll.ts @@ -18,6 +18,7 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { } export function style(options: ScrubAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as ParallaxScroll; const { speed = schema.speed.default, center: inputCenter = schema.center.default } = namedEffect; const easing = 'linear'; @@ -33,7 +34,8 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { const [parallaxScroll] = getNames(options); const custom = { - '--motion-parallax-to': end, + [`--motion-parallax-from${suffix}`]: start, + [`--motion-parallax-to${suffix}`]: end, }; // use transform: translateY() and not translate: 0 because of WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=276281 @@ -48,16 +50,16 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { custom, keyframes: [ { - transform: `translateY(calc(${-center / (1 - center)} * ${toKeyframeValue( + transform: `translateY(${toKeyframeValue( custom, - '--motion-parallax-to', + `--motion-parallax-from${suffix}`, asWeb, - )})) rotate(${toKeyframeValue({}, '--motion-rotate', false, '0')})`, + )}) rotate(${toKeyframeValue({}, '--motion-rotate', false, '0')})`, }, { transform: `translateY(${toKeyframeValue( custom, - '--motion-parallax-to', + `--motion-parallax-to${suffix}`, asWeb, )}) rotate(${toKeyframeValue({}, '--motion-rotate', false, '0')})`, }, diff --git a/packages/motion-presets/src/library/scroll/RevealScroll.ts b/packages/motion-presets/src/library/scroll/RevealScroll.ts index 899b893e..0efba087 100644 --- a/packages/motion-presets/src/library/scroll/RevealScroll.ts +++ b/packages/motion-presets/src/library/scroll/RevealScroll.ts @@ -32,6 +32,7 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { } export function style(options: ScrubAnimationOptions) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as RevealScroll; const { range = schema.range.default } = namedEffect; const toDirection = parseDirection( @@ -49,16 +50,26 @@ export function style(options: ScrubAnimationOptions) { const [revealScroll] = getNames(options); const custom = { - '--motion-clip-from': getRevealClipFrom(direction, range), - '--motion-clip-to': getRevealClipTo(direction, range), + [`--motion-clip-from${suffix}`]: getRevealClipFrom(direction, range), + [`--motion-clip-to${suffix}`]: getRevealClipTo(direction, range), }; const keyframes = [ { - clipPath: toKeyframeValue({}, '--motion-clip-from', false, custom['--motion-clip-from']), + clipPath: toKeyframeValue( + {}, + `--motion-clip-from${suffix}`, + false, + custom[`--motion-clip-from${suffix}`], + ), }, { - clipPath: toKeyframeValue({}, '--motion-clip-to', false, custom['--motion-clip-to']), + clipPath: toKeyframeValue( + {}, + `--motion-clip-to${suffix}`, + false, + custom[`--motion-clip-to${suffix}`], + ), }, ]; diff --git a/packages/motion-presets/src/library/scroll/ShapeScroll.ts b/packages/motion-presets/src/library/scroll/ShapeScroll.ts index efe8e536..6e7db3db 100644 --- a/packages/motion-presets/src/library/scroll/ShapeScroll.ts +++ b/packages/motion-presets/src/library/scroll/ShapeScroll.ts @@ -49,6 +49,7 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { } export function style(options: ScrubAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const { start: startOpen = schema.start.default, range = schema.range.default } = options.namedEffect as ShapeScroll; let { shape = schema.shape.default } = options.namedEffect as ShapeScroll; @@ -65,24 +66,24 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { const [shapeScroll] = getNames(options); const custom = { - '--motion-clip-from': range === 'out' ? end : start, - '--motion-clip-to': range === 'out' ? start : end, + [`--motion-clip-from${suffix}`]: range === 'out' ? end : start, + [`--motion-clip-to${suffix}`]: range === 'out' ? start : end, }; const easing = getEasing('circInOut'); const keyframes = [ { - clipPath: toKeyframeValue(custom, `--motion-clip-from`, asWeb), + clipPath: toKeyframeValue(custom, `--motion-clip-from${suffix}`, asWeb), easing, }, - { clipPath: toKeyframeValue(custom, `--motion-clip-to`, asWeb) }, + { clipPath: toKeyframeValue(custom, `--motion-clip-to${suffix}`, asWeb) }, ]; if (range === 'continuous') { keyframes[1].easing = easing; keyframes.push({ - clipPath: toKeyframeValue(custom, `--motion-clip-from`, asWeb), + clipPath: toKeyframeValue(custom, `--motion-clip-from${suffix}`, asWeb), }); } diff --git a/packages/motion-presets/src/library/scroll/ShrinkScroll.ts b/packages/motion-presets/src/library/scroll/ShrinkScroll.ts index 1a1efc38..f6a5c201 100644 --- a/packages/motion-presets/src/library/scroll/ShrinkScroll.ts +++ b/packages/motion-presets/src/library/scroll/ShrinkScroll.ts @@ -39,6 +39,7 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { } export function style(options: ScrubAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as ShrinkScroll; const { range = schema.range.default, @@ -80,12 +81,12 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { const [shrinkScroll] = getNames(options); const custom = { - '--motion-travel-from': `${fromValues.travel}vh`, - '--motion-travel-to': `${toValues.travel}vh`, - '--motion-shrink-from': fromValues.scale, - '--motion-shrink-to': toValues.scale, - '--motion-trans-x': `${trnsX}%`, - '--motion-trans-y': `${trnsY}%`, + [`--motion-travel-from${suffix}`]: `${fromValues.travel}vh`, + [`--motion-travel-to${suffix}`]: `${toValues.travel}vh`, + [`--motion-shrink-from${suffix}`]: fromValues.scale, + [`--motion-shrink-to${suffix}`]: toValues.scale, + [`--motion-trans-x${suffix}`]: `${trnsX}%`, + [`--motion-trans-y${suffix}`]: `${trnsY}%`, }; return [ @@ -101,46 +102,46 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { { transform: `translateY(${toKeyframeValue( custom, - '--motion-travel-from', + `--motion-travel-from${suffix}`, asWeb, - )}) translate(${toKeyframeValue(custom, '--motion-trans-x', asWeb)}, ${toKeyframeValue( + )}) translate(${toKeyframeValue(custom, `--motion-trans-x${suffix}`, asWeb)}, ${toKeyframeValue( custom, - '--motion-trans-y', + `--motion-trans-y${suffix}`, asWeb, )}) scale(${toKeyframeValue( custom, - '--motion-shrink-from', + `--motion-shrink-from${suffix}`, asWeb, )}) translate(calc(-1 * ${toKeyframeValue( custom, - '--motion-trans-x', + `--motion-trans-x${suffix}`, asWeb, )}), calc(-1 * ${toKeyframeValue( custom, - '--motion-trans-y', + `--motion-trans-y${suffix}`, asWeb, )})) rotate(${toKeyframeValue({}, '--motion-rotate', false, '0')})`, }, { transform: `translateY(${toKeyframeValue( custom, - '--motion-travel-to', + `--motion-travel-to${suffix}`, asWeb, - )}) translate(${toKeyframeValue(custom, '--motion-trans-x', asWeb)}, ${toKeyframeValue( + )}) translate(${toKeyframeValue(custom, `--motion-trans-x${suffix}`, asWeb)}, ${toKeyframeValue( custom, - '--motion-trans-y', + `--motion-trans-y${suffix}`, asWeb, )}) scale(${toKeyframeValue( custom, - '--motion-shrink-to', + `--motion-shrink-to${suffix}`, asWeb, )}) translate(calc(-1 * ${toKeyframeValue( custom, - '--motion-trans-x', + `--motion-trans-x${suffix}`, asWeb, )}), calc(-1 * ${toKeyframeValue( custom, - '--motion-trans-y', + `--motion-trans-y${suffix}`, asWeb, )})) rotate(${toKeyframeValue({}, '--motion-rotate', false, '0')})`, }, diff --git a/packages/motion-presets/src/library/scroll/ShuttersScroll.ts b/packages/motion-presets/src/library/scroll/ShuttersScroll.ts index 962178fc..74c3cdf8 100644 --- a/packages/motion-presets/src/library/scroll/ShuttersScroll.ts +++ b/packages/motion-presets/src/library/scroll/ShuttersScroll.ts @@ -22,9 +22,11 @@ export const schema = { } as const; export function getNames(options: ScrubAnimationOptions) { - const { range = schema.range.default } = options.namedEffect as ShuttersScroll; + const { range = schema.range.default, staggered = schema.staggered.default } = + options.namedEffect as ShuttersScroll; const { suffix = '' } = options; - return [`motion-shuttersScroll-${range === 'continuous' ? '-continuous' : ''}${suffix}`]; + const stagger = range === 'continuous' && staggered ? '-staggered' : ''; + return [`motion-shuttersScroll-${range}${stagger}${suffix}`]; } export function web(options: ScrubAnimationOptions, _dom?: DomApi) { @@ -32,6 +34,7 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { } export function style(options: ScrubAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as ShuttersScroll; const direction = parseDirection( namedEffect?.direction, @@ -58,8 +61,8 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { ); const custom = { - '--motion-shutters-clip-start': range === 'out' ? clipEnd : clipStart, - '--motion-shutters-clip-end': range === 'out' ? clipStart : clipEnd, + [`--motion-shutters-clip-start${suffix}`]: range === 'out' ? clipEnd : clipStart, + [`--motion-shutters-clip-end${suffix}`]: range === 'out' ? clipStart : clipEnd, }; const [shuttersScroll] = getNames(options); @@ -70,11 +73,11 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { offset?: number; }[] = [ { - clipPath: toKeyframeValue(custom, '--motion-shutters-clip-start', asWeb), + clipPath: toKeyframeValue(custom, `--motion-shutters-clip-start${suffix}`, asWeb), easing, }, { - clipPath: toKeyframeValue(custom, '--motion-shutters-clip-end', asWeb), + clipPath: toKeyframeValue(custom, `--motion-shutters-clip-end${suffix}`, asWeb), }, ]; @@ -89,24 +92,24 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { true, ); Object.assign(custom, { - '--motion-shutters-clip-opp-end': oppClipEnd, - '--motion-shutters-clip-opp-start': oppClipStart, + [`--motion-shutters-clip-opp-end${suffix}`]: oppClipEnd, + [`--motion-shutters-clip-opp-start${suffix}`]: oppClipStart, }); const secondOffset = staggered ? 0.55 : 0.6; keyframes.push( { - clipPath: toKeyframeValue(custom, '--motion-shutters-clip-end', asWeb), + clipPath: toKeyframeValue(custom, `--motion-shutters-clip-end${suffix}`, asWeb), offset: secondOffset, easing, }, { - clipPath: toKeyframeValue(custom, '--motion-shutters-clip-opp-end', asWeb), + clipPath: toKeyframeValue(custom, `--motion-shutters-clip-opp-end${suffix}`, asWeb), offset: secondOffset, easing, }, { - clipPath: toKeyframeValue(custom, '--motion-shutters-clip-opp-start', asWeb), + clipPath: toKeyframeValue(custom, `--motion-shutters-clip-opp-start${suffix}`, asWeb), }, ); } diff --git a/packages/motion-presets/src/library/scroll/SkewPanScroll.ts b/packages/motion-presets/src/library/scroll/SkewPanScroll.ts index a1521cf7..9f47c570 100644 --- a/packages/motion-presets/src/library/scroll/SkewPanScroll.ts +++ b/packages/motion-presets/src/library/scroll/SkewPanScroll.ts @@ -45,6 +45,7 @@ export function web(options: ScrubAnimationOptions, dom?: DomApi) { } export function style(options: ScrubAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as SkewPanScroll; const toDirection = parseDirection( namedEffect?.direction, @@ -81,10 +82,10 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { const [skewPanScroll] = getNames(options); const custom = { - '--motion-skewpan-start-x': fromValues.translate, - '--motion-skewpan-end-x': toValues.translate, - '--motion-skewpan-from-skew': `${fromValues.skew}deg`, - '--motion-skewpan-to-skew': `${toValues.skew}deg`, + [`--motion-skewpan-start-x${suffix}`]: fromValues.translate, + [`--motion-skewpan-end-x${suffix}`]: toValues.translate, + [`--motion-skewpan-from-skew${suffix}`]: `${fromValues.skew}deg`, + [`--motion-skewpan-to-skew${suffix}`]: `${toValues.skew}deg`, }; return [ @@ -98,22 +99,22 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { { transform: `translateX(${toKeyframeValue( custom, - '--motion-skewpan-start-x', + `--motion-skewpan-start-x${suffix}`, asWeb, )}) skewX(${toKeyframeValue( custom, - '--motion-skewpan-from-skew', + `--motion-skewpan-from-skew${suffix}`, asWeb, )}) rotate(${toKeyframeValue({}, '--motion-rotate', false, '0')})`, }, { transform: `translateX(${toKeyframeValue( custom, - '--motion-skewpan-end-x', + `--motion-skewpan-end-x${suffix}`, asWeb, )}) skewX(${toKeyframeValue( custom, - '--motion-skewpan-to-skew', + `--motion-skewpan-to-skew${suffix}`, asWeb, )}) rotate(${toKeyframeValue({}, '--motion-rotate', false, '0')})`, }, diff --git a/packages/motion-presets/src/library/scroll/SlideScroll.ts b/packages/motion-presets/src/library/scroll/SlideScroll.ts index f29fe083..f09bd9ff 100644 --- a/packages/motion-presets/src/library/scroll/SlideScroll.ts +++ b/packages/motion-presets/src/library/scroll/SlideScroll.ts @@ -39,6 +39,7 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { } export function style(options: ScrubAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as SlideScroll; const { range = schema.range.default } = namedEffect; const toDirection = parseDirection( @@ -61,17 +62,22 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { : DIRECTION_TRANSLATION_MAP[range === 'out' ? direction : oppositeDirection]; const custom = { - '--motion-clip-from': getRevealClipFrom(direction, range), - '--motion-clip-to': getRevealClipTo(direction, range), - '--motion-translate-from-x': translateFrom.x, - '--motion-translate-from-y': translateFrom.y, - '--motion-translate-to-x': translateTo.x, - '--motion-translate-to-y': translateTo.y, + [`--motion-clip-from${suffix}`]: getRevealClipFrom(direction, range), + [`--motion-clip-to${suffix}`]: getRevealClipTo(direction, range), + [`--motion-translate-from-x${suffix}`]: translateFrom.x, + [`--motion-translate-from-y${suffix}`]: translateFrom.y, + [`--motion-translate-to-x${suffix}`]: translateTo.x, + [`--motion-translate-to-y${suffix}`]: translateTo.y, }; const keyframes = [ { - clipPath: toKeyframeValue({}, '--motion-clip-from', false, custom['--motion-clip-from']), + clipPath: toKeyframeValue( + {}, + `--motion-clip-from${suffix}`, + false, + custom[`--motion-clip-from${suffix}`], + ), transform: `rotate(${toKeyframeValue( {}, '--motion-rotate', @@ -79,12 +85,17 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { '0', )}) translate(${toKeyframeValue( custom, - `--motion-translate-from-x`, + `--motion-translate-from-x${suffix}`, asWeb, - )}, ${toKeyframeValue(custom, `--motion-translate-from-y`, asWeb)})`, + )}, ${toKeyframeValue(custom, `--motion-translate-from-y${suffix}`, asWeb)})`, }, { - clipPath: toKeyframeValue({}, '--motion-clip-to', false, custom['--motion-clip-to']), + clipPath: toKeyframeValue( + {}, + `--motion-clip-to${suffix}`, + false, + custom[`--motion-clip-to${suffix}`], + ), transform: `rotate(${toKeyframeValue( {}, '--motion-rotate', @@ -92,9 +103,9 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { '0', )}) translate(${toKeyframeValue( custom, - `--motion-translate-to-x`, + `--motion-translate-to-x${suffix}`, asWeb, - )}, ${toKeyframeValue(custom, `--motion-translate-to-y`, asWeb)})`, + )}, ${toKeyframeValue(custom, `--motion-translate-to-y${suffix}`, asWeb)})`, }, ]; diff --git a/packages/motion-presets/src/library/scroll/Spin3dScroll.ts b/packages/motion-presets/src/library/scroll/Spin3dScroll.ts index 4f78c0e5..9465ae63 100644 --- a/packages/motion-presets/src/library/scroll/Spin3dScroll.ts +++ b/packages/motion-presets/src/library/scroll/Spin3dScroll.ts @@ -21,6 +21,7 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { } export function style(options: ScrubAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const { angle = schema.angle.default, speed = schema.speed.default, @@ -55,15 +56,15 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { const [spin3dScroll] = getNames(options); const custom = { - '--motion-perspective': `${perspective}px`, - '--motion-travel-from': `${fromValues.travel}vh`, - '--motion-travel-to': `${toValues.travel}vh`, - '--motion-rot-x-from': `${fromValues.rotationX}deg`, - '--motion-rot-x-to': `${toValues.rotationX}deg`, - '--motion-rot-y-from': `${fromValues.rotationY}deg`, - '--motion-rot-y-to': `${toValues.rotationY}deg`, - '--motion-rot-z-from': `${fromValues.rotationZ}deg`, - '--motion-rot-z-to': `${toValues.rotationZ}deg`, + [`--motion-perspective${suffix}`]: `${perspective}px`, + [`--motion-travel-from${suffix}`]: `${fromValues.travel}vh`, + [`--motion-travel-to${suffix}`]: `${toValues.travel}vh`, + [`--motion-rot-x-from${suffix}`]: `${fromValues.rotationX}deg`, + [`--motion-rot-x-to${suffix}`]: `${toValues.rotationX}deg`, + [`--motion-rot-y-from${suffix}`]: `${fromValues.rotationY}deg`, + [`--motion-rot-y-to${suffix}`]: `${toValues.rotationY}deg`, + [`--motion-rot-z-from${suffix}`]: `${fromValues.rotationZ}deg`, + [`--motion-rot-z-to${suffix}`]: `${toValues.rotationZ}deg`, }; return [ @@ -77,36 +78,36 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { endOffsetAdd, keyframes: [ { - transform: `perspective(${toKeyframeValue(custom, '--motion-perspective', asWeb)}) translateY(${toKeyframeValue( + transform: `perspective(${toKeyframeValue(custom, `--motion-perspective${suffix}`, asWeb)}) translateY(${toKeyframeValue( custom, - '--motion-travel-from', + `--motion-travel-from${suffix}`, asWeb, )}) rotateZ(calc(${toKeyframeValue( {}, '--motion-rotate', false, '0deg', - )} + ${toKeyframeValue(custom, '--motion-rot-z-from', asWeb)})) rotateY(${toKeyframeValue( + )} + ${toKeyframeValue(custom, `--motion-rot-z-from${suffix}`, asWeb)})) rotateY(${toKeyframeValue( custom, - '--motion-rot-y-from', + `--motion-rot-y-from${suffix}`, asWeb, - )}) rotateX(${toKeyframeValue(custom, '--motion-rot-x-from', asWeb)})`, + )}) rotateX(${toKeyframeValue(custom, `--motion-rot-x-from${suffix}`, asWeb)})`, }, { - transform: `perspective(${toKeyframeValue(custom, '--motion-perspective', asWeb)}) translateY(${toKeyframeValue( + transform: `perspective(${toKeyframeValue(custom, `--motion-perspective${suffix}`, asWeb)}) translateY(${toKeyframeValue( custom, - '--motion-travel-to', + `--motion-travel-to${suffix}`, asWeb, )}) rotateZ(calc(${toKeyframeValue( {}, '--motion-rotate', false, '0deg', - )} + ${toKeyframeValue(custom, '--motion-rot-z-to', asWeb)})) rotateY(${toKeyframeValue( + )} + ${toKeyframeValue(custom, `--motion-rot-z-to${suffix}`, asWeb)})) rotateY(${toKeyframeValue( custom, - '--motion-rot-y-to', + `--motion-rot-y-to${suffix}`, asWeb, - )}) rotateX(${toKeyframeValue(custom, '--motion-rot-x-to', asWeb)})`, + )}) rotateX(${toKeyframeValue(custom, `--motion-rot-x-to${suffix}`, asWeb)})`, }, ], }, diff --git a/packages/motion-presets/src/library/scroll/SpinScroll.ts b/packages/motion-presets/src/library/scroll/SpinScroll.ts index 28fb72f9..c1e99146 100644 --- a/packages/motion-presets/src/library/scroll/SpinScroll.ts +++ b/packages/motion-presets/src/library/scroll/SpinScroll.ts @@ -23,6 +23,7 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { } export function style(options: ScrubAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as SpinScroll; const direction = parseDirection<(typeof SPIN_DIRECTIONS)[number]>( namedEffect?.direction, @@ -49,10 +50,10 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { const [spinScroll] = getNames(options); const custom = { - '--motion-spin-from': `${spinDirection * fromValue}deg`, - '--motion-spin-to': `${spinDirection * toValue}deg`, - '--motion-spin-scale-from': isIn ? scale : 1, - '--motion-spin-scale-to': isIn ? 1 : scale, + [`--motion-spin-from${suffix}`]: `${spinDirection * fromValue}deg`, + [`--motion-spin-to${suffix}`]: `${spinDirection * toValue}deg`, + [`--motion-spin-scale-from${suffix}`]: isIn ? scale : 1, + [`--motion-spin-scale-to${suffix}`]: isIn ? 1 : scale, }; return [ @@ -66,26 +67,26 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { { transform: `scale(${toKeyframeValue( custom, - '--motion-spin-scale-from', + `--motion-spin-scale-from${suffix}`, asWeb, )}) rotate(calc(${toKeyframeValue( {}, '--motion-rotate', false, '0deg', - )} + ${toKeyframeValue(custom, '--motion-spin-from', asWeb)}))`, + )} + ${toKeyframeValue(custom, `--motion-spin-from${suffix}`, asWeb)}))`, }, { transform: `scale(${toKeyframeValue( custom, - '--motion-spin-scale-to', + `--motion-spin-scale-to${suffix}`, asWeb, )}) rotate(calc(${toKeyframeValue( {}, '--motion-rotate', false, '0deg', - )} + ${toKeyframeValue(custom, '--motion-spin-to', asWeb)}))`, + )} + ${toKeyframeValue(custom, `--motion-spin-to${suffix}`, asWeb)}))`, }, ], }, diff --git a/packages/motion-presets/src/library/scroll/StretchScroll.ts b/packages/motion-presets/src/library/scroll/StretchScroll.ts index 60daad7f..c40626f4 100644 --- a/packages/motion-presets/src/library/scroll/StretchScroll.ts +++ b/packages/motion-presets/src/library/scroll/StretchScroll.ts @@ -25,6 +25,17 @@ const opacityKeyframesMap = { ], }; +// CSS eases each keyframe interval on its own, so these sample the WAAPI fade (eased as a whole) over two keyframes +const CSS_FADE_IN_EASING = + 'linear(0 0%, 0 33.9%, 0.0472 35.6%, 0.1041 37.3%, 0.173 39%, 0.2557 40.7%, 0.3539 42.4%, 0.4681 44.1%, 0.5964 45.8%, 0.7335 47.5%, 0.871 49.2%, 1 50.9%, 1 100%)'; +const CSS_FADE_OUT_EASING = + 'linear(0 0%, 0 45.1%, 0.1458 46.9%, 0.2918 48.7%, 0.4315 50.5%, 0.5575 52.3%, 0.6668 54.1%, 0.7595 55.9%, 0.8372 57.7%, 0.902 59.5%, 0.9558 61.3%, 1 63%, 1 100%)'; + +const cssOpacityMap = { + in: { easing: CSS_FADE_IN_EASING, keyframes: [{ opacity: 0 }, { opacity: 1 }] }, + out: { easing: CSS_FADE_OUT_EASING, keyframes: [{ opacity: 1 }, { opacity: 0 }] }, +}; + export function getNames(options: ScrubAnimationOptions) { const { range = schema.range.default } = options.namedEffect as StretchScroll; const { suffix = '' } = options; @@ -55,31 +66,39 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { const roundedScaleX = roundNumber(scaleX); const roundedScaleY = roundNumber(scaleY); + const prefix = `--motion-stretch${options.suffix || ''}`; + const scaleXFrom = `${prefix}-scale-x-from`; + const scaleYFrom = `${prefix}-scale-y-from`; + const scaleXTo = `${prefix}-scale-x-to`; + const scaleYTo = `${prefix}-scale-y-to`; + const transFrom = `${prefix}-trans-from`; + const transTo = `${prefix}-trans-to`; + const custom = { - '--motion-stretch-scale-x-from': isOut ? 1 : roundedScaleX, - '--motion-stretch-scale-y-from': isOut ? 1 : roundedScaleY, - '--motion-stretch-scale-x-to': isOut ? roundedScaleX : 1, - '--motion-stretch-scale-y-to': isOut ? roundedScaleY : 1, - '--motion-stretch-trans-from': isOut ? 0 : `calc(-100% * (1 - ${roundedScaleY}))`, - '--motion-stretch-trans-to': isOut ? `calc(100% * (1 - ${roundedScaleY}))` : 0, + [scaleXFrom]: isOut ? 1 : roundedScaleX, + [scaleYFrom]: isOut ? 1 : roundedScaleY, + [scaleXTo]: isOut ? roundedScaleX : 1, + [scaleYTo]: isOut ? roundedScaleY : 1, + [transFrom]: isOut ? 0 : `calc(-100% * (1 - ${roundedScaleY}))`, + [transTo]: isOut ? `calc(100% * (1 - ${roundedScaleY}))` : 0, }; const stretchKeyframes = [ { - scale: `${toKeyframeValue( + scale: `${toKeyframeValue(custom, scaleXFrom, asWeb)} ${toKeyframeValue( custom, - '--motion-stretch-scale-x-from', + scaleYFrom, asWeb, - )} ${toKeyframeValue(custom, '--motion-stretch-scale-y-from', asWeb)}`, - translate: `0 ${toKeyframeValue(custom, '--motion-stretch-trans-from', asWeb)}`, + )}`, + translate: `0 ${toKeyframeValue(custom, transFrom, asWeb)}`, }, { - scale: `${toKeyframeValue( + scale: `${toKeyframeValue(custom, scaleXTo, asWeb)} ${toKeyframeValue( custom, - '--motion-stretch-scale-x-to', + scaleYTo, asWeb, - )} ${toKeyframeValue(custom, '--motion-stretch-scale-y-to', asWeb)}`, - translate: `0 ${toKeyframeValue(custom, '--motion-stretch-trans-to', asWeb)}`, + )}`, + translate: `0 ${toKeyframeValue(custom, transTo, asWeb)}`, }, ]; @@ -89,16 +108,12 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { }); stretchKeyframes.push({ - scale: `${toKeyframeValue( - custom, - '--motion-stretch-scale-x-from', - asWeb, - )} ${toKeyframeValue(custom, '--motion-stretch-scale-y-from', asWeb)}`, - translate: `0 calc(100% * (1 - ${toKeyframeValue( + scale: `${toKeyframeValue(custom, scaleXFrom, asWeb)} ${toKeyframeValue( custom, - '--motion-stretch-scale-y-from', + scaleYFrom, asWeb, - )}))`, + )}`, + translate: `0 calc(100% * (1 - ${toKeyframeValue(custom, scaleYFrom, asWeb)}))`, }); } @@ -117,6 +132,7 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { fill, easing, keyframes: opacityKeyframesMap[range] || opacityKeyframesMap.out, + ...(!asWeb && cssOpacityMap[range as keyof typeof cssOpacityMap]), }, ]; } diff --git a/packages/motion-presets/src/library/scroll/TiltScroll.ts b/packages/motion-presets/src/library/scroll/TiltScroll.ts index fd6648c1..bbbc5bbc 100644 --- a/packages/motion-presets/src/library/scroll/TiltScroll.ts +++ b/packages/motion-presets/src/library/scroll/TiltScroll.ts @@ -34,6 +34,7 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { } export function style(options: ScrubAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as TiltScroll; const direction = parseDirection( namedEffect?.direction, @@ -69,15 +70,15 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { const [tiltScrollTranslate, tiltScrollRotate] = getNames(options); const custom = { - '--motion-perspective': `${perspective}px`, - '--motion-tilt-y-from': `${from.transY}vh`, - '--motion-tilt-y-to': `${to.transY}vh`, - '--motion-tilt-x-from': `${from.x}deg`, - '--motion-tilt-x-to': `${to.x}deg`, - '--motion-tilt-y-rot-from': `${from.y}deg`, - '--motion-tilt-y-rot-to': `${to.y}deg`, - '--motion-tilt-z-from': `${from.z}deg`, - '--motion-tilt-z-to': `${to.z}deg`, + [`--motion-perspective${suffix}`]: `${perspective}px`, + [`--motion-tilt-y-from${suffix}`]: `${from.transY}vh`, + [`--motion-tilt-y-to${suffix}`]: `${to.transY}vh`, + [`--motion-tilt-x-from${suffix}`]: `${from.x}deg`, + [`--motion-tilt-x-to${suffix}`]: `${to.x}deg`, + [`--motion-tilt-y-rot-from${suffix}`]: `${from.y}deg`, + [`--motion-tilt-y-rot-to${suffix}`]: `${to.y}deg`, + [`--motion-tilt-z-from${suffix}`]: `${from.z}deg`, + [`--motion-tilt-z-to${suffix}`]: `${to.z}deg`, }; return [ @@ -91,26 +92,26 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { custom, keyframes: [ { - transform: `perspective(${toKeyframeValue(custom, '--motion-perspective', asWeb)}) translateY(${toKeyframeValue( + transform: `perspective(${toKeyframeValue(custom, `--motion-perspective${suffix}`, asWeb)}) translateY(${toKeyframeValue( custom, - '--motion-tilt-y-from', + `--motion-tilt-y-from${suffix}`, asWeb, )}) rotateX(${toKeyframeValue( custom, - '--motion-tilt-x-from', + `--motion-tilt-x-from${suffix}`, asWeb, - )}) rotateY(${toKeyframeValue(custom, '--motion-tilt-y-rot-from', asWeb)})`, + )}) rotateY(${toKeyframeValue(custom, `--motion-tilt-y-rot-from${suffix}`, asWeb)})`, }, { - transform: `perspective(${toKeyframeValue(custom, '--motion-perspective', asWeb)}) translateY(${toKeyframeValue( + transform: `perspective(${toKeyframeValue(custom, `--motion-perspective${suffix}`, asWeb)}) translateY(${toKeyframeValue( custom, - '--motion-tilt-y-to', + `--motion-tilt-y-to${suffix}`, asWeb, )}) rotateX(${toKeyframeValue( custom, - '--motion-tilt-x-to', + `--motion-tilt-x-to${suffix}`, asWeb, - )}) rotateY(${toKeyframeValue(custom, '--motion-tilt-y-rot-to', asWeb)})`, + )}) rotateY(${toKeyframeValue(custom, `--motion-tilt-y-rot-to${suffix}`, asWeb)})`, }, ], }, @@ -130,7 +131,7 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { '--motion-rotate', false, '0deg', - )} + ${toKeyframeValue(custom, '--motion-tilt-z-from', asWeb)}))`, + )} + ${toKeyframeValue(custom, `--motion-tilt-z-from${suffix}`, asWeb)}))`, }, { transform: `rotate(calc(${toKeyframeValue( @@ -138,7 +139,7 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { '--motion-rotate', false, '0deg', - )} + ${toKeyframeValue(custom, '--motion-tilt-z-to', asWeb)}))`, + )} + ${toKeyframeValue(custom, `--motion-tilt-z-to${suffix}`, asWeb)}))`, }, ], }, diff --git a/packages/motion-presets/src/library/scroll/TurnScroll.ts b/packages/motion-presets/src/library/scroll/TurnScroll.ts index 7c402931..9198acc6 100644 --- a/packages/motion-presets/src/library/scroll/TurnScroll.ts +++ b/packages/motion-presets/src/library/scroll/TurnScroll.ts @@ -48,6 +48,7 @@ export function web(options: ScrubAnimationOptions, dom?: DomApi) { } export function style(options: ScrubAnimationOptions, asWeb = false) { + const { suffix = '' } = options; const namedEffect = options.namedEffect as TurnScroll; const toDirection = parseDirection( namedEffect?.direction, @@ -95,12 +96,12 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { const [turnScroll] = getNames(options); const custom = { - '--motion-turn-translate-from': fromValues.translate, - '--motion-turn-translate-to': toValues.translate, - '--motion-turn-scale-from': fromValues.scale, - '--motion-turn-scale-to': toValues.scale, - '--motion-turn-rotation-from': `${fromValues.rotation}deg`, - '--motion-turn-rotation-to': `${toValues.rotation}deg`, + [`--motion-turn-translate-from${suffix}`]: fromValues.translate, + [`--motion-turn-translate-to${suffix}`]: toValues.translate, + [`--motion-turn-scale-from${suffix}`]: fromValues.scale, + [`--motion-turn-scale-to${suffix}`]: toValues.scale, + [`--motion-turn-rotation-from${suffix}`]: `${fromValues.rotation}deg`, + [`--motion-turn-rotation-to${suffix}`]: `${toValues.rotation}deg`, }; return [ @@ -114,34 +115,34 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { { transform: `translateX(${toKeyframeValue( custom, - '--motion-turn-translate-from', + `--motion-turn-translate-from${suffix}`, asWeb, )}) scale(${toKeyframeValue( custom, - '--motion-turn-scale-from', + `--motion-turn-scale-from${suffix}`, asWeb, )}) rotate(calc(${toKeyframeValue( {}, '--motion-rotate', false, '0deg', - )} + ${toKeyframeValue(custom, '--motion-turn-rotation-from', asWeb)}))`, + )} + ${toKeyframeValue(custom, `--motion-turn-rotation-from${suffix}`, asWeb)}))`, }, { transform: `translateX(${toKeyframeValue( custom, - '--motion-turn-translate-to', + `--motion-turn-translate-to${suffix}`, asWeb, )}) scale(${toKeyframeValue( custom, - '--motion-turn-scale-to', + `--motion-turn-scale-to${suffix}`, asWeb, )}) rotate(calc(${toKeyframeValue( {}, '--motion-rotate', false, '0deg', - )} + ${toKeyframeValue(custom, '--motion-turn-rotation-to', asWeb)}))`, + )} + ${toKeyframeValue(custom, `--motion-turn-rotation-to${suffix}`, asWeb)}))`, }, ], }, diff --git a/packages/motion-presets/src/library/scroll/test/MoveScroll.spec.ts b/packages/motion-presets/src/library/scroll/test/MoveScroll.spec.ts index 15f50273..c95ae3be 100644 --- a/packages/motion-presets/src/library/scroll/test/MoveScroll.spec.ts +++ b/packages/motion-presets/src/library/scroll/test/MoveScroll.spec.ts @@ -226,4 +226,52 @@ describe('MoveScroll', () => { expect(result).toMatchObject(expectedResult); }); }); + + describe('calc() travel', () => { + const travel = 'calc(10vh + 20px)'; + const sinIn = Math.sin((240 * Math.PI) / 180); + const sinOut = Math.sin((60 * Math.PI) / 180); + + test.each([ + ['in', `calc(${sinIn} * ${travel})`, ''], + ['continuous', `calc(${sinIn} * ${travel})`, `calc(${-sinIn} * ${travel})`], + ['out', '', `calc(${sinOut} * ${travel})`], + ])( + 'range %s derives offset adds from the angle sign', + (range, startOffsetAdd, endOffsetAdd) => { + const result = MoveScroll.style({ + ...baseMockOptions, + namedEffect: { travel, direction: 60, range } as unknown as MoveScrollType, + }); + + expect(result[0]).toMatchObject({ startOffsetAdd, endOffsetAdd }); + }, + ); + + test('translate values are scaled calc() expressions', () => { + const result = MoveScroll.style({ + ...baseMockOptions, + namedEffect: { travel, direction: 60, range: 'continuous' } as unknown as MoveScrollType, + }); + + expect(result[0].custom).toEqual({ + '--motion-move-from-x': `calc(${Math.cos((240 * Math.PI) / 180)} * ${travel})`, + '--motion-move-from-y': `calc(${sinIn} * ${travel})`, + '--motion-move-to-x': `calc(${-Math.cos((240 * Math.PI) / 180)} * ${travel})`, + '--motion-move-to-y': `calc(${-sinIn} * ${travel})`, + }); + }); + + test('numeric travel keeps rounded output', () => { + const result = MoveScroll.style({ + ...baseMockOptions, + namedEffect: { travel: { value: 200, unit: 'px' }, direction: 60 } as MoveScrollType, + }); + + expect(result[0]).toMatchObject({ + startOffsetAdd: `${200 * sinIn}px`, + custom: { '--motion-move-from-x': '-100px', '--motion-move-from-y': '-173px' }, + }); + }); + }); }); diff --git a/packages/motion-presets/src/library/scroll/test/PanScroll.spec.ts b/packages/motion-presets/src/library/scroll/test/PanScroll.spec.ts index 1199e81c..d0fa4c67 100644 --- a/packages/motion-presets/src/library/scroll/test/PanScroll.spec.ts +++ b/packages/motion-presets/src/library/scroll/test/PanScroll.spec.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from 'vitest'; +import { describe, expect, test, vi } from 'vitest'; import * as PanScroll from '../PanScroll'; import type { PanScroll as PanScrollType, ScrubAnimationOptions } from '../../../types'; @@ -266,4 +266,29 @@ describe('PanScroll', () => { expect(result).toMatchObject(expectedResult); }); }); + + describe('prepare', () => { + const run = (namedEffect: PanScrollType) => { + const target = document.createElement('div'); + const dom = { + measure: vi.fn((fn: (el: HTMLElement) => void) => fn(target)), + mutate: vi.fn((fn: (el: HTMLElement) => void) => fn(target)), + }; + PanScroll.prepare({ ...baseMockOptions, namedEffect }, dom); + return { dom, target }; + }; + + test('measures the element by default', () => { + const { dom, target } = run({} as PanScrollType); + + expect(dom.measure).toHaveBeenCalled(); + expect(target.style.getPropertyValue('--motion-left')).toBe('0px'); + }); + + test('does not measure when startFromOffScreen is false', () => { + const { dom } = run({ startFromOffScreen: false } as PanScrollType); + + expect(dom.measure).not.toHaveBeenCalled(); + }); + }); }); diff --git a/packages/motion-presets/src/library/scroll/test/ParallaxScroll.spec.ts b/packages/motion-presets/src/library/scroll/test/ParallaxScroll.spec.ts index 526a469b..8bb497f7 100644 --- a/packages/motion-presets/src/library/scroll/test/ParallaxScroll.spec.ts +++ b/packages/motion-presets/src/library/scroll/test/ParallaxScroll.spec.ts @@ -20,7 +20,7 @@ describe('ParallaxScroll', () => { endOffsetAdd: '25vh', keyframes: [ { - transform: 'translateY(calc(-1 * 25vh)) rotate(var(--motion-rotate, 0))', + transform: 'translateY(-25vh) rotate(var(--motion-rotate, 0))', }, { transform: 'translateY(25vh) rotate(var(--motion-rotate, 0))', @@ -46,8 +46,7 @@ describe('ParallaxScroll', () => { endOffsetAdd: '62.9971840901091vh', keyframes: [ { - transform: - 'translateY(calc(-1 * 62.9971840901091vh)) rotate(var(--motion-rotate, 0))', + transform: 'translateY(-62.9971840901091vh) rotate(var(--motion-rotate, 0))', }, { transform: 'translateY(62.9971840901091vh) rotate(var(--motion-rotate, 0))', @@ -77,8 +76,7 @@ describe('ParallaxScroll', () => { endOffsetAdd: '25vh', keyframes: [ { - transform: - 'translateY(calc(-1 * var(--motion-parallax-to))) rotate(var(--motion-rotate, 0))', + transform: 'translateY(var(--motion-parallax-from)) rotate(var(--motion-rotate, 0))', }, { transform: 'translateY(var(--motion-parallax-to)) rotate(var(--motion-rotate, 0))', @@ -104,8 +102,7 @@ describe('ParallaxScroll', () => { endOffsetAdd: '37.5vh', keyframes: [ { - transform: - 'translateY(calc(-0.3333333333333333 * var(--motion-parallax-to))) rotate(var(--motion-rotate, 0))', + transform: 'translateY(var(--motion-parallax-from)) rotate(var(--motion-rotate, 0))', }, { transform: 'translateY(var(--motion-parallax-to)) rotate(var(--motion-rotate, 0))', diff --git a/packages/motion-presets/src/library/scroll/test/StretchScroll.spec.ts b/packages/motion-presets/src/library/scroll/test/StretchScroll.spec.ts index 4a45cef9..250d9788 100644 --- a/packages/motion-presets/src/library/scroll/test/StretchScroll.spec.ts +++ b/packages/motion-presets/src/library/scroll/test/StretchScroll.spec.ts @@ -1,7 +1,11 @@ import { describe, expect, test } from 'vitest'; import * as StretchScroll from '../StretchScroll'; -import type { StretchScroll as StretchScrollType, ScrubAnimationOptions } from '../../../types'; +import type { + AnimationData, + StretchScroll as StretchScrollType, + ScrubAnimationOptions, +} from '../../../types'; import { baseMockOptions } from './testUtils'; describe('StretchScroll', () => { @@ -208,17 +212,8 @@ describe('StretchScroll', () => { }, { fill: 'forwards', - easing: 'backInOut', - keyframes: [ - { - opacity: 1, - offset: 0.35, - }, - { - opacity: 0, - offset: 1, - }, - ], + easing: expect.stringMatching(/^linear\(0 0%, 0 45.1%, .*, 1 63%, 1 100%\)$/), + keyframes: [{ opacity: 1 }, { opacity: 0 }], }, ]; @@ -249,16 +244,8 @@ describe('StretchScroll', () => { }, { fill: 'backwards', - keyframes: [ - { - opacity: 0, - offset: 0, - }, - { - opacity: 1, - offset: 0.65, - }, - ], + easing: expect.stringMatching(/^linear\(0 0%, 0 33.9%, .*, 1 50.9%, 1 100%\)$/), + keyframes: [{ opacity: 0 }, { opacity: 1 }], }, ]; @@ -323,5 +310,60 @@ describe('StretchScroll', () => { expect(result).toMatchObject(expectedResult); }); + + const styleOf = (namedEffect: Partial, suffix?: string) => + StretchScroll.style({ + ...baseMockOptions, + suffix, + namedEffect: namedEffect as StretchScrollType, + }) as AnimationData[]; + + const getVarNames = (keyframes: Keyframe[]) => + keyframes.flatMap((frame) => + Object.values(frame).flatMap((value) => + [...String(value).matchAll(/var\((--[\w-]+)/g)].map(([, name]) => name), + ), + ); + + test.each([ + [{}, { x: 0.4, y: 1.6 }], + [{ stretch: 0.2 }, { x: 0.8, y: 1.2 }], + [ + { stretch: -0.5, range: 'in' }, + { x: 1.5, y: 0.5 }, + ], + ])('declares every custom property its keyframes use - %o', (namedEffect, scale) => { + const [stretch, fade] = styleOf(namedEffect as Partial); + const custom = stretch.custom!; + + expect(getVarNames(stretch.keyframes).every((name) => name in custom)).toBe(true); + expect(getVarNames(fade.keyframes)).toEqual([]); + expect(Object.values(custom).every((value) => !String(value).includes('var('))).toBe(true); + expect(Object.values(custom)).toEqual(expect.arrayContaining([scale.x, scale.y])); + expect(fade.keyframes.every((frame) => !('offset' in frame))).toBe(true); + }); + + test('custom stretch value', () => { + const [stretch] = styleOf({ stretch: 0.8 }); + + expect(stretch.custom).toEqual({ + '--motion-stretch-scale-x-from': 1, + '--motion-stretch-scale-y-from': 1, + '--motion-stretch-scale-x-to': 0.2, + '--motion-stretch-scale-y-to': 1.8, + '--motion-stretch-trans-from': 0, + '--motion-stretch-trans-to': 'calc(100% * (1 - 1.8))', + }); + }); + + test('suffixes its custom properties, so effects in other keyframe slots keep their own values', () => { + const [stretch] = styleOf({ stretch: 0.2, range: 'in' }, '-1'); + + expect(stretch.name).toBe('motion-stretchScrollScale-1'); + expect( + Object.keys(stretch.custom!).every((name) => name.startsWith('--motion-stretch-1-')), + ).toBe(true); + expect(getVarNames(stretch.keyframes).every((name) => name in stretch.custom!)).toBe(true); + }); }); }); diff --git a/packages/motion-presets/src/test/presets.spec.ts b/packages/motion-presets/src/test/presets.spec.ts index adc1206c..58269135 100644 --- a/packages/motion-presets/src/test/presets.spec.ts +++ b/packages/motion-presets/src/test/presets.spec.ts @@ -4,6 +4,7 @@ import * as entrance from '../library/entrance'; import * as scroll from '../library/scroll'; import * as ongoing from '../library/ongoing'; import * as mouse from '../library/mouse'; +import * as backgroundScroll from '../library/backgroundScroll'; type Field = { type: string; @@ -126,3 +127,98 @@ describe.each(mousePresets)('mouse $name', ({ name, create }) => { expectValidDefaults(schema); }); }); + +const sampleValues = (field: Field & { int?: boolean }) => { + if (field.type === 'enum') return field.values!; + if (field.type === 'bool') return [true, false]; + if (field.type === 'length') { + return [field.default, { value: 33, unit: 'px' }, { value: 2, unit: 'em' }, 'calc(1vh + 10px)']; + } + if (field.type === 'angle') return [field.default, 45, 'top', '0.25turn']; + if (field.type === 'number') { + const value = field.default as number; + const values = [value, field.min ?? value - 1, field.max ?? value * 2 + 1]; + return [...new Set(field.int ? values.map(Math.round) : values)]; + } + return [field.default]; +}; + +const sampleParams = (schema: Record = {}) => + Object.entries(schema).reduce[]>( + (params, [key, field]) => + params.flatMap((param) => sampleValues(field).map((value) => ({ ...param, [key]: value }))), + [{}], + ); + +// var() fallbacks never apply, since every animation declares its custom properties +const withoutVarFallbacks = (css: string) => { + let result = ''; + for (let i = 0; i < css.length; i++) { + const comma = css.indexOf(',', i); + if (css.startsWith('var(', i) && comma !== -1 && comma < css.indexOf(')', i)) { + let depth = 1; + let j = comma + 1; + for (; depth; j++) { + if (css[j] === '(') depth++; + if (css[j] === ')') depth--; + } + result += `${css.slice(i, comma)})`; + i = j - 1; + } else { + result += css[i]; + } + } + return result; +}; + +test('each keyframes name always has the same keyframes', () => { + const groups = { entrance, scroll, ongoing, backgroundScroll }; + const groupOptions: Record[]> = { + entrance: [{ duration: 1000 }], + ongoing: [{ duration: 1000 }, { duration: 500 }], + }; + const extraParams: Record[]> = { + ongoing: [{}, { iterationDelay: 500 }, { iterationDelay: 1000 }], + }; + const keyframesByName = new Map(); + const collisions = new Set(); + + for (const [group, modules] of Object.entries(groups)) { + for (const [type, module] of Object.entries(modules as Record)) { + if (!module.style) continue; + for (const options of groupOptions[group] ?? [{}]) + for (const params of sampleParams(module.schema)) + for (const extra of extraParams[group] ?? [{}]) { + const namedEffect = { type, ...params, ...extra }; + for (const { name, keyframes } of module.style( + structuredClone({ ...options, namedEffect }), + )) { + const css = withoutVarFallbacks(JSON.stringify(keyframes)); + if (!keyframesByName.has(name)) keyframesByName.set(name, css); + else if (keyframesByName.get(name) !== css) collisions.add(name); + } + } + } + } + + expect([...collisions]).toEqual([]); +}); + +const EXTERNAL_PROPERTIES = ['--motion-rotate', '--motion-left']; + +describe.each(presets.filter(({ module }) => module.style))( + '$group $name custom properties', + ({ group, name, module }) => { + test.each(sampleParams(module.schema))('are suffixed for %o', (params) => { + const options = { ...optionsFor(group, { type: name, ...params }), suffix: '-s1' }; + for (const { custom = {}, keyframes } of module.style!(options)) { + for (const key of Object.keys(custom)) expect(key).toContain('-s1'); + const referenced = [...JSON.stringify(keyframes).matchAll(/var\((--[\w-]+)/g)].map( + ([, property]) => property, + ); + for (const property of referenced) + expect([...Object.keys(custom), ...EXTERNAL_PROPERTIES]).toContain(property); + } + }); + }, +); diff --git a/packages/motion-presets/src/test/utils.spec.ts b/packages/motion-presets/src/test/utils.spec.ts index b4fcfc4a..3e4ed982 100644 --- a/packages/motion-presets/src/test/utils.spec.ts +++ b/packages/motion-presets/src/test/utils.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from 'vitest'; -import { parseLength, parseDirection } from '../utils'; +import { parseLength, parseDirection, getShuttersClipPaths } from '../utils'; describe('parseLength', () => { const defaultValue = { value: 100, unit: 'percentage' }; @@ -224,3 +224,12 @@ describe('parseDirection', () => { }); }); }); + +describe('getShuttersClipPaths', () => { + test('rounds the shutter count to a positive integer', () => { + expect(getShuttersClipPaths('left', 2.4, false)).toEqual( + getShuttersClipPaths('left', 2, false), + ); + expect(getShuttersClipPaths('top', -3, true)).toEqual(getShuttersClipPaths('top', 1, true)); + }); +}); diff --git a/packages/motion-presets/src/utils.ts b/packages/motion-presets/src/utils.ts index abbe4b63..eec52ad4 100644 --- a/packages/motion-presets/src/utils.ts +++ b/packages/motion-presets/src/utils.ts @@ -298,9 +298,10 @@ export function getElementOffset(element: HTMLElement, parent?: HTMLElement) { const generateShuttersClipPath = ( direction: EffectFourDirections, - shutterCount: number, + shutters: number, staggered: boolean, ) => { + const shutterCount = Math.max(1, Math.round(shutters) || 1); const isTopOrLeft = direction === 'top' || direction === 'left'; const iterStart = isTopOrLeft ? shutterCount : 0; const iterEnd = isTopOrLeft ? 0 : shutterCount; @@ -446,6 +447,36 @@ export function parseLength(input: LengthInput, defaultValue: LengthValue): Leng return defaultValue; } +const CSS_CALC_REGEX = /^calc\(.+\)$/i; + +export function parseLengthOrCalc(input: LengthInput, defaultValue: LengthValue) { + if (typeof input === 'string' && CSS_CALC_REGEX.test(input.trim())) { + return input.trim(); + } + return parseLength(input, defaultValue); +} + +const CSS_ANGLE_REGEX = /^-?\d*\.?\d+(rad|grad|turn)$/i; + +export function parseCssAngle(input: unknown) { + if (typeof input !== 'string') { + return undefined; + } + const trimmed = input.trim(); + return CSS_ANGLE_REGEX.test(trimmed) || CSS_CALC_REGEX.test(trimmed) ? trimmed : undefined; +} + +export function scaleLength( + length: LengthValue | string, + factor: number = 1, + round: (value: number) => number = (value) => value, +) { + if (typeof length === 'string') { + return factor === 1 ? length : `calc(${factor} * ${length})`; + } + return `${round(length.value * factor)}${getCssUnits(length.unit)}`; +} + export type DirectionKeywords = readonly string[]; /** From 18613f05092b96f0422fc2f3d9234d4b6a378ce1 Mon Sep 17 00:00:00 2001 From: ameerf-wix Date: Tue, 20 Oct 2026 17:50:15 +0300 Subject: [PATCH 3/4] fix(motion-presets): exact ongoing amounts, parallax speed and loop delays - ongoing amounts are the peaks the keyframes reach (Pulse, Rubber, Wiggle, Bounce, Poke, Jello), as in #330 - iterationDelay holds at rest after an unchanged motion (holdAtRest) - getScrollParallax: visual speed relative to the scroll for GrowScroll, ShrinkScroll, Spin3dScroll, TiltScroll and ParallaxScroll (speed/center), by translating and widening the scroll range - Cross supports CSS (style + prepare); Pulse keeps the layout rotation; Spin no longer rotates rotated elements twice; Swing's CSS easings are real values - keyframe names include the easing where keyframes use it - motion: persist the animations of layered effects so finished layers are not auto-removed - remove the unused transitionDelay option everywhere Co-Authored-By: Claude Opus 5.5 (1M context) --- apps/playground/SPEC.md | 2 +- .../inspector/pg-scrub-effect-editor.ts | 8 - .../interact-validate/src/schema/effects.ts | 1 - packages/interact/docs/api/types.md | 2 - packages/interact/rules/full-lean.md | 1 - packages/interact/src/types/effects.ts | 1 - packages/motion-presets/README.md | 2 +- .../rules/presets/ongoing-presets.md | 2 +- .../rules/presets/presets-main.md | 4 +- .../rules/presets/scroll-presets.md | 10 +- .../src/library/ongoing/Bounce.ts | 26 +- .../src/library/ongoing/Breathe.ts | 68 ++-- .../src/library/ongoing/Cross.ts | 225 ++++++----- .../src/library/ongoing/Flash.ts | 13 +- .../src/library/ongoing/Flip.ts | 43 ++- .../src/library/ongoing/Fold.ts | 74 ++-- .../src/library/ongoing/Jello.ts | 31 +- .../src/library/ongoing/Poke.ts | 49 +-- .../src/library/ongoing/Pulse.ts | 43 +-- .../src/library/ongoing/Rubber.ts | 32 +- .../src/library/ongoing/Spin.ts | 39 +- .../src/library/ongoing/Swing.ts | 90 ++--- .../src/library/ongoing/Wiggle.ts | 47 +-- .../src/library/ongoing/test/Bounce.spec.ts | 189 ++++++--- .../src/library/ongoing/test/Breathe.spec.ts | 115 +++--- .../src/library/ongoing/test/Cross.spec.ts | 363 +++++++++--------- .../src/library/ongoing/test/Flash.spec.ts | 60 ++- .../src/library/ongoing/test/Flip.spec.ts | 32 +- .../src/library/ongoing/test/Fold.spec.ts | 137 +++---- .../src/library/ongoing/test/Jello.spec.ts | 95 +++-- .../src/library/ongoing/test/Poke.spec.ts | 166 +++++--- .../src/library/ongoing/test/Pulse.spec.ts | 80 ++-- .../src/library/ongoing/test/Rubber.spec.ts | 123 +++--- .../src/library/ongoing/test/Spin.spec.ts | 70 +++- .../src/library/ongoing/test/Swing.spec.ts | 92 ++--- .../src/library/ongoing/test/Wiggle.spec.ts | 32 ++ .../src/library/scroll/GrowScroll.ts | 19 +- .../src/library/scroll/ParallaxScroll.ts | 22 +- .../src/library/scroll/ShrinkScroll.ts | 19 +- .../src/library/scroll/Spin3dScroll.ts | 20 +- .../src/library/scroll/TiltScroll.ts | 25 +- .../library/scroll/test/GrowScroll.spec.ts | 92 +++-- .../scroll/test/ParallaxScroll.spec.ts | 48 ++- .../library/scroll/test/ShrinkScroll.spec.ts | 38 +- .../library/scroll/test/Spin3dScroll.spec.ts | 58 +-- .../library/scroll/test/TiltScroll.spec.ts | 83 ++-- .../motion-presets/src/test/presets.spec.ts | 84 ++-- .../src/test/scrollParallax.spec.ts | 176 +++++++++ packages/motion-presets/src/utils.ts | 105 +++++ packages/motion/docs/api/types.md | 3 +- packages/motion/docs/core-concepts.md | 2 +- packages/motion/rules/waapi.md | 1 - packages/motion/src/AnimationGroup.ts | 5 + packages/motion/src/api/webAnimations.ts | 5 + packages/motion/src/types.ts | 1 - packages/motion/test/AnimationGroup.spec.ts | 12 + packages/motion/test/motion.spec.ts | 25 ++ skills/interactor/references/config-schema.md | 1 - skills/interactor/references/motion-engine.md | 2 +- skills/interactor/references/presets.md | 4 +- 60 files changed, 1887 insertions(+), 1330 deletions(-) create mode 100644 packages/motion-presets/src/test/scrollParallax.spec.ts diff --git a/apps/playground/SPEC.md b/apps/playground/SPEC.md index 52235666..6d68acee 100644 --- a/apps/playground/SPEC.md +++ b/apps/playground/SPEC.md @@ -299,7 +299,7 @@ For scroll/pointer-driven animations: - **Animation Source** toggle: same Named Effect / Keyframes radio as Time effects - **Timing properties**: easing, iterations, alternate, fill, reversed - **Range** (viewProgress only): `rangeStart` / `rangeEnd` with name (entry/exit/contain/cover) + offset (value + unit) -- **Transition params** (pointerMove only): `transitionDuration`, `transitionDelay`, `transitionEasing`, `centeredToTarget` +- **Transition params** (pointerMove only): `transitionDuration`, `transitionEasing`, `centeredToTarget` ### Transition Effect Editor (``) diff --git a/apps/playground/src/components/inspector/pg-scrub-effect-editor.ts b/apps/playground/src/components/inspector/pg-scrub-effect-editor.ts index f98e30d6..5f5b44a5 100644 --- a/apps/playground/src/components/inspector/pg-scrub-effect-editor.ts +++ b/apps/playground/src/components/inspector/pg-scrub-effect-editor.ts @@ -231,7 +231,6 @@ export class PgScrubEffectEditor extends BaseComponent { const rangeEnd = (e.rangeEnd as RangeOffset) ?? {}; const transitionDuration = (e.transitionDuration as number) ?? 0; - const transitionDelay = (e.transitionDelay as number) ?? 0; const transitionEasing = (e.transitionEasing as string) ?? ''; const centeredToTarget = (e.centeredToTarget as boolean) ?? false; @@ -349,10 +348,6 @@ export class PgScrubEffectEditor extends BaseComponent { -
- - -
@@ -502,9 +497,6 @@ export class PgScrubEffectEditor extends BaseComponent { shadow.getElementById('trans-duration')?.addEventListener('change', (e) => { update({ transitionDuration: parseInt((e.target as HTMLInputElement).value, 10) || 0 }); }); - shadow.getElementById('trans-delay')?.addEventListener('change', (e) => { - update({ transitionDelay: parseInt((e.target as HTMLInputElement).value, 10) || 0 }); - }); shadow.getElementById('trans-easing')?.addEventListener('change', (e) => { const val = (e.target as HTMLSelectElement).value; update({ transitionEasing: val || undefined }); diff --git a/packages/interact-validate/src/schema/effects.ts b/packages/interact-validate/src/schema/effects.ts index cda3b853..f7683524 100644 --- a/packages/interact-validate/src/schema/effects.ts +++ b/packages/interact-validate/src/schema/effects.ts @@ -164,7 +164,6 @@ const viewProgressEffectFields = { const pointerMoveEffectFields = { centeredToTarget: z.boolean().optional(), transitionDuration: z.number().int().nonnegative().optional(), - transitionDelay: z.number().int().nonnegative().optional(), transitionEasing: z.enum(['linear', 'hardBackOut', 'easeOut', 'elastic', 'bounce']).optional(), }; diff --git a/packages/interact/docs/api/types.md b/packages/interact/docs/api/types.md index 728d71ed..2ce9d9de 100644 --- a/packages/interact/docs/api/types.md +++ b/packages/interact/docs/api/types.md @@ -544,7 +544,6 @@ type ScrubEffect = { rangeEnd?: RangeOffset; centeredToTarget?: boolean; transitionDuration?: number; - transitionDelay?: number; transitionEasing?: ScrubTransitionEasing; } & EffectProperty; ``` @@ -555,7 +554,6 @@ type ScrubEffect = { - `rangeEnd` - Scroll position where animation ends - `centeredToTarget` - Whether to center scroll range on target element - `transitionDuration` - Smooth transition duration when entering/exiting scrub -- `transitionDelay` - Delay for scrub transition - `transitionEasing` - Easing for scrub transition **Examples:** diff --git a/packages/interact/rules/full-lean.md b/packages/interact/rules/full-lean.md index 0bbaca19..ff07e696 100644 --- a/packages/interact/rules/full-lean.md +++ b/packages/interact/rules/full-lean.md @@ -418,7 +418,6 @@ Used with `viewProgress` and `pointerMove` triggers. composite?: 'replace' | 'add' | 'accumulate'; 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'; // pointerMove + customEffect only // + exactly one animation payload (see below) } diff --git a/packages/interact/src/types/effects.ts b/packages/interact/src/types/effects.ts index 4cf8a35e..46e37509 100644 --- a/packages/interact/src/types/effects.ts +++ b/packages/interact/src/types/effects.ts @@ -51,7 +51,6 @@ export type ScrubEffect = { rangeEnd?: RangeOffset; centeredToTarget?: boolean; transitionDuration?: number; - transitionDelay?: number; transitionEasing?: ScrubTransitionEasing; } & EffectEffectProperty; diff --git a/packages/motion-presets/README.md b/packages/motion-presets/README.md index fc102d5a..f2e19001 100644 --- a/packages/motion-presets/README.md +++ b/packages/motion-presets/README.md @@ -203,7 +203,7 @@ Supported units: `px`, `em`, `rem`, `vh`, `vw`, `vmin`, `vmax`, `percentage`. ### `iterationDelay` (ongoing only) -Adds an idle pause between loop cycles. Set on the `namedEffect`: +Adds an idle pause at rest after each loop cycle, without changing the motion itself. Set on the `namedEffect`: ```typescript namedEffect: { type: 'Bounce', iterationDelay: 1000 } // 1 s pause after each cycle diff --git a/packages/motion-presets/rules/presets/ongoing-presets.md b/packages/motion-presets/rules/presets/ongoing-presets.md index fbf47317..7f4ad8e5 100644 --- a/packages/motion-presets/rules/presets/ongoing-presets.md +++ b/packages/motion-presets/rules/presets/ongoing-presets.md @@ -172,7 +172,7 @@ Visual: Element stretches non-uniformly on X and Y axes, creating a rubber-band Parameters: -- `stretch`: number — -1 to 1, the stretch amplitude (default: `0.1`) +- `stretch`: number — -1 to 1, the stretch amount at the first squash: scaleX = 1 + stretch, scaleY = 1 - stretch (default: `0.1`) - `iterationDelay`: number — idle time in ms after each iteration cycle (default: `0`) ```typescript diff --git a/packages/motion-presets/rules/presets/presets-main.md b/packages/motion-presets/rules/presets/presets-main.md index 93d8aa08..c0f40398 100644 --- a/packages/motion-presets/rules/presets/presets-main.md +++ b/packages/motion-presets/rules/presets/presets-main.md @@ -71,12 +71,12 @@ These are set on the effect configuration level, not on the preset itself: **Ongoing-specific preset parameter:** -- `iterationDelay`: Idle time in ms appended after each active iteration cycle. Available on all ongoing presets. This compresses the active animation keyframes into a fraction of the total iteration duration, creating a pause between repetitions. Set on the `namedEffect`, not on the animation options. +- `iterationDelay`: Idle time in ms appended after each active iteration cycle. Available on all ongoing presets. The element holds at rest for this time after each iteration; the motion's shape is unchanged. Set on the `namedEffect`, not on the animation options. **Scroll-specific animation options:** - `rangeStart` / `rangeEnd`: `RangeOffset` controlling when the scroll animation starts/ends -- `transitionDuration` / `transitionDelay` / `transitionEasing`: Transition smoothing +- `transitionDuration` / `transitionEasing`: Transition smoothing ### Overloaded Parameter Names diff --git a/packages/motion-presets/rules/presets/scroll-presets.md b/packages/motion-presets/rules/presets/scroll-presets.md index d6f2e6da..d7f81d18 100644 --- a/packages/motion-presets/rules/presets/scroll-presets.md +++ b/packages/motion-presets/rules/presets/scroll-presets.md @@ -116,7 +116,7 @@ Parameters: - `pivot`: EffectNineDirections — origin point for scaling (default: `'center'`) - `range`: 'in' | 'out' | 'continuous' (default: `'in'`) - `scale`: number — starting scale on 'in', ending scale otherwise; inverted to below 1 for 'in' and above 1 otherwise (default: `0.25`, i.e. `4` for 'out') -- `speed`: number — approximate visual speed relative to the scroll, 1 = moves with the page (default: `1`) +- `speed`: number — visual speed relative to the scroll, 1 = moves with the page (default: `1`) ```typescript { type: 'GrowScroll', pivot: 'top-left', scale: 0.5 } @@ -163,7 +163,7 @@ Visual: Element moves at a different speed than the scroll, creating a depth ill Parameters: -- `speed`: number — approximate visual speed relative to the scroll; 1 = moves with the page, below 1 = slower (default: `0.7159`) +- `speed`: number — visual speed relative to the scroll; 1 = moves with the page, below 1 = slower (default: `0.7159`) - `center`: number — 0 to 1, point of the scroll range where the element is at its natural position (default: `0.5`) ```typescript @@ -212,7 +212,7 @@ Parameters: - `pivot`: EffectNineDirections — shrink origin (default: `'center'`) - `range`: 'in' | 'out' | 'continuous' (default: `'in'`) - `scale`: number — starting scale on 'in', ending scale otherwise; inverted to above 1 for 'in' and below 1 otherwise (default: `1.2`, i.e. `1/1.2` for 'out') -- `speed`: number — approximate visual speed relative to the scroll, 1 = moves with the page (default: `1`) +- `speed`: number — visual speed relative to the scroll, 1 = moves with the page (default: `1`) ```typescript { type: 'ShrinkScroll', pivot: 'bottom', scale: 2 } @@ -275,7 +275,7 @@ Visual: Element performs a 3D spin with rotation on multiple axes as it scrolls. Parameters: - `angle`: number — rotation amount in degrees, applied on all axes (default: `100`) -- `speed`: number — approximate visual speed relative to the scroll, 1 = moves with the page (default: `1`) +- `speed`: number — visual speed relative to the scroll, 1 = moves with the page (default: `1`) - `range`: 'in' | 'out' | 'continuous' (default: `'in'`) - `perspective`: number — 3D perspective in px (default: `1000`) @@ -324,7 +324,7 @@ Visual: Element tilts in 3D and perspective, with optional parallax vertical mov Parameters: - `direction`: 'clockwise' | 'counter-clockwise' — z-rotation direction (default: `'counter-clockwise'`) -- `speed`: number — approximate visual speed relative to the scroll, 1 = moves with the page (default: `1`) +- `speed`: number — visual speed relative to the scroll, 1 = moves with the page (default: `1`) - `perspective`: number — 3D perspective in px (default: `400`) - `range`: 'in' | 'out' | 'continuous' (default: `'in'`) diff --git a/packages/motion-presets/src/library/ongoing/Bounce.ts b/packages/motion-presets/src/library/ongoing/Bounce.ts index fa331c7c..fe8a1bc6 100644 --- a/packages/motion-presets/src/library/ongoing/Bounce.ts +++ b/packages/motion-presets/src/library/ongoing/Bounce.ts @@ -2,13 +2,14 @@ import type { TimeAnimationOptions, Bounce, AnimationExtraOptions, DomApi } from import { getEasing, getTimingFactor, + holdAtRest, toKeyframeValue, parseLengthOrCalc, scaleLength, roundNumber, } from '../../utils'; -const PEAK_TRANSLATE = 49; +const PEAK_TRANSLATE = 98; export const schema = { iterationDelay: { type: 'number', min: 0, default: 0 }, @@ -49,15 +50,20 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW const easing = getEasing('sineOut'); - const custom = { - [`--motion-bounce-unit${suffix}`]: scaleLength(travel, 1 / PEAK_TRANSLATE, roundUnit), - }; + const custom: Record = {}; - const keyframes = TRANSLATE_Y_KEYFRAMES.map(({ keyframe, translateY }) => ({ - offset: (keyframe / 100) * timingFactor, - translate: `0px calc(${translateY / 2} * ${toKeyframeValue(custom, `--motion-bounce-unit${suffix}`, asWeb)})`, - easing, - })); + const keyframes = TRANSLATE_Y_KEYFRAMES.map(({ keyframe, translateY }, index) => { + const translateYKey = `--motion-bounce-y${suffix}-${index}`; + if (translateY) { + custom[translateYKey] = scaleLength(travel, translateY / PEAK_TRANSLATE, roundUnit); + } + + return { + offset: keyframe / 100, + translate: `0px ${translateY ? toKeyframeValue(custom, translateYKey, asWeb) : '0px'}`, + easing, + }; + }); return [ { @@ -66,7 +72,7 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW easing: 'linear', duration: duration + iterationDelay, custom, - keyframes, + keyframes: holdAtRest(keyframes, timingFactor), }, ]; } diff --git a/packages/motion-presets/src/library/ongoing/Breathe.ts b/packages/motion-presets/src/library/ongoing/Breathe.ts index a29ae628..079051ad 100644 --- a/packages/motion-presets/src/library/ongoing/Breathe.ts +++ b/packages/motion-presets/src/library/ongoing/Breathe.ts @@ -4,9 +4,11 @@ import { getEasing, getEasingFamily, getTimingFactor, + holdAtRest, toKeyframeValue, parseDirection, parseLengthOrCalc, + getEasingKey, } from '../../utils'; import { AXIS_DIRECTIONS } from '../../consts'; @@ -25,14 +27,6 @@ const DIRECTION_MAP = { center: { x: 0, y: 0, z: 1 }, }; -const FACTORS_SEQUENCE = [ - { translateFactor: 1, timeFactor: 0.1 }, - { translateFactor: -1, timeFactor: 0.302 }, - { translateFactor: 1, timeFactor: 0.504 }, - { translateFactor: -0.7, timeFactor: 0.705 }, - { translateFactor: 0.6, timeFactor: 0.839 }, -]; - export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom?: DomApi) { return style(options, true); } @@ -82,19 +76,20 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW )}`; const breatheDistance = `${toKeyframeValue(custom, `--motion-breathe-distance${suffix}`, asWeb)}`; - const keyframes = - timingFactor < 1 - ? FACTORS_SEQUENCE.map(({ translateFactor, timeFactor }) => { - const keyframeOffset = timeFactor * timingFactor; - const distancePart = `${breatheDistance} * ${translateFactor}`; - - return { - offset: keyframeOffset, - easing: getEasing(ease.inOut), - transform: `${breathePerspective} translate3d(calc(${breatheX} * ${distancePart}), calc(${breatheY} * ${distancePart}), calc(${breatheZ} * ${distancePart})) rotateZ(var(--motion-rotate, 0deg))`, - }; - }) - : [ + return [ + { + ...options, + name, + easing: 'linear', + duration: totalDuration, + custom, + keyframes: holdAtRest( + [ + { + offset: 0, + easing: getEasing(ease.out), + transform: `${breathePerspective} translate3d(0, 0, 0) rotateZ(var(--motion-rotate, 0deg))`, + }, { offset: 0.25, easing: getEasing(ease.inOut), @@ -105,27 +100,13 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW easing: getEasing(ease.in), transform: `${breathePerspective} translate3d(calc(${breatheX} * -1 * ${breatheDistance}), calc(${breatheY} * -1 * ${breatheDistance}), calc(${breatheZ} * -1 * ${breatheDistance})) rotateZ(var(--motion-rotate, 0deg))`, }, - ]; - - return [ - { - ...options, - name, - easing: 'linear', - duration: totalDuration, - custom, - keyframes: [ - { - offset: 0, - easing: getEasing(ease.out), - transform: `${breathePerspective} translate3d(0, 0, 0) rotateZ(var(--motion-rotate, 0deg))`, - }, - ...keyframes, - { - offset: 1, - transform: `${breathePerspective} translate3d(0, 0, 0) rotateZ(var(--motion-rotate, 0deg))`, - }, - ], + { + offset: 1, + transform: `${breathePerspective} translate3d(0, 0, 0) rotateZ(var(--motion-rotate, 0deg))`, + }, + ], + timingFactor, + ), }, ]; } @@ -135,6 +116,7 @@ export function getNames(options: TimeAnimationOptions & AnimationExtraOptions) (options.namedEffect as Breathe)?.iterationDelay || schema.iterationDelay.default; const timingFactor = getTimingFactor(options.duration!, iterationDelay, true); const { suffix = '' } = options; + const easingKey = getEasingKey(options.easing); - return [`motion-breathe-${timingFactor}`].map((name) => name + suffix); + return [`motion-breathe-${timingFactor}`].map((name) => name + easingKey + suffix); } diff --git a/packages/motion-presets/src/library/ongoing/Cross.ts b/packages/motion-presets/src/library/ongoing/Cross.ts index ee7a54fb..4e533fe0 100644 --- a/packages/motion-presets/src/library/ongoing/Cross.ts +++ b/packages/motion-presets/src/library/ongoing/Cross.ts @@ -8,7 +8,13 @@ import type { OffsetByDirectionParams, TimeAnimationOptions, } from '../../types'; -import { getElementOffset, getTimingFactor, parseDirection } from '../../utils'; +import { + getElementOffset, + getTimingFactor, + holdAtRest, + parseDirection, + toKeyframeValue, +} from '../../utils'; export const schema = { direction: { type: 'enum', values: EIGHT_DIRECTIONS, default: 'right' }, @@ -116,62 +122,13 @@ function generateTranslate(direction: keyof typeof FOUR_CORNERS_TRANSLATIONS) { }; } -export function web(options: TimeAnimationOptions & AnimationExtraOptions, dom?: DomApi) { - const namedEffect = options.namedEffect as Cross; - const direction = parseDirection( - namedEffect?.direction, - EIGHT_DIRECTIONS, - schema.direction.default, - ); - const duration = options.duration || 1; - const iterationDelay = namedEffect?.iterationDelay || schema.iterationDelay.default; - const timingFactor = getTimingFactor(duration, iterationDelay) as number; - const [name] = getNames(options); - - // Create CSS custom properties for the Cross configuration - const custom: Record = { - '--motion-left': '0px', - '--motion-top': '0px', - '--motion-width': '100%', - '--motion-height': '100%', - '--motion-parent-width': '100vw', - '--motion-parent-height': '100vh', - }; - - let from, to; - if (direction in TRANSLATE_BY_DIRECTION_MAP) { - from = TRANSLATE_BY_DIRECTION_MAP[direction as keyof typeof TRANSLATE_BY_DIRECTION_MAP].from; - to = TRANSLATE_BY_DIRECTION_MAP[direction as keyof typeof TRANSLATE_BY_DIRECTION_MAP].to; - } else { - const cornerFromTo = generateTranslate(direction as keyof typeof FOUR_CORNERS_TRANSLATIONS); - from = cornerFromTo.from; - to = cornerFromTo.to; - } - - const keyframes = [ - { - offset: 0, - translate: '0 0', - }, - { - offset: 0, - translate: to, - easing: 'step-start', - }, - { - offset: 0, - translate: from, - }, - { - offset: timingFactor, - translate: '0 0', - }, - { - offset: 1, - translate: '0 0', - }, - ]; +const WRAP_GAP = 0.0001; +function measure( + direction: EffectEightDirections, + dom: DomApi, + onMeasure?: (wrap: number) => void, +) { let left = 0; let top = 0; let width = 0; @@ -179,48 +136,121 @@ export function web(options: TimeAnimationOptions & AnimationExtraOptions, dom?: let parentWidth = 0; let parentHeight = 0; + dom.measure((target) => { + if (!target) { + return; + } + + const { width: targetWidth, height: targetHeight } = target.getBoundingClientRect(); + const parent = target.offsetParent as HTMLElement; + const parentRect = parent?.getBoundingClientRect() || ({} as DOMRect); + const offset = getElementOffset(target, parent); + + left = offset.left; + top = offset.top; + width = targetWidth; + height = targetHeight; + parentWidth = parentRect.width; + parentHeight = parentRect.height; + + onMeasure?.( + getOffsetByDirection(direction, { + left, + top, + width, + height, + parentWidth, + parentHeight, + }), + ); + }); + + dom.mutate((target) => { + target?.style.setProperty('--motion-left', `${left}px`); + target?.style.setProperty('--motion-top', `${top}px`); + target?.style.setProperty('--motion-width', `${width}px`); + target?.style.setProperty('--motion-height', `${height}px`); + target?.style.setProperty('--motion-parent-width', `${parentWidth}px`); + target?.style.setProperty('--motion-parent-height', `${parentHeight}px`); + }); +} + +function getDirection(options: TimeAnimationOptions & AnimationExtraOptions) { + return parseDirection( + (options.namedEffect as Cross)?.direction, + EIGHT_DIRECTIONS, + schema.direction.default, + ); +} + +export function prepare(options: TimeAnimationOptions & AnimationExtraOptions, dom?: DomApi) { if (dom) { - // @wix/motion reads the keyframes before measuring, so the wrap offset is updated in place - dom.measure((target) => { - if (!target) { - return; - } - - const { width: targetWidth, height: targetHeight } = target.getBoundingClientRect(); - const parent = target.offsetParent as HTMLElement; - const parentRect = parent?.getBoundingClientRect() || ({} as DOMRect); - const offset = getElementOffset(target, parent); - - left = offset.left; - top = offset.top; - width = targetWidth; - height = targetHeight; - parentWidth = parentRect.width; - parentHeight = parentRect.height; - - const toDurationOffset = - getOffsetByDirection(direction, { - left, - top, - width, - height, - parentWidth, - parentHeight, - }) * timingFactor; - keyframes[1].offset = toDurationOffset; - keyframes[2].offset = toDurationOffset; - }); + measure(getDirection(options), dom); + } +} - dom.mutate((target) => { - target?.style.setProperty('--motion-left', `${left}px`); - target?.style.setProperty('--motion-top', `${top}px`); - target?.style.setProperty('--motion-width', `${width}px`); - target?.style.setProperty('--motion-height', `${height}px`); - target?.style.setProperty('--motion-parent-width', `${parentWidth}px`); - target?.style.setProperty('--motion-parent-height', `${parentHeight}px`); +export function web(options: TimeAnimationOptions & AnimationExtraOptions, dom?: DomApi) { + const animations = style(options, true); + const timingFactor = getTimingFactor( + options.duration || 1, + (options.namedEffect as Cross)?.iterationDelay || schema.iterationDelay.default, + ) as number; + + if (dom) { + const { keyframes } = animations[0]; + // @wix/motion reads the keyframes before measuring, so the wrap offset is updated in place + measure(getDirection(options), dom, (wrap) => { + keyframes[1].offset = wrap * timingFactor; + keyframes[2].offset = Math.min(wrap + WRAP_GAP, 1) * timingFactor; }); } + return animations; +} + +// unmeasured, the wrap is halfway - the distances to both sides are assumed equal +export function style(options: TimeAnimationOptions & AnimationExtraOptions, asWeb = false) { + const { suffix = '' } = options; + const namedEffect = options.namedEffect as Cross; + const direction = getDirection(options); + const duration = options.duration || 1; + const iterationDelay = namedEffect?.iterationDelay || schema.iterationDelay.default; + const timingFactor = getTimingFactor(duration, iterationDelay) as number; + const [name] = getNames(options); + + const { from, to } = + direction in TRANSLATE_BY_DIRECTION_MAP + ? TRANSLATE_BY_DIRECTION_MAP[direction as keyof typeof TRANSLATE_BY_DIRECTION_MAP] + : generateTranslate(direction as keyof typeof FOUR_CORNERS_TRANSLATIONS); + + const custom: Record = { + [`--motion-cross-from${suffix}`]: from, + [`--motion-cross-to${suffix}`]: to, + }; + + const keyframes = holdAtRest( + [ + { + offset: 0, + translate: '0 0', + }, + { + offset: 0.5, + translate: toKeyframeValue(custom, `--motion-cross-to${suffix}`, asWeb), + easing: 'step-start', + }, + { + offset: 0.5 + WRAP_GAP, + translate: toKeyframeValue(custom, `--motion-cross-from${suffix}`, asWeb), + }, + { + offset: 1, + translate: '0 0', + }, + ], + timingFactor, + ); + return [ { ...options, @@ -233,6 +263,11 @@ export function web(options: TimeAnimationOptions & AnimationExtraOptions, dom?: ]; } -export function getNames({ suffix = '' }: TimeAnimationOptions & AnimationExtraOptions) { - return ['motion-cross'].map((name) => name + suffix); +export function getNames(options: TimeAnimationOptions & AnimationExtraOptions) { + const iterationDelay = + (options.namedEffect as Cross)?.iterationDelay || schema.iterationDelay.default; + const timingFactor = getTimingFactor(options.duration!, iterationDelay, true); + const { suffix = '' } = options; + + return [`motion-cross-${timingFactor}`].map((name) => name + suffix); } diff --git a/packages/motion-presets/src/library/ongoing/Flash.ts b/packages/motion-presets/src/library/ongoing/Flash.ts index 1d6f4e36..a852a777 100644 --- a/packages/motion-presets/src/library/ongoing/Flash.ts +++ b/packages/motion-presets/src/library/ongoing/Flash.ts @@ -1,5 +1,5 @@ import type { TimeAnimationOptions, DomApi, AnimationExtraOptions, Flash } from '../../types'; -import { getEasing, getTimingFactor } from '../../utils'; +import { getEasing, getTimingFactor, holdAtRest, getEasingKey } from '../../utils'; export const schema = { iterationDelay: { type: 'number', min: 0, default: 0 }, @@ -24,14 +24,10 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, _as easing, }, { - offset: 0.5 * timingFactor, + offset: 0.5, opacity: 0, easing, }, - { - offset: timingFactor, - opacity: 1, - }, { offset: 1, opacity: 1, @@ -44,7 +40,7 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, _as name, easing: 'linear', duration: duration + iterationDelay, - keyframes, + keyframes: holdAtRest(keyframes, timingFactor), }, ]; } @@ -54,6 +50,7 @@ export function getNames(options: TimeAnimationOptions & AnimationExtraOptions) (options.namedEffect as Flash)?.iterationDelay || schema.iterationDelay.default; const timingFactor = getTimingFactor(options.duration!, iterationDelay, true); const { suffix = '' } = options; + const easingKey = getEasingKey(options.easing); - return [`motion-flash-${timingFactor}`].map((name) => name + suffix); + return [`motion-flash-${timingFactor}`].map((name) => name + easingKey + suffix); } diff --git a/packages/motion-presets/src/library/ongoing/Flip.ts b/packages/motion-presets/src/library/ongoing/Flip.ts index 063079ae..3379d9f2 100644 --- a/packages/motion-presets/src/library/ongoing/Flip.ts +++ b/packages/motion-presets/src/library/ongoing/Flip.ts @@ -1,5 +1,12 @@ import type { AnimationExtraOptions, DomApi, Flip, TimeAnimationOptions } from '../../types'; -import { getEasing, getTimingFactor, toKeyframeValue, parseDirection } from '../../utils'; +import { + getEasing, + getTimingFactor, + holdAtRest, + toKeyframeValue, + parseDirection, + getEasingKey, +} from '../../utils'; import { AXIS_DIRECTIONS } from '../../consts'; export const schema = { @@ -29,7 +36,7 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW const duration = options.duration || 1; const iterationDelay = namedEffect?.iterationDelay || schema.iterationDelay.default; - const offset = getTimingFactor(duration, iterationDelay) as number; + const timingFactor = getTimingFactor(duration, iterationDelay) as number; const [name] = getNames(options); const rotationAxes = DIRECTION_MAP[direction]; @@ -60,21 +67,20 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW easing: 'linear', duration: duration + iterationDelay, custom, - keyframes: [ - { - offset: 0, - transform: `perspective(${toKeyframeValue(custom, `--motion-perspective${suffix}`, asWeb)}) rotateZ(var(--motion-rotate, 0deg)) ${rotateStart}`, - easing: getEasing(easing), - }, - { - offset, - transform: `perspective(${toKeyframeValue(custom, `--motion-perspective${suffix}`, asWeb)}) rotateZ(var(--motion-rotate, 0deg)) ${rotateEnd}`, - }, - { - offset: 1, - transform: `perspective(${toKeyframeValue(custom, `--motion-perspective${suffix}`, asWeb)}) rotateZ(var(--motion-rotate, 0deg)) ${rotateEnd}`, - }, - ], + keyframes: holdAtRest( + [ + { + offset: 0, + transform: `perspective(${toKeyframeValue(custom, `--motion-perspective${suffix}`, asWeb)}) rotateZ(var(--motion-rotate, 0deg)) ${rotateStart}`, + easing: getEasing(easing), + }, + { + offset: 1, + transform: `perspective(${toKeyframeValue(custom, `--motion-perspective${suffix}`, asWeb)}) rotateZ(var(--motion-rotate, 0deg)) ${rotateEnd}`, + }, + ], + timingFactor, + ), }, ]; } @@ -84,6 +90,7 @@ export function getNames(options: TimeAnimationOptions & AnimationExtraOptions) (options.namedEffect as Flip)?.iterationDelay || schema.iterationDelay.default; const timingFactor = getTimingFactor(options.duration!, iterationDelay, true); const { suffix = '' } = options; + const easingKey = getEasingKey(options.easing); - return [`motion-flip-${timingFactor}`].map((name) => name + suffix); + return [`motion-flip-${timingFactor}`].map((name) => name + easingKey + suffix); } diff --git a/packages/motion-presets/src/library/ongoing/Fold.ts b/packages/motion-presets/src/library/ongoing/Fold.ts index 2536752e..6903825f 100644 --- a/packages/motion-presets/src/library/ongoing/Fold.ts +++ b/packages/motion-presets/src/library/ongoing/Fold.ts @@ -9,8 +9,10 @@ import { getEasing, getEasingFamily, getTimingFactor, + holdAtRest, toKeyframeValue, parseDirection, + getEasingKey, } from '../../utils'; import { FOUR_DIRECTIONS } from '../../consts'; @@ -40,16 +42,6 @@ const DIRECTION_MAP = { }, }; -const KEYFRAME_FACTORS = [ - { fold: 1, frameFactor: 0.1 }, - { fold: -0.7, frameFactor: 0.302 }, - { fold: 0.6, frameFactor: 0.504 }, - { fold: -0.3, frameFactor: 0.686 }, - { fold: 0.2, frameFactor: 0.847 }, - { fold: -0.05, frameFactor: 1.049 }, - { fold: 0, frameFactor: 1.189 }, -]; - export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom?: DomApi) { return style(options, true); } @@ -109,17 +101,22 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW asWeb, )} * ${value} * ${angle_})) ${transformRight}`; - const keyframes = - timingFactor < 1 - ? KEYFRAME_FACTORS.map(({ fold, frameFactor }) => { - const keyframeOffset = frameFactor * timingFactor; - return { - offset: keyframeOffset, - easing: getEasing('sineInOut'), - transform: getTransform(fold), - }; - }) - : [ + const transform_0 = getTransform(0); + + return [ + { + ...options, + name, + easing: 'linear', + duration: totalDuration, + custom, + keyframes: holdAtRest( + [ + { + offset: 0, + easing: getEasing(ease.out), + transform: transform_0, + }, { offset: 0.25, easing: getEasing(ease.inOut), @@ -130,29 +127,13 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW easing: getEasing(ease.in), transform: getTransform(-1), }, - ]; - - const transform_0 = getTransform(0); - - return [ - { - ...options, - name, - easing: 'linear', - duration: totalDuration, - custom, - keyframes: [ - { - offset: 0, - easing: getEasing(ease.out), - transform: transform_0, - }, - ...keyframes, - { - offset: 1, - transform: transform_0, - }, - ], + { + offset: 1, + transform: transform_0, + }, + ], + timingFactor, + ), }, ]; } @@ -163,12 +144,13 @@ export function getNames(options: TimeAnimationOptions & AnimationExtraOptions) (options.namedEffect as Fold)?.iterationDelay || schema.iterationDelay.default ); const { suffix = '' } = options; + const easingKey = getEasingKey(options.easing); if (!iterationDelay) { - return ['motion-fold'].map((name) => name + suffix); + return ['motion-fold'].map((name) => name + easingKey + suffix); } const timingFactor = getTimingFactor(duration, iterationDelay, true) as string; - return [`motion-fold-${timingFactor}`].map((name) => name + suffix); + return [`motion-fold-${timingFactor}`].map((name) => name + easingKey + suffix); } diff --git a/packages/motion-presets/src/library/ongoing/Jello.ts b/packages/motion-presets/src/library/ongoing/Jello.ts index af81de9c..241c2be5 100644 --- a/packages/motion-presets/src/library/ongoing/Jello.ts +++ b/packages/motion-presets/src/library/ongoing/Jello.ts @@ -1,7 +1,5 @@ import type { Jello, TimeAnimationOptions, DomApi, AnimationExtraOptions } from '../../types'; -import { getTimingFactor, toKeyframeValue } from '../../utils'; - -const PEAK_SKEW = 7; +import { getTimingFactor, holdAtRest, roundNumber, toKeyframeValue } from '../../utils'; export const schema = { iterationDelay: { type: 'number', min: 0, default: 0 }, @@ -9,10 +7,10 @@ export const schema = { } as const; const SKEW_Y_KEYFRAMES = [ - { keyframe: 24, skewY: 7 }, - { keyframe: 38, skewY: -2 }, - { keyframe: 58, skewY: 4 }, - { keyframe: 80, skewY: -2 }, + { keyframe: 24, skewY: 1 }, + { keyframe: 38, skewY: -2 / 7 }, + { keyframe: 58, skewY: 4 / 7 }, + { keyframe: 80, skewY: -2 / 7 }, { keyframe: 100, skewY: 0 }, ]; @@ -30,23 +28,20 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW const [name] = getNames(options); const timingFactor = getTimingFactor(duration, iterationDelay) as number; - const jelloFactor = skew / PEAK_SKEW; - // Create CSS custom properties for the jello configuration - const custom: Record = { - [`--motion-skew-y${suffix}`]: jelloFactor, - }; + const custom: Record = {}; const keyframes = SKEW_Y_KEYFRAMES.map(({ keyframe, skewY }) => { - const offset = (keyframe / 100) * timingFactor; + const skewKey = `--motion-skew-y${suffix}-${keyframe}`; + custom[skewKey] = `${roundNumber(skewY * skew, 4)}deg`; return { - offset, - transform: `rotateZ(var(--motion-rotate, 0deg)) skewY(calc(${toKeyframeValue( + offset: keyframe / 100, + transform: `rotateZ(var(--motion-rotate, 0deg)) skewY(${toKeyframeValue( custom, - `--motion-skew-y${suffix}`, + skewKey, asWeb, - )} * ${skewY}deg))`, + )})`, }; }); @@ -57,7 +52,7 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW easing: 'linear', duration: duration + iterationDelay, custom, - keyframes, + keyframes: holdAtRest(keyframes, timingFactor), }, ]; } diff --git a/packages/motion-presets/src/library/ongoing/Poke.ts b/packages/motion-presets/src/library/ongoing/Poke.ts index 513b6716..eec9d3f2 100644 --- a/packages/motion-presets/src/library/ongoing/Poke.ts +++ b/packages/motion-presets/src/library/ongoing/Poke.ts @@ -9,6 +9,7 @@ import { scaleLength, roundNumber, getTimingFactor, + holdAtRest, toKeyframeValue, parseDirection, parseLengthOrCalc, @@ -22,17 +23,15 @@ export const schema = { } as const; const TRANSLATE_KEYFRAMES = [ - { keyframe: 17, translate: 7 }, - { keyframe: 32, translate: 25 }, - { keyframe: 48, translate: 8 }, - { keyframe: 56, translate: 11 }, - { keyframe: 66, translate: 25 }, - { keyframe: 83, translate: 4 }, + { keyframe: 17, translate: 0.28 }, + { keyframe: 32, translate: 1 }, + { keyframe: 48, translate: 0.32 }, + { keyframe: 56, translate: 0.44 }, + { keyframe: 66, translate: 1 }, + { keyframe: 83, translate: 0.16 }, { keyframe: 100, translate: 0 }, ]; -const PEAK_TRANSLATE = 25; - const DIRECTION_MAP = { top: { x: 0, y: -1 }, bottom: { x: 0, y: 1 }, @@ -62,30 +61,32 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW const timingFactor = getTimingFactor(duration, iterationDelay) as number; const [name] = getNames(options); - const pokeUnit = scaleLength(travel, 1 / PEAK_TRANSLATE, roundUnit); - // Create CSS custom properties for the poke configuration const custom: Record = { [`--motion-translate-x${suffix}`]: x, [`--motion-translate-y${suffix}`]: y, - [`--motion-poke-unit${suffix}`]: pokeUnit, }; - const pokeUnit_ = toKeyframeValue(custom, `--motion-poke-unit${suffix}`, asWeb); const keyframes = TRANSLATE_KEYFRAMES.map(({ keyframe, translate }) => { - const translateValue = `calc(${toKeyframeValue( - custom, - `--motion-translate-x${suffix}`, - asWeb, - )} * ${translate} * ${pokeUnit_}) calc(${toKeyframeValue( - custom, - `--motion-translate-y${suffix}`, - asWeb, - )} * ${translate} * ${pokeUnit_})`; + const travelKey = `--motion-poke-travel${suffix}-${keyframe}`; + if (translate) { + custom[travelKey] = scaleLength(travel, translate, roundUnit); + } + const travel_ = toKeyframeValue(custom, travelKey, asWeb); return { - offset: (keyframe / 100) * timingFactor, - translate: translateValue, + offset: keyframe / 100, + translate: translate + ? `calc(${toKeyframeValue( + custom, + `--motion-translate-x${suffix}`, + asWeb, + )} * ${travel_}) calc(${toKeyframeValue( + custom, + `--motion-translate-y${suffix}`, + asWeb, + )} * ${travel_})` + : '0px 0px', }; }); @@ -96,7 +97,7 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW easing: 'linear', duration: duration + iterationDelay, custom, - keyframes, + keyframes: holdAtRest(keyframes, timingFactor), }, ]; } diff --git a/packages/motion-presets/src/library/ongoing/Pulse.ts b/packages/motion-presets/src/library/ongoing/Pulse.ts index 8148f13d..6eefd6c6 100644 --- a/packages/motion-presets/src/library/ongoing/Pulse.ts +++ b/packages/motion-presets/src/library/ongoing/Pulse.ts @@ -1,5 +1,5 @@ import type { Pulse, TimeAnimationOptions, DomApi, AnimationExtraOptions } from '../../types'; -import { getTimingFactor, roundNumber, toKeyframeValue } from '../../utils'; +import { getTimingFactor, holdAtRest, roundNumber, toKeyframeValue } from '../../utils'; export const schema = { iterationDelay: { type: 'number', min: 0, default: 0 }, @@ -7,10 +7,10 @@ export const schema = { } as const; const SCALE_KEYFRAMES = [ - { keyframe: 27, scale: 0.96 }, - { keyframe: 45, scale: 1 }, - { keyframe: 72, scale: 0.93 }, - { keyframe: 100, scale: 1 }, + { keyframe: 27, depth: 4 / 7 }, + { keyframe: 45, depth: 0 }, + { keyframe: 72, depth: 1 }, + { keyframe: 100, depth: 0 }, ]; export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom?: DomApi) { @@ -20,40 +20,29 @@ export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom? export function style(options: TimeAnimationOptions & AnimationExtraOptions, asWeb = false) { const { suffix = '' } = options; const namedEffect = options.namedEffect as Pulse; - const { scale: minScale = schema.scale.default } = namedEffect; + const { scale = schema.scale.default } = namedEffect; const duration = options.duration || 1; const iterationDelay = namedEffect?.iterationDelay || schema.iterationDelay.default; const timingFactor = getTimingFactor(duration, iterationDelay) as number; const [name] = getNames(options); - const pulseOffset = roundNumber(schema.scale.default - minScale, 4); + const custom: Record = {}; - // Create CSS custom properties for the pulse configuration - const custom: Record = { - [`--motion-pulse-offset${suffix}`]: pulseOffset, - }; - - const keyframes = SCALE_KEYFRAMES.map(({ keyframe, scale }) => { - const offset = (keyframe / 100) * timingFactor; + const keyframes = SCALE_KEYFRAMES.map(({ keyframe, depth }) => { + const scaleKey = `--motion-scale${suffix}-${keyframe}`; + if (depth) { + custom[scaleKey] = roundNumber(scale + (1 - depth) * (1 - scale), 4); + } return { - offset, - transform: `scale(${ - scale < 1 - ? `calc(${scale} - ${toKeyframeValue(custom, `--motion-pulse-offset${suffix}`, asWeb)})` - : '1' + offset: keyframe / 100, + transform: `rotateZ(var(--motion-rotate, 0deg)) scale(${ + depth ? toKeyframeValue(custom, scaleKey, asWeb) : '1' })`, }; }); - if (timingFactor < 1) { - keyframes.push({ - offset: 1, - transform: 'scale(1)', - }); - } - return [ { ...options, @@ -61,7 +50,7 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW easing: 'linear', duration: duration + iterationDelay, custom, - keyframes, + keyframes: holdAtRest(keyframes, timingFactor), }, ]; } diff --git a/packages/motion-presets/src/library/ongoing/Rubber.ts b/packages/motion-presets/src/library/ongoing/Rubber.ts index f8fa496a..29bd211c 100644 --- a/packages/motion-presets/src/library/ongoing/Rubber.ts +++ b/packages/motion-presets/src/library/ongoing/Rubber.ts @@ -1,7 +1,5 @@ import type { Rubber, TimeAnimationOptions, DomApi, AnimationExtraOptions } from '../../types'; -import { getTimingFactor, roundNumber, toKeyframeValue } from '../../utils'; - -const BASE_STRETCH = 0.05; +import { getTimingFactor, holdAtRest, roundNumber, toKeyframeValue } from '../../utils'; export const schema = { iterationDelay: { type: 'number', min: 0, default: 0 }, @@ -9,12 +7,12 @@ export const schema = { } as const; const SCALE_KEYFRAMES = [ - { keyframe: 45, scaleX: 1.03, scaleY: 0.93 }, - { keyframe: 56, scaleX: 0.9, scaleY: 1.03 }, - { keyframe: 66, scaleX: 1.02, scaleY: 0.96 }, - { keyframe: 78, scaleX: 0.98, scaleY: 1.02 }, - { keyframe: 89, scaleX: 1.005, scaleY: 0.9995 }, - { keyframe: 100, scaleX: 1, scaleY: 1 }, + { keyframe: 45, squash: 1 }, + { keyframe: 56, squash: -0.9 }, + { keyframe: 66, squash: 0.8 }, + { keyframe: 78, squash: -0.45 }, + { keyframe: 89, squash: 0.5275 }, + { keyframe: 100, squash: 0 }, ]; export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom?: DomApi) { @@ -31,18 +29,12 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW const timingFactor = getTimingFactor(duration, iterationDelay) as number; const [name] = getNames(options); - const rubberOffset = stretch - BASE_STRETCH; - // Create CSS custom properties for the rubber configuration const custom: Record = {}; - const keyframes = SCALE_KEYFRAMES.map(({ keyframe, scaleX, scaleY }, index) => { - const isLastKeyframe = index === SCALE_KEYFRAMES.length - 1; - const isEvenKeyframe = index % 2 === 0; - const offset = rubberOffset * (isLastKeyframe ? 0 : isEvenKeyframe ? 1 : -0.5); - - const adjustedScaleX = roundNumber(scaleX + offset, 4); - const adjustedScaleY = roundNumber(scaleY - offset, 4); + const keyframes = SCALE_KEYFRAMES.map(({ keyframe, squash }) => { + const adjustedScaleX = roundNumber(1 + squash * stretch, 4); + const adjustedScaleY = roundNumber(1 - squash * stretch, 4); // Create custom property keys for this keyframe const scaleXKey = `--motion-scale-x${suffix}-${keyframe}`; @@ -53,7 +45,7 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW custom[scaleYKey] = adjustedScaleY; return { - offset: (keyframe / 100) * timingFactor, + offset: keyframe / 100, transform: `rotateZ(var(--motion-rotate, 0deg)) scale(${toKeyframeValue( custom, scaleXKey, @@ -69,7 +61,7 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW easing: 'linear', duration: duration + iterationDelay, custom, - keyframes, + keyframes: holdAtRest(keyframes, timingFactor), }, ]; } diff --git a/packages/motion-presets/src/library/ongoing/Spin.ts b/packages/motion-presets/src/library/ongoing/Spin.ts index 0d4134a0..107a6ea7 100644 --- a/packages/motion-presets/src/library/ongoing/Spin.ts +++ b/packages/motion-presets/src/library/ongoing/Spin.ts @@ -1,5 +1,12 @@ import type { AnimationExtraOptions, DomApi, Spin, TimeAnimationOptions } from '../../types'; -import { getEasing, getTimingFactor, toKeyframeValue, parseDirection } from '../../utils'; +import { + getEasing, + getTimingFactor, + holdAtRest, + toKeyframeValue, + parseDirection, + getEasingKey, +} from '../../utils'; import { SPIN_DIRECTIONS } from '../../consts'; export const schema = { @@ -35,7 +42,7 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW const transformRotate = (DIRECTION_MAP[direction] > 0 ? 1 : -1) * 360; const custom = { - [`--motion-rotate-start${suffix}`]: `calc(var(--motion-rotate, 0deg) + ${transformRotate}deg)`, + [`--motion-rotate-start${suffix}`]: `${transformRotate}deg`, }; return [ @@ -45,17 +52,20 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW easing: 'linear', duration: duration + iterationDelay, custom, - keyframes: [ - { - offset: 0, - easing: getEasing(easing), - rotate: toKeyframeValue(custom, `--motion-rotate-start${suffix}`, asWeb), - }, - { - offset: timingFactor, - rotate: `var(--motion-rotate, 0deg)`, - }, - ], + keyframes: holdAtRest( + [ + { + offset: 0, + easing: getEasing(easing), + rotate: toKeyframeValue(custom, `--motion-rotate-start${suffix}`, asWeb), + }, + { + offset: 1, + rotate: '0deg', + }, + ], + timingFactor, + ), }, ]; } @@ -65,6 +75,7 @@ export function getNames(options: TimeAnimationOptions & AnimationExtraOptions) (options.namedEffect as Spin)?.iterationDelay || schema.iterationDelay.default; const timingFactor = getTimingFactor(options.duration!, iterationDelay, true); const { suffix = '' } = options; + const easingKey = getEasingKey(options.easing); - return [`motion-spin-${timingFactor}`].map((name) => name + suffix); + return [`motion-spin-${timingFactor}`].map((name) => name + easingKey + suffix); } diff --git a/packages/motion-presets/src/library/ongoing/Swing.ts b/packages/motion-presets/src/library/ongoing/Swing.ts index bd8d7662..07562cde 100644 --- a/packages/motion-presets/src/library/ongoing/Swing.ts +++ b/packages/motion-presets/src/library/ongoing/Swing.ts @@ -9,8 +9,10 @@ import { getEasing, getEasingFamily, getTimingFactor, + holdAtRest, toKeyframeValue, parseDirection, + getEasingKey, } from '../../utils'; import { FOUR_DIRECTIONS } from '../../consts'; @@ -29,16 +31,6 @@ const DIRECTION_MAP = { const TRANSLATE_DISTANCE = 50; -const FACTORS_SEQUENCE = [ - { factor: 1, timeFactor: 0.0934 }, - { factor: -1, timeFactor: 0.28 }, - { factor: 0.6, timeFactor: 0.466 }, - { factor: -0.3, timeFactor: 0.653 }, - { factor: 0.2, timeFactor: 0.839 }, - { factor: -0.05, timeFactor: 1.026 }, - { factor: 0, timeFactor: 1.175 }, -]; - export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom?: DomApi) { return style(options, true); } @@ -68,9 +60,6 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW [`--motion-swing-deg${suffix}`]: `${swing}deg`, [`--motion-trans-x${suffix}`]: `${x * TRANSLATE_DISTANCE}%`, [`--motion-trans-y${suffix}`]: `${y * TRANSLATE_DISTANCE}%`, - [`--motion-ease-in${suffix}`]: getEasing(ease.in), - [`--motion-ease-inout${suffix}`]: getEasing(ease.inOut), - [`--motion-ease-out${suffix}`]: getEasing(ease.out), }; const translateBefore = `translate(${toKeyframeValue( @@ -85,41 +74,7 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW asWeb, )} * -1), calc(${toKeyframeValue(custom, `--motion-trans-y${suffix}`, asWeb)} * -1))`; - const keyframes = - timingFactor < 1 - ? FACTORS_SEQUENCE.map(({ factor, timeFactor }) => { - const keyframeOffset = timeFactor * timingFactor; - - return { - offset: keyframeOffset, - easing: toKeyframeValue(custom, `--motion-ease-inout${suffix}`, asWeb), - transform: `rotate(var(--motion-rotate, 0deg)) ${translateBefore} rotate(calc(${toKeyframeValue( - custom, - `--motion-swing-deg${suffix}`, - asWeb, - )} * ${factor})) ${translateAfter}`, - }; - }) - : [ - { - offset: 0.25, - easing: toKeyframeValue(custom, `--motion-ease-inout${suffix}`, asWeb), - transform: `rotate(var(--motion-rotate, 0deg)) ${translateBefore} rotate(${toKeyframeValue( - custom, - `--motion-swing-deg${suffix}`, - asWeb, - )}) ${translateAfter}`, - }, - { - offset: 0.75, - easing: toKeyframeValue(custom, `--motion-ease-in${suffix}`, asWeb), - transform: `rotate(var(--motion-rotate, 0deg)) ${translateBefore} rotate(calc(${toKeyframeValue( - custom, - `--motion-swing-deg${suffix}`, - asWeb, - )} * -1)) ${translateAfter}`, - }, - ]; + const swing_ = toKeyframeValue(custom, `--motion-swing-deg${suffix}`, asWeb); return [ { @@ -128,18 +83,30 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW easing: 'linear', duration: totalDuration, custom, - keyframes: [ - { - offset: 0, - easing: toKeyframeValue(custom, `--motion-ease-out${suffix}`, asWeb), - transform: `rotateZ(var(--motion-rotate, 0deg)) ${translateBefore} rotate(0deg) ${translateAfter}`, - }, - ...keyframes, - { - offset: 1, - transform: `rotateZ(var(--motion-rotate, 0deg)) ${translateBefore} rotate(0deg) ${translateAfter}`, - }, - ], + keyframes: holdAtRest( + [ + { + offset: 0, + easing: getEasing(ease.out), + transform: `rotateZ(var(--motion-rotate, 0deg)) ${translateBefore} rotate(0deg) ${translateAfter}`, + }, + { + offset: 0.25, + easing: getEasing(ease.inOut), + transform: `rotate(var(--motion-rotate, 0deg)) ${translateBefore} rotate(${swing_}) ${translateAfter}`, + }, + { + offset: 0.75, + easing: getEasing(ease.in), + transform: `rotate(var(--motion-rotate, 0deg)) ${translateBefore} rotate(calc(${swing_} * -1)) ${translateAfter}`, + }, + { + offset: 1, + transform: `rotateZ(var(--motion-rotate, 0deg)) ${translateBefore} rotate(0deg) ${translateAfter}`, + }, + ], + timingFactor, + ), }, ]; } @@ -149,6 +116,7 @@ export function getNames(options: TimeAnimationOptions & AnimationExtraOptions) (options.namedEffect as Swing)?.iterationDelay || schema.iterationDelay.default; const timingFactor = getTimingFactor(options.duration!, iterationDelay, true); const { suffix = '' } = options; + const easingKey = getEasingKey(options.easing); - return [`motion-swing-${timingFactor}`].map((name) => name + suffix); + return [`motion-swing-${timingFactor}`].map((name) => name + easingKey + suffix); } diff --git a/packages/motion-presets/src/library/ongoing/Wiggle.ts b/packages/motion-presets/src/library/ongoing/Wiggle.ts index 5bb7f765..03549ed6 100644 --- a/packages/motion-presets/src/library/ongoing/Wiggle.ts +++ b/packages/motion-presets/src/library/ongoing/Wiggle.ts @@ -1,16 +1,13 @@ import type { Wiggle, TimeAnimationOptions, DomApi, AnimationExtraOptions } from '../../types'; import { - getCssUnits, getTimingFactor, + holdAtRest, roundNumber, toKeyframeValue, parseLengthOrCalc, scaleLength, } from '../../utils'; -const PEAK_ROTATE = 10; -const PEAK_TRANSLATE = 10; - export const schema = { angle: { type: 'number', default: 25 }, iterationDelay: { type: 'number', min: 0, default: 0 }, @@ -18,13 +15,15 @@ export const schema = { } as const; const TRANSFORM_KEYFRAMES = [ - { keyframe: 18, transY: -10, accRotate: 10 }, - { keyframe: 35, transY: 0, accRotate: -18 }, - { keyframe: 53, transY: 0, accRotate: 14 }, - { keyframe: 73, transY: 0, accRotate: -10 }, - { keyframe: 100, transY: 0, accRotate: 4 }, + { keyframe: 18, lift: 1, rotate: 1 }, + { keyframe: 35, lift: 0, rotate: -0.8 }, + { keyframe: 53, lift: 0, rotate: 0.6 }, + { keyframe: 73, lift: 0, rotate: -0.4 }, + { keyframe: 100, lift: 0, rotate: 0 }, ]; +const roundUnit = (value: number) => roundNumber(value, 6); + export function web(options: TimeAnimationOptions & AnimationExtraOptions, _dom?: DomApi) { return style(options, true); } @@ -39,36 +38,18 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW const timingFactor = getTimingFactor(duration, iterationDelay) as number; const [name] = getNames(options); - const wiggleFactor = angle / PEAK_ROTATE; - - let currentRotation = 0; - // Create CSS custom properties for the wiggle configuration - const custom: Record = { - [`--motion-wiggle-factor${suffix}`]: wiggleFactor, - }; - - const keyframes = TRANSFORM_KEYFRAMES.map(({ keyframe, transY, accRotate }) => { - const offset = (keyframe / 100) * timingFactor; - const rotateValue = `calc(var(--motion-rotate, 0deg) + ${roundNumber( - currentRotation + accRotate * wiggleFactor, - )}deg)`; - const translateYValue = - typeof travel === 'string' - ? scaleLength(travel, transY / PEAK_TRANSLATE) - : `${transY * (travel.value / PEAK_TRANSLATE)}${getCssUnits(travel.unit)}`; + const custom: Record = {}; + const keyframes = TRANSFORM_KEYFRAMES.map(({ keyframe, lift, rotate }) => { const rotateKey = `--motion-rotate${suffix}-${keyframe}`; const translateYKey = `--motion-translate-y${suffix}-${keyframe}`; - // For non-web usage, add the values to custom properties - custom[rotateKey] = rotateValue; - custom[translateYKey] = translateYValue; - - currentRotation += accRotate * wiggleFactor; + custom[rotateKey] = `calc(var(--motion-rotate, 0deg) + ${roundNumber(rotate * angle, 4)}deg)`; + custom[translateYKey] = lift ? scaleLength(travel, -lift, roundUnit) : '0px'; return { - offset, + offset: keyframe / 100, transform: `rotate(${toKeyframeValue( custom, rotateKey, @@ -84,7 +65,7 @@ export function style(options: TimeAnimationOptions & AnimationExtraOptions, asW easing: 'linear', duration: duration + iterationDelay, custom, - keyframes, + keyframes: holdAtRest(keyframes, timingFactor), }, ]; } diff --git a/packages/motion-presets/src/library/ongoing/test/Bounce.spec.ts b/packages/motion-presets/src/library/ongoing/test/Bounce.spec.ts index 30b55591..6f956e77 100644 --- a/packages/motion-presets/src/library/ongoing/test/Bounce.spec.ts +++ b/packages/motion-presets/src/library/ongoing/test/Bounce.spec.ts @@ -15,77 +15,86 @@ describe('Bounce', () => { const expectedResult = [ { id: 'test-id', - namedEffect: { type: 'Bounce' }, + namedEffect: { + type: 'Bounce', + }, name: 'motion-bounce-1', easing: 'linear', duration: 1, custom: { - '--motion-bounce-unit': '1px', + '--motion-bounce-y-1': '-27.5px', + '--motion-bounce-y-2': '-43.5px', + '--motion-bounce-y-3': '-49px', + '--motion-bounce-y-4': '-43.5px', + '--motion-bounce-y-5': '-27.5px', + '--motion-bounce-y-7': '-11.5px', + '--motion-bounce-y-9': '-2.5px', + '--motion-bounce-y-11': '-1px', }, keyframes: [ { offset: 0, - translate: '0px calc(0 * var(--motion-bounce-unit))', + translate: '0px 0px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.08800000000000001, - translate: '0px calc(-27.5 * var(--motion-bounce-unit))', + translate: '0px var(--motion-bounce-y-1)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.17600000000000002, - translate: '0px calc(-43.5 * var(--motion-bounce-unit))', + translate: '0px var(--motion-bounce-y-2)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.265, - translate: '0px calc(-49 * var(--motion-bounce-unit))', + translate: '0px var(--motion-bounce-y-3)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.353, - translate: '0px calc(-43.5 * var(--motion-bounce-unit))', + translate: '0px var(--motion-bounce-y-4)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.441, - translate: '0px calc(-27.5 * var(--motion-bounce-unit))', + translate: '0px var(--motion-bounce-y-5)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.531, - translate: '0px calc(0 * var(--motion-bounce-unit))', + translate: '0px 0px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.662, - translate: '0px calc(-11.5 * var(--motion-bounce-unit))', + translate: '0px var(--motion-bounce-y-7)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.81, - translate: '0px calc(0 * var(--motion-bounce-unit))', + translate: '0px 0px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.868, - translate: '0px calc(-2.5 * var(--motion-bounce-unit))', + translate: '0px var(--motion-bounce-y-9)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.941, - translate: '0px calc(0 * var(--motion-bounce-unit))', + translate: '0px 0px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.971, - translate: '0px calc(-1 * var(--motion-bounce-unit))', + translate: '0px var(--motion-bounce-y-11)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 1, - translate: '0px calc(0 * var(--motion-bounce-unit))', + translate: '0px 0px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, ], @@ -108,76 +117,92 @@ describe('Bounce', () => { { id: 'test-id', duration: 1500, - namedEffect: { type: 'Bounce', travel: 127.4, iterationDelay: 500 }, + namedEffect: { + type: 'Bounce', + travel: 127.4, + iterationDelay: 500, + }, name: 'motion-bounce-067', easing: 'linear', custom: { - '--motion-bounce-unit': '2.6px', + '--motion-bounce-y-1': '-71.5px', + '--motion-bounce-y-2': '-113.1px', + '--motion-bounce-y-3': '-127.4px', + '--motion-bounce-y-4': '-113.1px', + '--motion-bounce-y-5': '-71.5px', + '--motion-bounce-y-7': '-29.9px', + '--motion-bounce-y-9': '-6.5px', + '--motion-bounce-y-11': '-2.6px', }, keyframes: [ { offset: 0, - translate: '0px calc(0 * var(--motion-bounce-unit))', + translate: '0px 0px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.05896000000000001, - translate: '0px calc(-27.5 * var(--motion-bounce-unit))', + translate: '0px var(--motion-bounce-y-1)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.11792000000000002, - translate: '0px calc(-43.5 * var(--motion-bounce-unit))', + translate: '0px var(--motion-bounce-y-2)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.17755, - translate: '0px calc(-49 * var(--motion-bounce-unit))', + translate: '0px var(--motion-bounce-y-3)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.23651, - translate: '0px calc(-43.5 * var(--motion-bounce-unit))', + translate: '0px var(--motion-bounce-y-4)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.29547, - translate: '0px calc(-27.5 * var(--motion-bounce-unit))', + translate: '0px var(--motion-bounce-y-5)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.35577000000000003, - translate: '0px calc(0 * var(--motion-bounce-unit))', + translate: '0px 0px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.44354000000000005, - translate: '0px calc(-11.5 * var(--motion-bounce-unit))', + translate: '0px var(--motion-bounce-y-7)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.5427000000000001, - translate: '0px calc(0 * var(--motion-bounce-unit))', + translate: '0px 0px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.5815600000000001, - translate: '0px calc(-2.5 * var(--motion-bounce-unit))', + translate: '0px var(--motion-bounce-y-9)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.63047, - translate: '0px calc(0 * var(--motion-bounce-unit))', + translate: '0px 0px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.65057, - translate: '0px calc(-1 * var(--motion-bounce-unit))', + translate: '0px var(--motion-bounce-y-11)', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.67, - translate: '0px calc(0 * var(--motion-bounce-unit))', + translate: '0px 0px', + easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', + }, + { + offset: 1, + translate: '0px 0px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, ], @@ -200,77 +225,86 @@ describe('Bounce', () => { const expectedResult = [ { id: 'test-id', - namedEffect: { type: 'Bounce' }, + namedEffect: { + type: 'Bounce', + }, name: 'motion-bounce-1', easing: 'linear', duration: 1, custom: { - '--motion-bounce-unit': '1px', + '--motion-bounce-y-1': '-27.5px', + '--motion-bounce-y-2': '-43.5px', + '--motion-bounce-y-3': '-49px', + '--motion-bounce-y-4': '-43.5px', + '--motion-bounce-y-5': '-27.5px', + '--motion-bounce-y-7': '-11.5px', + '--motion-bounce-y-9': '-2.5px', + '--motion-bounce-y-11': '-1px', }, keyframes: [ { offset: 0, - translate: '0px calc(0 * 1px)', + translate: '0px 0px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.08800000000000001, - translate: '0px calc(-27.5 * 1px)', + translate: '0px -27.5px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.17600000000000002, - translate: '0px calc(-43.5 * 1px)', + translate: '0px -43.5px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.265, - translate: '0px calc(-49 * 1px)', + translate: '0px -49px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.353, - translate: '0px calc(-43.5 * 1px)', + translate: '0px -43.5px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.441, - translate: '0px calc(-27.5 * 1px)', + translate: '0px -27.5px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.531, - translate: '0px calc(0 * 1px)', + translate: '0px 0px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.662, - translate: '0px calc(-11.5 * 1px)', + translate: '0px -11.5px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.81, - translate: '0px calc(0 * 1px)', + translate: '0px 0px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.868, - translate: '0px calc(-2.5 * 1px)', + translate: '0px -2.5px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.941, - translate: '0px calc(0 * 1px)', + translate: '0px 0px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.971, - translate: '0px calc(-1 * 1px)', + translate: '0px -1px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 1, - translate: '0px calc(0 * 1px)', + translate: '0px 0px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, ], @@ -293,76 +327,92 @@ describe('Bounce', () => { { id: 'test-id', duration: 1500, - namedEffect: { type: 'Bounce', travel: 127.4, iterationDelay: 500 }, + namedEffect: { + type: 'Bounce', + travel: 127.4, + iterationDelay: 500, + }, name: 'motion-bounce-067', easing: 'linear', custom: { - '--motion-bounce-unit': '2.6px', + '--motion-bounce-y-1': '-71.5px', + '--motion-bounce-y-2': '-113.1px', + '--motion-bounce-y-3': '-127.4px', + '--motion-bounce-y-4': '-113.1px', + '--motion-bounce-y-5': '-71.5px', + '--motion-bounce-y-7': '-29.9px', + '--motion-bounce-y-9': '-6.5px', + '--motion-bounce-y-11': '-2.6px', }, keyframes: [ { offset: 0, - translate: '0px calc(0 * 2.6px)', + translate: '0px 0px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.05896000000000001, - translate: '0px calc(-27.5 * 2.6px)', + translate: '0px -71.5px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.11792000000000002, - translate: '0px calc(-43.5 * 2.6px)', + translate: '0px -113.1px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.17755, - translate: '0px calc(-49 * 2.6px)', + translate: '0px -127.4px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.23651, - translate: '0px calc(-43.5 * 2.6px)', + translate: '0px -113.1px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.29547, - translate: '0px calc(-27.5 * 2.6px)', + translate: '0px -71.5px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.35577000000000003, - translate: '0px calc(0 * 2.6px)', + translate: '0px 0px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.44354000000000005, - translate: '0px calc(-11.5 * 2.6px)', + translate: '0px -29.9px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.5427000000000001, - translate: '0px calc(0 * 2.6px)', + translate: '0px 0px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.5815600000000001, - translate: '0px calc(-2.5 * 2.6px)', + translate: '0px -6.5px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.63047, - translate: '0px calc(0 * 2.6px)', + translate: '0px 0px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.65057, - translate: '0px calc(-1 * 2.6px)', + translate: '0px -2.6px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, { offset: 0.67, - translate: '0px calc(0 * 2.6px)', + translate: '0px 0px', + easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', + }, + { + offset: 1, + translate: '0px 0px', easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, ], @@ -458,3 +508,22 @@ describe('Bounce', () => { }); }); }); + +describe('Bounce travel', () => { + const peakOf = (travel: unknown) => { + const { keyframes } = Bounce.web({ + ...baseMockOptions, + namedEffect: { travel } as any, + })[0]; + return keyframes.map(({ translate }) => (translate as string).replace(/^0px /, '')); + }; + + test.each([49, 98, 33.3])('the peak is the travel %spx', (travel) => { + const ys = peakOf({ value: travel, unit: 'px' }).map(parseFloat); + expect(Math.min(...ys)).toBe(-travel); + }); + + test('the peak is a calc travel', () => { + expect(peakOf('calc(1vh + 10px)')).toContain('calc(-1 * calc(1vh + 10px))'); + }); +}); diff --git a/packages/motion-presets/src/library/ongoing/test/Breathe.spec.ts b/packages/motion-presets/src/library/ongoing/test/Breathe.spec.ts index 7e38d140..7cb5a5a9 100644 --- a/packages/motion-presets/src/library/ongoing/test/Breathe.spec.ts +++ b/packages/motion-presets/src/library/ongoing/test/Breathe.spec.ts @@ -25,19 +25,19 @@ describe('Breathe', () => { }, keyframes: [ { - easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0, + easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', transform: ' translate3d(0, 0, 0) rotateZ(var(--motion-rotate, 0deg))', }, { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', offset: 0.25, + easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', transform: ' translate3d(calc(0 * 25px), calc(1 * 25px), calc(0 * 25px)) rotateZ(var(--motion-rotate, 0deg))', }, { - easing: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.75, + easing: 'cubic-bezier(0.47, 0, 0.745, 0.715)', transform: ' translate3d(calc(0 * -1 * 25px), calc(1 * -1 * 25px), calc(0 * -1 * 25px)) rotateZ(var(--motion-rotate, 0deg))', }, @@ -74,39 +74,25 @@ describe('Breathe', () => { }, keyframes: [ { - easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0, + easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', transform: ' translate3d(0, 0, 0) rotateZ(var(--motion-rotate, 0deg))', }, { + offset: 0.1675, easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', - offset: 0.067, - transform: - ' translate3d(calc(0 * 25px * 1), calc(1 * 25px * 1), calc(0 * 25px * 1)) rotateZ(var(--motion-rotate, 0deg))', - }, - { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', - offset: 0.20234, - transform: - ' translate3d(calc(0 * 25px * -1), calc(1 * 25px * -1), calc(0 * 25px * -1)) rotateZ(var(--motion-rotate, 0deg))', - }, - { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', - offset: 0.33768000000000004, transform: - ' translate3d(calc(0 * 25px * 1), calc(1 * 25px * 1), calc(0 * 25px * 1)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 25px), calc(1 * 25px), calc(0 * 25px)) rotateZ(var(--motion-rotate, 0deg))', }, { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', - offset: 0.47235, + offset: 0.5025000000000001, + easing: 'cubic-bezier(0.47, 0, 0.745, 0.715)', transform: - ' translate3d(calc(0 * 25px * -0.7), calc(1 * 25px * -0.7), calc(0 * 25px * -0.7)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * -1 * 25px), calc(1 * -1 * 25px), calc(0 * -1 * 25px)) rotateZ(var(--motion-rotate, 0deg))', }, { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', - offset: 0.56213, - transform: - ' translate3d(calc(0 * 25px * 0.6), calc(1 * 25px * 0.6), calc(0 * 25px * 0.6)) rotateZ(var(--motion-rotate, 0deg))', + offset: 0.67, + transform: ' translate3d(0, 0, 0) rotateZ(var(--motion-rotate, 0deg))', }, { offset: 1, @@ -139,19 +125,19 @@ describe('Breathe', () => { }, keyframes: [ { - easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0, + easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', transform: ' translate3d(0, 0, 0) rotateZ(var(--motion-rotate, 0deg))', }, { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', offset: 0.25, + easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', transform: ' translate3d(calc(0 * 50%), calc(1 * 50%), calc(0 * 50%)) rotateZ(var(--motion-rotate, 0deg))', }, { - easing: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.75, + easing: 'cubic-bezier(0.47, 0, 0.745, 0.715)', transform: ' translate3d(calc(0 * -1 * 50%), calc(1 * -1 * 50%), calc(0 * -1 * 50%)) rotateZ(var(--motion-rotate, 0deg))', }, @@ -177,7 +163,7 @@ describe('Breathe', () => { const expectedResult: Partial[] = [ { - name: 'motion-breathe-1', + name: 'motion-breathe-1-easeIn', duration: 1, custom: { '--motion-breathe-perspective': '', @@ -188,19 +174,19 @@ describe('Breathe', () => { }, keyframes: [ { - easing: 'ease-out', offset: 0, + easing: 'ease-out', transform: ' translate3d(0, 0, 0) rotateZ(var(--motion-rotate, 0deg))', }, { - easing: 'ease-in-out', offset: 0.25, + easing: 'ease-in-out', transform: ' translate3d(calc(0 * 25px), calc(1 * 25px), calc(0 * 25px)) rotateZ(var(--motion-rotate, 0deg))', }, { - easing: 'ease-in', offset: 0.75, + easing: 'ease-in', transform: ' translate3d(calc(0 * -1 * 25px), calc(1 * -1 * 25px), calc(0 * -1 * 25px)) rotateZ(var(--motion-rotate, 0deg))', }, @@ -236,19 +222,19 @@ describe('Breathe', () => { }, keyframes: [ { - easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0, + easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', transform: ' translate3d(0, 0, 0) rotateZ(var(--motion-rotate, 0deg))', }, { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', offset: 0.25, + easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', transform: ' translate3d(calc(1 * 25px), calc(0 * 25px), calc(0 * 25px)) rotateZ(var(--motion-rotate, 0deg))', }, { - easing: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.75, + easing: 'cubic-bezier(0.47, 0, 0.745, 0.715)', transform: ' translate3d(calc(1 * -1 * 25px), calc(0 * -1 * 25px), calc(0 * -1 * 25px)) rotateZ(var(--motion-rotate, 0deg))', }, @@ -284,20 +270,20 @@ describe('Breathe', () => { }, keyframes: [ { - easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0, + easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', transform: 'perspective(800px) translate3d(0, 0, 0) rotateZ(var(--motion-rotate, 0deg))', }, { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', offset: 0.25, + easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', transform: 'perspective(800px) translate3d(calc(0 * 25px), calc(0 * 25px), calc(1 * 25px)) rotateZ(var(--motion-rotate, 0deg))', }, { - easing: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.75, + easing: 'cubic-bezier(0.47, 0, 0.745, 0.715)', transform: 'perspective(800px) translate3d(calc(0 * -1 * 25px), calc(0 * -1 * 25px), calc(1 * -1 * 25px)) rotateZ(var(--motion-rotate, 0deg))', }, @@ -336,20 +322,20 @@ describe('Breathe', () => { }, keyframes: [ { - easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0, + easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', transform: 'var(--motion-breathe-perspective, ) translate3d(0, 0, 0) rotateZ(var(--motion-rotate, 0deg))', }, { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', offset: 0.25, + easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', transform: 'var(--motion-breathe-perspective, ) translate3d(calc(var(--motion-breathe-x) * var(--motion-breathe-distance)), calc(var(--motion-breathe-y) * var(--motion-breathe-distance)), calc(var(--motion-breathe-z) * var(--motion-breathe-distance))) rotateZ(var(--motion-rotate, 0deg))', }, { - easing: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.75, + easing: 'cubic-bezier(0.47, 0, 0.745, 0.715)', transform: 'var(--motion-breathe-perspective, ) translate3d(calc(var(--motion-breathe-x) * -1 * var(--motion-breathe-distance)), calc(var(--motion-breathe-y) * -1 * var(--motion-breathe-distance)), calc(var(--motion-breathe-z) * -1 * var(--motion-breathe-distance))) rotateZ(var(--motion-rotate, 0deg))', }, @@ -387,40 +373,27 @@ describe('Breathe', () => { }, keyframes: [ { - easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0, + easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', transform: 'var(--motion-breathe-perspective, ) translate3d(0, 0, 0) rotateZ(var(--motion-rotate, 0deg))', }, { + offset: 0.1675, easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', - offset: 0.067, - transform: - 'var(--motion-breathe-perspective, ) translate3d(calc(var(--motion-breathe-x) * var(--motion-breathe-distance) * 1), calc(var(--motion-breathe-y) * var(--motion-breathe-distance) * 1), calc(var(--motion-breathe-z) * var(--motion-breathe-distance) * 1)) rotateZ(var(--motion-rotate, 0deg))', - }, - { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', - offset: 0.20234, transform: - 'var(--motion-breathe-perspective, ) translate3d(calc(var(--motion-breathe-x) * var(--motion-breathe-distance) * -1), calc(var(--motion-breathe-y) * var(--motion-breathe-distance) * -1), calc(var(--motion-breathe-z) * var(--motion-breathe-distance) * -1)) rotateZ(var(--motion-rotate, 0deg))', - }, - { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', - offset: 0.33768000000000004, - transform: - 'var(--motion-breathe-perspective, ) translate3d(calc(var(--motion-breathe-x) * var(--motion-breathe-distance) * 1), calc(var(--motion-breathe-y) * var(--motion-breathe-distance) * 1), calc(var(--motion-breathe-z) * var(--motion-breathe-distance) * 1)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-breathe-perspective, ) translate3d(calc(var(--motion-breathe-x) * var(--motion-breathe-distance)), calc(var(--motion-breathe-y) * var(--motion-breathe-distance)), calc(var(--motion-breathe-z) * var(--motion-breathe-distance))) rotateZ(var(--motion-rotate, 0deg))', }, { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', - offset: 0.47235, + offset: 0.5025000000000001, + easing: 'cubic-bezier(0.47, 0, 0.745, 0.715)', transform: - 'var(--motion-breathe-perspective, ) translate3d(calc(var(--motion-breathe-x) * var(--motion-breathe-distance) * -0.7), calc(var(--motion-breathe-y) * var(--motion-breathe-distance) * -0.7), calc(var(--motion-breathe-z) * var(--motion-breathe-distance) * -0.7)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-breathe-perspective, ) translate3d(calc(var(--motion-breathe-x) * -1 * var(--motion-breathe-distance)), calc(var(--motion-breathe-y) * -1 * var(--motion-breathe-distance)), calc(var(--motion-breathe-z) * -1 * var(--motion-breathe-distance))) rotateZ(var(--motion-rotate, 0deg))', }, { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', - offset: 0.56213, + offset: 0.67, transform: - 'var(--motion-breathe-perspective, ) translate3d(calc(var(--motion-breathe-x) * var(--motion-breathe-distance) * 0.6), calc(var(--motion-breathe-y) * var(--motion-breathe-distance) * 0.6), calc(var(--motion-breathe-z) * var(--motion-breathe-distance) * 0.6)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-breathe-perspective, ) translate3d(0, 0, 0) rotateZ(var(--motion-rotate, 0deg))', }, { offset: 1, @@ -454,20 +427,20 @@ describe('Breathe', () => { }, keyframes: [ { - easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0, + easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', transform: 'var(--motion-breathe-perspective, ) translate3d(0, 0, 0) rotateZ(var(--motion-rotate, 0deg))', }, { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', offset: 0.25, + easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', transform: 'var(--motion-breathe-perspective, ) translate3d(calc(var(--motion-breathe-x) * var(--motion-breathe-distance)), calc(var(--motion-breathe-y) * var(--motion-breathe-distance)), calc(var(--motion-breathe-z) * var(--motion-breathe-distance))) rotateZ(var(--motion-rotate, 0deg))', }, { - easing: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.75, + easing: 'cubic-bezier(0.47, 0, 0.745, 0.715)', transform: 'var(--motion-breathe-perspective, ) translate3d(calc(var(--motion-breathe-x) * -1 * var(--motion-breathe-distance)), calc(var(--motion-breathe-y) * -1 * var(--motion-breathe-distance)), calc(var(--motion-breathe-z) * -1 * var(--motion-breathe-distance))) rotateZ(var(--motion-rotate, 0deg))', }, @@ -504,20 +477,20 @@ describe('Breathe', () => { }, keyframes: [ { - easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0, + easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', transform: 'var(--motion-breathe-perspective, ) translate3d(0, 0, 0) rotateZ(var(--motion-rotate, 0deg))', }, { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', offset: 0.25, + easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', transform: 'var(--motion-breathe-perspective, ) translate3d(calc(var(--motion-breathe-x) * var(--motion-breathe-distance)), calc(var(--motion-breathe-y) * var(--motion-breathe-distance)), calc(var(--motion-breathe-z) * var(--motion-breathe-distance))) rotateZ(var(--motion-rotate, 0deg))', }, { - easing: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.75, + easing: 'cubic-bezier(0.47, 0, 0.745, 0.715)', transform: 'var(--motion-breathe-perspective, ) translate3d(calc(var(--motion-breathe-x) * -1 * var(--motion-breathe-distance)), calc(var(--motion-breathe-y) * -1 * var(--motion-breathe-distance)), calc(var(--motion-breathe-z) * -1 * var(--motion-breathe-distance))) rotateZ(var(--motion-rotate, 0deg))', }, @@ -554,20 +527,20 @@ describe('Breathe', () => { }, keyframes: [ { - easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0, + easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', transform: 'var(--motion-breathe-perspective, ) translate3d(0, 0, 0) rotateZ(var(--motion-rotate, 0deg))', }, { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', offset: 0.25, + easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', transform: 'var(--motion-breathe-perspective, ) translate3d(calc(var(--motion-breathe-x) * var(--motion-breathe-distance)), calc(var(--motion-breathe-y) * var(--motion-breathe-distance)), calc(var(--motion-breathe-z) * var(--motion-breathe-distance))) rotateZ(var(--motion-rotate, 0deg))', }, { - easing: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.75, + easing: 'cubic-bezier(0.47, 0, 0.745, 0.715)', transform: 'var(--motion-breathe-perspective, ) translate3d(calc(var(--motion-breathe-x) * -1 * var(--motion-breathe-distance)), calc(var(--motion-breathe-y) * -1 * var(--motion-breathe-distance)), calc(var(--motion-breathe-z) * -1 * var(--motion-breathe-distance))) rotateZ(var(--motion-rotate, 0deg))', }, diff --git a/packages/motion-presets/src/library/ongoing/test/Cross.spec.ts b/packages/motion-presets/src/library/ongoing/test/Cross.spec.ts index dec1485c..c3b9cdd6 100644 --- a/packages/motion-presets/src/library/ongoing/test/Cross.spec.ts +++ b/packages/motion-presets/src/library/ongoing/test/Cross.spec.ts @@ -19,18 +19,16 @@ describe('Cross', () => { const expectedResult = [ { - ...baseMockOptions, - namedEffect: {} as CrossType, - name: 'motion-cross', + id: 'test-id', + namedEffect: {}, + name: 'motion-cross-1', easing: 'linear', duration: 1, custom: { - '--motion-left': '0px', - '--motion-top': '0px', - '--motion-width': '100%', - '--motion-height': '100%', - '--motion-parent-width': '100vw', - '--motion-parent-height': '100vh', + '--motion-cross-from': + 'calc(var(--motion-left, 0px) * -1 - var(--motion-width, 100%)) 0', + '--motion-cross-to': + 'calc(var(--motion-parent-width, 100vw) - var(--motion-left, 0px)) 0', }, keyframes: [ { @@ -38,22 +36,18 @@ describe('Cross', () => { translate: '0 0', }, { - easing: 'step-start', - offset: 0, + offset: 0.5, translate: 'calc(var(--motion-parent-width, 100vw) - var(--motion-left, 0px)) 0', + easing: 'step-start', }, { - offset: 0, + offset: 0.5001, translate: 'calc(var(--motion-left, 0px) * -1 - var(--motion-width, 100%)) 0', }, { offset: 1, translate: '0 0', }, - { - offset: 1, - translate: '0 0', - }, ], }, ]; @@ -72,18 +66,18 @@ describe('Cross', () => { const expectedResult = [ { - ...baseMockOptions, - namedEffect: {} as CrossType, - name: 'motion-cross', + id: 'test-id', + namedEffect: { + iterationDelay: 500, + }, + name: 'motion-cross-067', easing: 'linear', duration: 1500, custom: { - '--motion-left': '0px', - '--motion-top': '0px', - '--motion-width': '100%', - '--motion-height': '100%', - '--motion-parent-width': '100vw', - '--motion-parent-height': '100vh', + '--motion-cross-from': + 'calc(var(--motion-left, 0px) * -1 - var(--motion-width, 100%)) 0', + '--motion-cross-to': + 'calc(var(--motion-parent-width, 100vw) - var(--motion-left, 0px)) 0', }, keyframes: [ { @@ -91,12 +85,12 @@ describe('Cross', () => { translate: '0 0', }, { - easing: 'step-start', - offset: 0, + offset: 0.335, translate: 'calc(var(--motion-parent-width, 100vw) - var(--motion-left, 0px)) 0', + easing: 'step-start', }, { - offset: 0, + offset: 0.335067, translate: 'calc(var(--motion-left, 0px) * -1 - var(--motion-width, 100%)) 0', }, { @@ -124,18 +118,17 @@ describe('Cross', () => { const expectedResult = [ { - ...baseMockOptions, - namedEffect: { direction: 'top' } as CrossType, - name: 'motion-cross', + id: 'test-id', + namedEffect: { + direction: 'top', + }, + name: 'motion-cross-1', easing: 'linear', duration: 1, custom: { - '--motion-left': '0px', - '--motion-top': '0px', - '--motion-width': '100%', - '--motion-height': '100%', - '--motion-parent-width': '100vw', - '--motion-parent-height': '100vh', + '--motion-cross-from': + '0 calc(var(--motion-parent-height, 100vh) - var(--motion-top, 0px))', + '--motion-cross-to': '0 calc(var(--motion-top, 0px) * -1 - var(--motion-height, 100%))', }, keyframes: [ { @@ -143,22 +136,18 @@ describe('Cross', () => { translate: '0 0', }, { - easing: 'step-start', - offset: 0, + offset: 0.5, translate: '0 calc(var(--motion-top, 0px) * -1 - var(--motion-height, 100%))', + easing: 'step-start', }, { - offset: 0, + offset: 0.5001, translate: '0 calc(var(--motion-parent-height, 100vh) - var(--motion-top, 0px))', }, { offset: 1, translate: '0 0', }, - { - offset: 1, - translate: '0 0', - }, ], }, ]; @@ -176,18 +165,18 @@ describe('Cross', () => { const expectedResult = [ { - ...baseMockOptions, - namedEffect: { direction: 'bottom' } as CrossType, - name: 'motion-cross', + id: 'test-id', + namedEffect: { + direction: 'bottom', + }, + name: 'motion-cross-1', easing: 'linear', duration: 1, custom: { - '--motion-left': '0px', - '--motion-top': '0px', - '--motion-width': '100%', - '--motion-height': '100%', - '--motion-parent-width': '100vw', - '--motion-parent-height': '100vh', + '--motion-cross-from': + '0 calc(var(--motion-top, 0px) * -1 - var(--motion-height, 100%))', + '--motion-cross-to': + '0 calc(var(--motion-parent-height, 100vh) - var(--motion-top, 0px))', }, keyframes: [ { @@ -195,22 +184,18 @@ describe('Cross', () => { translate: '0 0', }, { - easing: 'step-start', - offset: 0, + offset: 0.5, translate: '0 calc(var(--motion-parent-height, 100vh) - var(--motion-top, 0px))', + easing: 'step-start', }, { - offset: 0, + offset: 0.5001, translate: '0 calc(var(--motion-top, 0px) * -1 - var(--motion-height, 100%))', }, { offset: 1, translate: '0 0', }, - { - offset: 1, - translate: '0 0', - }, ], }, ]; @@ -228,18 +213,17 @@ describe('Cross', () => { const expectedResult = [ { - ...baseMockOptions, - namedEffect: { direction: 'left' } as CrossType, - name: 'motion-cross', + id: 'test-id', + namedEffect: { + direction: 'left', + }, + name: 'motion-cross-1', easing: 'linear', duration: 1, custom: { - '--motion-left': '0px', - '--motion-top': '0px', - '--motion-width': '100%', - '--motion-height': '100%', - '--motion-parent-width': '100vw', - '--motion-parent-height': '100vh', + '--motion-cross-from': + 'calc(var(--motion-parent-width, 100vw) - var(--motion-left, 0px)) 0', + '--motion-cross-to': 'calc(var(--motion-left, 0px) * -1 - var(--motion-width, 100%)) 0', }, keyframes: [ { @@ -247,22 +231,18 @@ describe('Cross', () => { translate: '0 0', }, { - easing: 'step-start', - offset: 0, + offset: 0.5, translate: 'calc(var(--motion-left, 0px) * -1 - var(--motion-width, 100%)) 0', + easing: 'step-start', }, { - offset: 0, + offset: 0.5001, translate: 'calc(var(--motion-parent-width, 100vw) - var(--motion-left, 0px)) 0', }, { offset: 1, translate: '0 0', }, - { - offset: 1, - translate: '0 0', - }, ], }, ]; @@ -280,18 +260,18 @@ describe('Cross', () => { const expectedResult = [ { - ...baseMockOptions, - namedEffect: { direction: 'top-left' } as CrossType, - name: 'motion-cross', + id: 'test-id', + namedEffect: { + direction: 'top-left', + }, + name: 'motion-cross-1', easing: 'linear', duration: 1, custom: { - '--motion-left': '0px', - '--motion-top': '0px', - '--motion-width': '100%', - '--motion-height': '100%', - '--motion-parent-width': '100vw', - '--motion-parent-height': '100vh', + '--motion-cross-from': + 'calc(min(calc(var(--motion-parent-width, 100vw) - var(--motion-left, 0px)), calc(var(--motion-parent-height, 100vh) - var(--motion-top, 0px))) * 1) calc(min(calc(var(--motion-parent-width, 100vw) - var(--motion-left, 0px)), calc(var(--motion-parent-height, 100vh) - var(--motion-top, 0px))) * 1)', + '--motion-cross-to': + 'calc(min(calc(calc(var(--motion-left, 0px) * -1 - var(--motion-width, 100%)) * -1), calc(calc(var(--motion-top, 0px) * -1 - var(--motion-height, 100%)) * -1)) * -1) calc(min(calc(calc(var(--motion-left, 0px) * -1 - var(--motion-width, 100%)) * -1), calc(calc(var(--motion-top, 0px) * -1 - var(--motion-height, 100%)) * -1)) * -1)', }, keyframes: [ { @@ -299,13 +279,13 @@ describe('Cross', () => { translate: '0 0', }, { - easing: 'step-start', - offset: 0, + offset: 0.5, translate: 'calc(min(calc(calc(var(--motion-left, 0px) * -1 - var(--motion-width, 100%)) * -1), calc(calc(var(--motion-top, 0px) * -1 - var(--motion-height, 100%)) * -1)) * -1) calc(min(calc(calc(var(--motion-left, 0px) * -1 - var(--motion-width, 100%)) * -1), calc(calc(var(--motion-top, 0px) * -1 - var(--motion-height, 100%)) * -1)) * -1)', + easing: 'step-start', }, { - offset: 0, + offset: 0.5001, translate: 'calc(min(calc(var(--motion-parent-width, 100vw) - var(--motion-left, 0px)), calc(var(--motion-parent-height, 100vh) - var(--motion-top, 0px))) * 1) calc(min(calc(var(--motion-parent-width, 100vw) - var(--motion-left, 0px)), calc(var(--motion-parent-height, 100vh) - var(--motion-top, 0px))) * 1)', }, @@ -313,10 +293,6 @@ describe('Cross', () => { offset: 1, translate: '0 0', }, - { - offset: 1, - translate: '0 0', - }, ], }, ]; @@ -334,18 +310,18 @@ describe('Cross', () => { const expectedResult = [ { - ...baseMockOptions, - namedEffect: { direction: 'top-right' } as CrossType, - name: 'motion-cross', + id: 'test-id', + namedEffect: { + direction: 'top-right', + }, + name: 'motion-cross-1', easing: 'linear', duration: 1, custom: { - '--motion-left': '0px', - '--motion-top': '0px', - '--motion-width': '100%', - '--motion-height': '100%', - '--motion-parent-width': '100vw', - '--motion-parent-height': '100vh', + '--motion-cross-from': + 'calc(min(calc(calc(var(--motion-left, 0px) * -1 - var(--motion-width, 100%)) * -1), calc(var(--motion-parent-height, 100vh) - var(--motion-top, 0px))) * -1) calc(min(calc(calc(var(--motion-left, 0px) * -1 - var(--motion-width, 100%)) * -1), calc(var(--motion-parent-height, 100vh) - var(--motion-top, 0px))) * 1)', + '--motion-cross-to': + 'calc(min(calc(var(--motion-parent-width, 100vw) - var(--motion-left, 0px)), calc(calc(var(--motion-top, 0px) * -1 - var(--motion-height, 100%)) * -1)) * 1) calc(min(calc(var(--motion-parent-width, 100vw) - var(--motion-left, 0px)), calc(calc(var(--motion-top, 0px) * -1 - var(--motion-height, 100%)) * -1)) * -1)', }, keyframes: [ { @@ -353,13 +329,13 @@ describe('Cross', () => { translate: '0 0', }, { - easing: 'step-start', - offset: 0, + offset: 0.5, translate: 'calc(min(calc(var(--motion-parent-width, 100vw) - var(--motion-left, 0px)), calc(calc(var(--motion-top, 0px) * -1 - var(--motion-height, 100%)) * -1)) * 1) calc(min(calc(var(--motion-parent-width, 100vw) - var(--motion-left, 0px)), calc(calc(var(--motion-top, 0px) * -1 - var(--motion-height, 100%)) * -1)) * -1)', + easing: 'step-start', }, { - offset: 0, + offset: 0.5001, translate: 'calc(min(calc(calc(var(--motion-left, 0px) * -1 - var(--motion-width, 100%)) * -1), calc(var(--motion-parent-height, 100vh) - var(--motion-top, 0px))) * -1) calc(min(calc(calc(var(--motion-left, 0px) * -1 - var(--motion-width, 100%)) * -1), calc(var(--motion-parent-height, 100vh) - var(--motion-top, 0px))) * 1)', }, @@ -367,10 +343,6 @@ describe('Cross', () => { offset: 1, translate: '0 0', }, - { - offset: 1, - translate: '0 0', - }, ], }, ]; @@ -388,18 +360,18 @@ describe('Cross', () => { const expectedResult = [ { - ...baseMockOptions, - namedEffect: { direction: 'bottom-left' } as CrossType, - name: 'motion-cross', + id: 'test-id', + namedEffect: { + direction: 'bottom-left', + }, + name: 'motion-cross-1', easing: 'linear', duration: 1, custom: { - '--motion-left': '0px', - '--motion-top': '0px', - '--motion-width': '100%', - '--motion-height': '100%', - '--motion-parent-width': '100vw', - '--motion-parent-height': '100vh', + '--motion-cross-from': + 'calc(min(calc(var(--motion-parent-width, 100vw) - var(--motion-left, 0px)), calc(calc(var(--motion-top, 0px) * -1 - var(--motion-height, 100%)) * -1)) * 1) calc(min(calc(var(--motion-parent-width, 100vw) - var(--motion-left, 0px)), calc(calc(var(--motion-top, 0px) * -1 - var(--motion-height, 100%)) * -1)) * -1)', + '--motion-cross-to': + 'calc(min(calc(calc(var(--motion-left, 0px) * -1 - var(--motion-width, 100%)) * -1), calc(var(--motion-parent-height, 100vh) - var(--motion-top, 0px))) * -1) calc(min(calc(calc(var(--motion-left, 0px) * -1 - var(--motion-width, 100%)) * -1), calc(var(--motion-parent-height, 100vh) - var(--motion-top, 0px))) * 1)', }, keyframes: [ { @@ -407,13 +379,13 @@ describe('Cross', () => { translate: '0 0', }, { - easing: 'step-start', - offset: 0, + offset: 0.5, translate: 'calc(min(calc(calc(var(--motion-left, 0px) * -1 - var(--motion-width, 100%)) * -1), calc(var(--motion-parent-height, 100vh) - var(--motion-top, 0px))) * -1) calc(min(calc(calc(var(--motion-left, 0px) * -1 - var(--motion-width, 100%)) * -1), calc(var(--motion-parent-height, 100vh) - var(--motion-top, 0px))) * 1)', + easing: 'step-start', }, { - offset: 0, + offset: 0.5001, translate: 'calc(min(calc(var(--motion-parent-width, 100vw) - var(--motion-left, 0px)), calc(calc(var(--motion-top, 0px) * -1 - var(--motion-height, 100%)) * -1)) * 1) calc(min(calc(var(--motion-parent-width, 100vw) - var(--motion-left, 0px)), calc(calc(var(--motion-top, 0px) * -1 - var(--motion-height, 100%)) * -1)) * -1)', }, @@ -421,10 +393,6 @@ describe('Cross', () => { offset: 1, translate: '0 0', }, - { - offset: 1, - translate: '0 0', - }, ], }, ]; @@ -442,18 +410,18 @@ describe('Cross', () => { const expectedResult = [ { - ...baseMockOptions, - namedEffect: { direction: 'bottom-right' } as CrossType, - name: 'motion-cross', + id: 'test-id', + namedEffect: { + direction: 'bottom-right', + }, + name: 'motion-cross-1', easing: 'linear', duration: 1, custom: { - '--motion-left': '0px', - '--motion-top': '0px', - '--motion-width': '100%', - '--motion-height': '100%', - '--motion-parent-width': '100vw', - '--motion-parent-height': '100vh', + '--motion-cross-from': + 'calc(min(calc(calc(var(--motion-left, 0px) * -1 - var(--motion-width, 100%)) * -1), calc(calc(var(--motion-top, 0px) * -1 - var(--motion-height, 100%)) * -1)) * -1) calc(min(calc(calc(var(--motion-left, 0px) * -1 - var(--motion-width, 100%)) * -1), calc(calc(var(--motion-top, 0px) * -1 - var(--motion-height, 100%)) * -1)) * -1)', + '--motion-cross-to': + 'calc(min(calc(var(--motion-parent-width, 100vw) - var(--motion-left, 0px)), calc(var(--motion-parent-height, 100vh) - var(--motion-top, 0px))) * 1) calc(min(calc(var(--motion-parent-width, 100vw) - var(--motion-left, 0px)), calc(var(--motion-parent-height, 100vh) - var(--motion-top, 0px))) * 1)', }, keyframes: [ { @@ -461,13 +429,13 @@ describe('Cross', () => { translate: '0 0', }, { - easing: 'step-start', - offset: 0, + offset: 0.5, translate: 'calc(min(calc(var(--motion-parent-width, 100vw) - var(--motion-left, 0px)), calc(var(--motion-parent-height, 100vh) - var(--motion-top, 0px))) * 1) calc(min(calc(var(--motion-parent-width, 100vw) - var(--motion-left, 0px)), calc(var(--motion-parent-height, 100vh) - var(--motion-top, 0px))) * 1)', + easing: 'step-start', }, { - offset: 0, + offset: 0.5001, translate: 'calc(min(calc(calc(var(--motion-left, 0px) * -1 - var(--motion-width, 100%)) * -1), calc(calc(var(--motion-top, 0px) * -1 - var(--motion-height, 100%)) * -1)) * -1) calc(min(calc(calc(var(--motion-left, 0px) * -1 - var(--motion-width, 100%)) * -1), calc(calc(var(--motion-top, 0px) * -1 - var(--motion-height, 100%)) * -1)) * -1)', }, @@ -475,10 +443,6 @@ describe('Cross', () => { offset: 1, translate: '0 0', }, - { - offset: 1, - translate: '0 0', - }, ], }, ]; @@ -497,18 +461,16 @@ describe('Cross', () => { const expectedResult = [ { - ...baseMockOptions, - namedEffect: {} as CrossType, - name: 'motion-cross', + id: 'test-id', + namedEffect: {}, + name: 'motion-cross-1', easing: 'linear', duration: 1, custom: { - '--motion-left': '0px', - '--motion-top': '0px', - '--motion-width': '100%', - '--motion-height': '100%', - '--motion-parent-width': '100vw', - '--motion-parent-height': '100vh', + '--motion-cross-from': + 'calc(var(--motion-left, 0px) * -1 - var(--motion-width, 100%)) 0', + '--motion-cross-to': + 'calc(var(--motion-parent-width, 100vw) - var(--motion-left, 0px)) 0', }, }, ]; @@ -527,18 +489,18 @@ describe('Cross', () => { const expectedResult = [ { - ...baseMockOptions, - namedEffect: {} as CrossType, - name: 'motion-cross', + id: 'test-id', + namedEffect: { + iterationDelay: 1000, + }, + name: 'motion-cross-05', easing: 'linear', duration: 2000, custom: { - '--motion-left': '0px', - '--motion-top': '0px', - '--motion-width': '100%', - '--motion-height': '100%', - '--motion-parent-width': '100vw', - '--motion-parent-height': '100vh', + '--motion-cross-from': + 'calc(var(--motion-left, 0px) * -1 - var(--motion-width, 100%)) 0', + '--motion-cross-to': + 'calc(var(--motion-parent-width, 100vw) - var(--motion-left, 0px)) 0', }, keyframes: [ { @@ -546,12 +508,12 @@ describe('Cross', () => { translate: '0 0', }, { - easing: 'step-start', - offset: 0, + offset: 0.25, translate: 'calc(var(--motion-parent-width, 100vw) - var(--motion-left, 0px)) 0', + easing: 'step-start', }, { - offset: 0, + offset: 0.25005, translate: 'calc(var(--motion-left, 0px) * -1 - var(--motion-width, 100%)) 0', }, { @@ -580,18 +542,18 @@ describe('Cross', () => { const expectedResult = [ { - ...baseMockOptions, - namedEffect: {} as CrossType, - name: 'motion-cross', + id: 'test-id', + namedEffect: { + iterationDelay: 1500, + }, + name: 'motion-cross-025', easing: 'linear', duration: 2000, custom: { - '--motion-left': '0px', - '--motion-top': '0px', - '--motion-width': '100%', - '--motion-height': '100%', - '--motion-parent-width': '100vw', - '--motion-parent-height': '100vh', + '--motion-cross-from': + 'calc(var(--motion-left, 0px) * -1 - var(--motion-width, 100%)) 0', + '--motion-cross-to': + 'calc(var(--motion-parent-width, 100vw) - var(--motion-left, 0px)) 0', }, keyframes: [ { @@ -599,12 +561,12 @@ describe('Cross', () => { translate: '0 0', }, { - easing: 'step-start', - offset: 0, + offset: 0.125, translate: 'calc(var(--motion-parent-width, 100vw) - var(--motion-left, 0px)) 0', + easing: 'step-start', }, { - offset: 0, + offset: 0.125025, translate: 'calc(var(--motion-left, 0px) * -1 - var(--motion-width, 100%)) 0', }, { @@ -705,7 +667,7 @@ describe('Cross', () => { const fromDistance = Math.max(...expectedFrom.map(Math.abs)); expect(to.offset).toBeCloseTo(toDistance / (toDistance + fromDistance)); - expect(from.offset).toBe(to.offset); + expect(from.offset).toBeCloseTo(to.offset! + 0.0001, 8); expect(evaluateTranslate(to.translate as string, properties)).toEqual(expectedTo); expect(evaluateTranslate(from.translate as string, properties)).toEqual(expectedFrom); }, @@ -723,10 +685,61 @@ describe('Cross', () => { expect(effect.keyframes.map(({ offset }) => offset)).toEqual([ 0, (right / (right + left)) * 0.5, - (right / (right + left)) * 0.5, + (right / (right + left) + 0.0001) * 0.5, 0.5, 1, ]); }); }); }); + +describe('Cross CSS', () => { + test('style keyframes read the translations from suffixed custom properties', () => { + const [animation] = Cross.style({ + ...baseMockOptions, + duration: 1000, + suffix: '-s1', + namedEffect: { direction: 'right', iterationDelay: 1000 } as CrossType, + } as TimeAnimationOptions); + + expect(animation.name).toBe('motion-cross-05-s1'); + expect(animation.custom).toEqual({ + '--motion-cross-from-s1': 'calc(var(--motion-left, 0px) * -1 - var(--motion-width, 100%)) 0', + '--motion-cross-to-s1': 'calc(var(--motion-parent-width, 100vw) - var(--motion-left, 0px)) 0', + }); + expect(animation.keyframes).toEqual([ + { offset: 0, translate: '0 0' }, + { offset: 0.25, translate: 'var(--motion-cross-to-s1)', easing: 'step-start' }, + { offset: 0.5001 * 0.5, translate: 'var(--motion-cross-from-s1)' }, + { offset: 0.5, translate: '0 0' }, + { offset: 1, translate: '0 0' }, + ]); + }); + + test('prepare measures the element in its parent into custom properties', () => { + const properties: Record = {}; + const parent = { + offsetParent: null, + getBoundingClientRect: () => ({ width: 1000, height: 800 }), + }; + const target = { + offsetLeft: 200, + offsetTop: 100, + offsetParent: parent, + getBoundingClientRect: () => ({ width: 100, height: 50 }), + style: { setProperty: (name: string, value: string) => (properties[name] = value) }, + } as unknown as HTMLElement; + const dom: DomApi = { measure: (fn) => fn(target), mutate: (fn) => fn(target) }; + + Cross.prepare({ ...baseMockOptions, namedEffect: {} as CrossType }, dom); + + expect(properties).toEqual({ + '--motion-left': '200px', + '--motion-top': '100px', + '--motion-width': '100px', + '--motion-height': '50px', + '--motion-parent-width': '1000px', + '--motion-parent-height': '800px', + }); + }); +}); diff --git a/packages/motion-presets/src/library/ongoing/test/Flash.spec.ts b/packages/motion-presets/src/library/ongoing/test/Flash.spec.ts index 992629d6..50e9649d 100644 --- a/packages/motion-presets/src/library/ongoing/test/Flash.spec.ts +++ b/packages/motion-presets/src/library/ongoing/test/Flash.spec.ts @@ -27,8 +27,10 @@ describe('Flash', () => { opacity: 0, easing: 'cubic-bezier(0.645, 0.045, 0.355, 1)', }, - { offset: 1, opacity: 1 }, - { offset: 1, opacity: 1 }, + { + offset: 1, + opacity: 1, + }, ], }, ]; @@ -55,9 +57,19 @@ describe('Flash', () => { opacity: 1, easing: 'cubic-bezier(0.645, 0.045, 0.355, 1)', }, - { offset: 0.335, opacity: 0 }, - { offset: 0.67, opacity: 1 }, - { offset: 1, opacity: 1 }, + { + offset: 0.335, + opacity: 0, + easing: 'cubic-bezier(0.645, 0.045, 0.355, 1)', + }, + { + offset: 0.67, + opacity: 1, + }, + { + offset: 1, + opacity: 1, + }, ], }, ]; @@ -76,7 +88,7 @@ describe('Flash', () => { const expectedResult: Partial[] = [ { - name: 'motion-flash-1', + name: 'motion-flash-1-sineIn', duration: 1, keyframes: [ { @@ -89,8 +101,10 @@ describe('Flash', () => { opacity: 0, easing: 'cubic-bezier(0.47, 0, 0.745, 0.715)', }, - { offset: 1, opacity: 1 }, - { offset: 1, opacity: 1 }, + { + offset: 1, + opacity: 1, + }, ], }, ]; @@ -123,8 +137,10 @@ describe('Flash', () => { opacity: 0, easing: 'cubic-bezier(0.645, 0.045, 0.355, 1)', }, - { offset: 1, opacity: 1 }, - { offset: 1, opacity: 1 }, + { + offset: 1, + opacity: 1, + }, ], }, ]; @@ -151,9 +167,19 @@ describe('Flash', () => { opacity: 1, easing: 'cubic-bezier(0.645, 0.045, 0.355, 1)', }, - { offset: 0.335, opacity: 0 }, - { offset: 0.67, opacity: 1 }, - { offset: 1, opacity: 1 }, + { + offset: 0.335, + opacity: 0, + easing: 'cubic-bezier(0.645, 0.045, 0.355, 1)', + }, + { + offset: 0.67, + opacity: 1, + }, + { + offset: 1, + opacity: 1, + }, ], }, ]; @@ -172,7 +198,7 @@ describe('Flash', () => { const expectedResult: Partial[] = [ { - name: 'motion-flash-1', + name: 'motion-flash-1-sineIn', duration: 1, keyframes: [ { @@ -185,8 +211,10 @@ describe('Flash', () => { opacity: 0, easing: 'cubic-bezier(0.47, 0, 0.745, 0.715)', }, - { offset: 1, opacity: 1 }, - { offset: 1, opacity: 1 }, + { + offset: 1, + opacity: 1, + }, ], }, ]; diff --git a/packages/motion-presets/src/library/ongoing/test/Flip.spec.ts b/packages/motion-presets/src/library/ongoing/test/Flip.spec.ts index 9c38cc0f..4b261930 100644 --- a/packages/motion-presets/src/library/ongoing/test/Flip.spec.ts +++ b/packages/motion-presets/src/library/ongoing/test/Flip.spec.ts @@ -28,11 +28,6 @@ describe('Flip', () => { transform: 'perspective(800px) rotateZ(var(--motion-rotate, 0deg)) rotate3d(0, 1, 0, 360deg)', }, - { - offset: 1, - transform: - 'perspective(800px) rotateZ(var(--motion-rotate, 0deg)) rotate3d(0, 1, 0, 360deg)', - }, ], }, ]; @@ -65,11 +60,6 @@ describe('Flip', () => { transform: 'perspective(800px) rotateZ(var(--motion-rotate, 0deg)) rotate3d(1, 0, 0, 360deg)', }, - { - offset: 1, - transform: - 'perspective(800px) rotateZ(var(--motion-rotate, 0deg)) rotate3d(1, 0, 0, 360deg)', - }, ], }, ]; @@ -89,9 +79,11 @@ describe('Flip', () => { const expectedResult: Partial[] = [ { - name: 'motion-flip-05', + name: 'motion-flip-05-ease-in-out', easing: 'linear', - namedEffect: { iterationDelay: 1000 } as Flip, + namedEffect: { + iterationDelay: 1000, + } as Flip, duration: 2000, keyframes: [ { @@ -147,11 +139,6 @@ describe('Flip', () => { transform: 'perspective(var(--motion-perspective)) rotateZ(var(--motion-rotate, 0deg)) rotate3d(var(--motion-rotate-x), var(--motion-rotate-y), 0, 360deg)', }, - { - offset: 1, - transform: - 'perspective(var(--motion-perspective)) rotateZ(var(--motion-rotate, 0deg)) rotate3d(var(--motion-rotate-x), var(--motion-rotate-y), 0, 360deg)', - }, ], }, ]; @@ -188,11 +175,6 @@ describe('Flip', () => { transform: 'perspective(var(--motion-perspective)) rotateZ(var(--motion-rotate, 0deg)) rotate3d(var(--motion-rotate-x), var(--motion-rotate-y), 0, 360deg)', }, - { - offset: 1, - transform: - 'perspective(var(--motion-perspective)) rotateZ(var(--motion-rotate, 0deg)) rotate3d(var(--motion-rotate-x), var(--motion-rotate-y), 0, 360deg)', - }, ], }, ]; @@ -212,9 +194,11 @@ describe('Flip', () => { const expectedResult: Partial[] = [ { - name: 'motion-flip-05', + name: 'motion-flip-05-ease-in-out', easing: 'linear', - namedEffect: { iterationDelay: 1000 } as Flip, + namedEffect: { + iterationDelay: 1000, + } as Flip, duration: 2000, custom: { '--motion-perspective': '800px', diff --git a/packages/motion-presets/src/library/ongoing/test/Fold.spec.ts b/packages/motion-presets/src/library/ongoing/test/Fold.spec.ts index fddf70f7..e3bfb7e1 100644 --- a/packages/motion-presets/src/library/ongoing/test/Fold.spec.ts +++ b/packages/motion-presets/src/library/ongoing/test/Fold.spec.ts @@ -22,23 +22,24 @@ describe('Fold', () => { '--motion-rotate-angle': '15deg', '--motion-rotate-x': '1', '--motion-rotate-y': '0', + '--motion-perspective': '800px', }, keyframes: [ { - easing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', offset: 0, + easing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', transform: 'rotateZ(var(--motion-rotate, 0deg)) translateX(0%) translateY(-50%) perspective(800px) rotateX(calc(1 * 0 * 15deg)) rotateY(calc(0 * 0 * 15deg)) translateX(calc(-1 * 0%)) translateY(calc(-1 * -50%))', }, { - easing: 'cubic-bezier(0.645, 0.045, 0.355, 1)', offset: 0.25, + easing: 'cubic-bezier(0.645, 0.045, 0.355, 1)', transform: 'rotateZ(var(--motion-rotate, 0deg)) translateX(0%) translateY(-50%) perspective(800px) rotateX(calc(1 * 1 * 15deg)) rotateY(calc(0 * 1 * 15deg)) translateX(calc(-1 * 0%)) translateY(calc(-1 * -50%))', }, { - easing: 'cubic-bezier(0.55, 0.055, 0.675, 0.19)', offset: 0.75, + easing: 'cubic-bezier(0.55, 0.055, 0.675, 0.19)', transform: 'rotateZ(var(--motion-rotate, 0deg)) translateX(0%) translateY(-50%) perspective(800px) rotateX(calc(1 * -1 * 15deg)) rotateY(calc(0 * -1 * 15deg)) translateX(calc(-1 * 0%)) translateY(calc(-1 * -50%))', }, @@ -64,11 +65,9 @@ describe('Fold', () => { namedEffect: { iterationDelay: 500 } as Fold, }; - const dynamicName = `motion-fold-067`; - const expectedResult: Partial[] = [ { - name: dynamicName, + name: 'motion-fold-067-qiboha', duration: 1500, custom: { '--motion-origin-x': '0%', @@ -76,53 +75,29 @@ describe('Fold', () => { '--motion-rotate-angle': '15deg', '--motion-rotate-x': '1', '--motion-rotate-y': '0', + '--motion-perspective': '800px', }, keyframes: [ { - easing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', offset: 0, + easing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', transform: 'rotateZ(var(--motion-rotate, 0deg)) translateX(0%) translateY(-50%) perspective(800px) rotateX(calc(1 * 0 * 15deg)) rotateY(calc(0 * 0 * 15deg)) translateX(calc(-1 * 0%)) translateY(calc(-1 * -50%))', }, { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', - offset: 0.067, + offset: 0.1675, + easing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', transform: 'rotateZ(var(--motion-rotate, 0deg)) translateX(0%) translateY(-50%) perspective(800px) rotateX(calc(1 * 1 * 15deg)) rotateY(calc(0 * 1 * 15deg)) translateX(calc(-1 * 0%)) translateY(calc(-1 * -50%))', }, { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', - offset: 0.20234, - transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(0%) translateY(-50%) perspective(800px) rotateX(calc(1 * -0.7 * 15deg)) rotateY(calc(0 * -0.7 * 15deg)) translateX(calc(-1 * 0%)) translateY(calc(-1 * -50%))', - }, - { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', - offset: 0.33768000000000004, - transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(0%) translateY(-50%) perspective(800px) rotateX(calc(1 * 0.6 * 15deg)) rotateY(calc(0 * 0.6 * 15deg)) translateX(calc(-1 * 0%)) translateY(calc(-1 * -50%))', - }, - { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', - offset: 0.4596200000000001, - transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(0%) translateY(-50%) perspective(800px) rotateX(calc(1 * -0.3 * 15deg)) rotateY(calc(0 * -0.3 * 15deg)) translateX(calc(-1 * 0%)) translateY(calc(-1 * -50%))', - }, - { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', - offset: 0.56749, - transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(0%) translateY(-50%) perspective(800px) rotateX(calc(1 * 0.2 * 15deg)) rotateY(calc(0 * 0.2 * 15deg)) translateX(calc(-1 * 0%)) translateY(calc(-1 * -50%))', - }, - { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', - offset: 0.70283, + offset: 0.5025000000000001, + easing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(0%) translateY(-50%) perspective(800px) rotateX(calc(1 * -0.05 * 15deg)) rotateY(calc(0 * -0.05 * 15deg)) translateX(calc(-1 * 0%)) translateY(calc(-1 * -50%))', + 'rotateZ(var(--motion-rotate, 0deg)) translateX(0%) translateY(-50%) perspective(800px) rotateX(calc(1 * -1 * 15deg)) rotateY(calc(0 * -1 * 15deg)) translateX(calc(-1 * 0%)) translateY(calc(-1 * -50%))', }, { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', - offset: 0.7966300000000001, + offset: 0.67, transform: 'rotateZ(var(--motion-rotate, 0deg)) translateX(0%) translateY(-50%) perspective(800px) rotateX(calc(1 * 0 * 15deg)) rotateY(calc(0 * 0 * 15deg)) translateX(calc(-1 * 0%)) translateY(calc(-1 * -50%))', }, @@ -155,23 +130,24 @@ describe('Fold', () => { '--motion-rotate-angle': '15deg', '--motion-rotate-x': '0', '--motion-rotate-y': '1', + '--motion-perspective': '800px', }, keyframes: [ { - easing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', offset: 0, + easing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', transform: 'rotateZ(var(--motion-rotate, 0deg)) translateX(50%) translateY(0%) perspective(800px) rotateX(calc(0 * 0 * 15deg)) rotateY(calc(1 * 0 * 15deg)) translateX(calc(-1 * 50%)) translateY(calc(-1 * 0%))', }, { - easing: 'cubic-bezier(0.645, 0.045, 0.355, 1)', offset: 0.25, + easing: 'cubic-bezier(0.645, 0.045, 0.355, 1)', transform: 'rotateZ(var(--motion-rotate, 0deg)) translateX(50%) translateY(0%) perspective(800px) rotateX(calc(0 * 1 * 15deg)) rotateY(calc(1 * 1 * 15deg)) translateX(calc(-1 * 50%)) translateY(calc(-1 * 0%))', }, { - easing: 'cubic-bezier(0.55, 0.055, 0.675, 0.19)', offset: 0.75, + easing: 'cubic-bezier(0.55, 0.055, 0.675, 0.19)', transform: 'rotateZ(var(--motion-rotate, 0deg)) translateX(50%) translateY(0%) perspective(800px) rotateX(calc(0 * -1 * 15deg)) rotateY(calc(1 * -1 * 15deg)) translateX(calc(-1 * 50%)) translateY(calc(-1 * 0%))', }, @@ -204,23 +180,24 @@ describe('Fold', () => { '--motion-rotate-angle': '15deg', '--motion-rotate-x': '0', '--motion-rotate-y': '-1', + '--motion-perspective': '800px', }, keyframes: [ { - easing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', offset: 0, + easing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', transform: 'rotateZ(var(--motion-rotate, 0deg)) translateX(-50%) translateY(0%) perspective(800px) rotateX(calc(0 * 0 * 15deg)) rotateY(calc(-1 * 0 * 15deg)) translateX(calc(-1 * -50%)) translateY(calc(-1 * 0%))', }, { - easing: 'cubic-bezier(0.645, 0.045, 0.355, 1)', offset: 0.25, + easing: 'cubic-bezier(0.645, 0.045, 0.355, 1)', transform: 'rotateZ(var(--motion-rotate, 0deg)) translateX(-50%) translateY(0%) perspective(800px) rotateX(calc(0 * 1 * 15deg)) rotateY(calc(-1 * 1 * 15deg)) translateX(calc(-1 * -50%)) translateY(calc(-1 * 0%))', }, { - easing: 'cubic-bezier(0.55, 0.055, 0.675, 0.19)', offset: 0.75, + easing: 'cubic-bezier(0.55, 0.055, 0.675, 0.19)', transform: 'rotateZ(var(--motion-rotate, 0deg)) translateX(-50%) translateY(0%) perspective(800px) rotateX(calc(0 * -1 * 15deg)) rotateY(calc(-1 * -1 * 15deg)) translateX(calc(-1 * -50%)) translateY(calc(-1 * 0%))', }, @@ -253,23 +230,24 @@ describe('Fold', () => { '--motion-rotate-angle': '15deg', '--motion-rotate-x': '-1', '--motion-rotate-y': '0', + '--motion-perspective': '800px', }, keyframes: [ { - easing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', offset: 0, + easing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', transform: 'rotateZ(var(--motion-rotate, 0deg)) translateX(0%) translateY(50%) perspective(800px) rotateX(calc(-1 * 0 * 15deg)) rotateY(calc(0 * 0 * 15deg)) translateX(calc(-1 * 0%)) translateY(calc(-1 * 50%))', }, { - easing: 'cubic-bezier(0.645, 0.045, 0.355, 1)', offset: 0.25, + easing: 'cubic-bezier(0.645, 0.045, 0.355, 1)', transform: 'rotateZ(var(--motion-rotate, 0deg)) translateX(0%) translateY(50%) perspective(800px) rotateX(calc(-1 * 1 * 15deg)) rotateY(calc(0 * 1 * 15deg)) translateX(calc(-1 * 0%)) translateY(calc(-1 * 50%))', }, { - easing: 'cubic-bezier(0.55, 0.055, 0.675, 0.19)', offset: 0.75, + easing: 'cubic-bezier(0.55, 0.055, 0.675, 0.19)', transform: 'rotateZ(var(--motion-rotate, 0deg)) translateX(0%) translateY(50%) perspective(800px) rotateX(calc(-1 * -1 * 15deg)) rotateY(calc(0 * -1 * 15deg)) translateX(calc(-1 * 0%)) translateY(calc(-1 * 50%))', }, @@ -302,23 +280,24 @@ describe('Fold', () => { '--motion-rotate-angle': '30deg', '--motion-rotate-x': '1', '--motion-rotate-y': '0', + '--motion-perspective': '800px', }, keyframes: [ { - easing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', offset: 0, + easing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', transform: 'rotateZ(var(--motion-rotate, 0deg)) translateX(0%) translateY(-50%) perspective(800px) rotateX(calc(1 * 0 * 30deg)) rotateY(calc(0 * 0 * 30deg)) translateX(calc(-1 * 0%)) translateY(calc(-1 * -50%))', }, { - easing: 'cubic-bezier(0.645, 0.045, 0.355, 1)', offset: 0.25, + easing: 'cubic-bezier(0.645, 0.045, 0.355, 1)', transform: 'rotateZ(var(--motion-rotate, 0deg)) translateX(0%) translateY(-50%) perspective(800px) rotateX(calc(1 * 1 * 30deg)) rotateY(calc(0 * 1 * 30deg)) translateX(calc(-1 * 0%)) translateY(calc(-1 * -50%))', }, { - easing: 'cubic-bezier(0.55, 0.055, 0.675, 0.19)', offset: 0.75, + easing: 'cubic-bezier(0.55, 0.055, 0.675, 0.19)', transform: 'rotateZ(var(--motion-rotate, 0deg)) translateX(0%) translateY(-50%) perspective(800px) rotateX(calc(1 * -1 * 30deg)) rotateY(calc(0 * -1 * 30deg)) translateX(calc(-1 * 0%)) translateY(calc(-1 * -50%))', }, @@ -354,23 +333,24 @@ describe('Fold', () => { '--motion-rotate-angle': '15deg', '--motion-rotate-x': '1', '--motion-rotate-y': '0', + '--motion-perspective': '800px', }, keyframes: [ { - easing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', offset: 0, + easing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', transform: 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * 0 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * 0 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', }, { - easing: 'cubic-bezier(0.645, 0.045, 0.355, 1)', offset: 0.25, + easing: 'cubic-bezier(0.645, 0.045, 0.355, 1)', transform: 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * 1 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * 1 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', }, { - easing: 'cubic-bezier(0.55, 0.055, 0.675, 0.19)', offset: 0.75, + easing: 'cubic-bezier(0.55, 0.055, 0.675, 0.19)', transform: 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * -1 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * -1 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', }, @@ -403,23 +383,24 @@ describe('Fold', () => { '--motion-rotate-angle': '15deg', '--motion-rotate-x': '0', '--motion-rotate-y': '1', + '--motion-perspective': '800px', }, keyframes: [ { - easing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', offset: 0, + easing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', transform: 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * 0 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * 0 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', }, { - easing: 'cubic-bezier(0.645, 0.045, 0.355, 1)', offset: 0.25, + easing: 'cubic-bezier(0.645, 0.045, 0.355, 1)', transform: 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * 1 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * 1 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', }, { - easing: 'cubic-bezier(0.55, 0.055, 0.675, 0.19)', offset: 0.75, + easing: 'cubic-bezier(0.55, 0.055, 0.675, 0.19)', transform: 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * -1 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * -1 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', }, @@ -445,11 +426,9 @@ describe('Fold', () => { namedEffect: { iterationDelay: 500 } as Fold, }; - const dynamicName = `motion-fold-067`; - const expectedResult: Partial[] = [ { - name: dynamicName, + name: 'motion-fold-067-qiboha', duration: 1500, custom: { '--motion-origin-x': '0%', @@ -457,53 +436,29 @@ describe('Fold', () => { '--motion-rotate-angle': '15deg', '--motion-rotate-x': '1', '--motion-rotate-y': '0', + '--motion-perspective': '800px', }, keyframes: [ { - easing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', offset: 0, + easing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', transform: 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * 0 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * 0 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', }, { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', - offset: 0.067, + offset: 0.1675, + easing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', transform: 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * 1 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * 1 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', }, { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', - offset: 0.20234, - transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * -0.7 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * -0.7 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', - }, - { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', - offset: 0.33768000000000004, - transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * 0.6 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * 0.6 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', - }, - { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', - offset: 0.4596200000000001, - transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * -0.3 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * -0.3 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', - }, - { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', - offset: 0.56749, - transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * 0.2 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * 0.2 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', - }, - { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', - offset: 0.70283, + offset: 0.5025000000000001, + easing: 'cubic-bezier(0.215, 0.61, 0.355, 1)', transform: - 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * -0.05 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * -0.05 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', + 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * -1 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * -1 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', }, { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', - offset: 0.7966300000000001, + offset: 0.67, transform: 'rotateZ(var(--motion-rotate, 0deg)) translateX(var(--motion-origin-x)) translateY(var(--motion-origin-y)) perspective(var(--motion-perspective)) rotateX(calc(var(--motion-rotate-x) * 0 * var(--motion-rotate-angle))) rotateY(calc(var(--motion-rotate-y) * 0 * var(--motion-rotate-angle))) translateX(calc(-1 * var(--motion-origin-x))) translateY(calc(-1 * var(--motion-origin-y)))', }, diff --git a/packages/motion-presets/src/library/ongoing/test/Jello.spec.ts b/packages/motion-presets/src/library/ongoing/test/Jello.spec.ts index 93f624f2..01155d6f 100644 --- a/packages/motion-presets/src/library/ongoing/test/Jello.spec.ts +++ b/packages/motion-presets/src/library/ongoing/test/Jello.spec.ts @@ -17,28 +17,32 @@ describe('Jello.web()', () => { duration: 1, easing: 'linear', custom: { - '--motion-skew-y': 1.75, + '--motion-skew-y-24': '12.25deg', + '--motion-skew-y-38': '-3.5deg', + '--motion-skew-y-58': '7deg', + '--motion-skew-y-80': '-3.5deg', + '--motion-skew-y-100': '0deg', }, keyframes: [ { offset: 0.24, - transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(1.75 * 7deg))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(12.25deg)', }, { offset: 0.38, - transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(1.75 * -2deg))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(-3.5deg)', }, { offset: 0.58, - transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(1.75 * 4deg))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(7deg)', }, { offset: 0.8, - transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(1.75 * -2deg))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(-3.5deg)', }, { offset: 1, - transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(1.75 * 0deg))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(0deg)', }, ], }, @@ -62,28 +66,36 @@ describe('Jello.web()', () => { duration: 1500, easing: 'linear', custom: { - '--motion-skew-y': 3.4, + '--motion-skew-y-24': '23.8deg', + '--motion-skew-y-38': '-6.8deg', + '--motion-skew-y-58': '13.6deg', + '--motion-skew-y-80': '-6.8deg', + '--motion-skew-y-100': '0deg', }, keyframes: [ { offset: 0.1608, - transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(3.4 * 7deg))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(23.8deg)', }, { offset: 0.2546, - transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(3.4 * -2deg))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(-6.8deg)', }, { offset: 0.3886, - transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(3.4 * 4deg))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(13.6deg)', }, { offset: 0.536, - transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(3.4 * -2deg))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(-6.8deg)', }, { offset: 0.67, - transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(3.4 * 0deg))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(0deg)', + }, + { + offset: 1, + transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(0deg)', }, ], }, @@ -108,33 +120,32 @@ describe('Jello.style()', () => { duration: 1, easing: 'linear', custom: { - '--motion-skew-y': 1.75, + '--motion-skew-y-24': '12.25deg', + '--motion-skew-y-38': '-3.5deg', + '--motion-skew-y-58': '7deg', + '--motion-skew-y-80': '-3.5deg', + '--motion-skew-y-100': '0deg', }, keyframes: [ { offset: 0.24, - transform: - 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(var(--motion-skew-y) * 7deg))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(var(--motion-skew-y-24))', }, { offset: 0.38, - transform: - 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(var(--motion-skew-y) * -2deg))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(var(--motion-skew-y-38))', }, { offset: 0.58, - transform: - 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(var(--motion-skew-y) * 4deg))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(var(--motion-skew-y-58))', }, { offset: 0.8, - transform: - 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(var(--motion-skew-y) * -2deg))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(var(--motion-skew-y-80))', }, { offset: 1, - transform: - 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(var(--motion-skew-y) * 0deg))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(var(--motion-skew-y-100))', }, ], }, @@ -158,33 +169,36 @@ describe('Jello.style()', () => { duration: 1500, easing: 'linear', custom: { - '--motion-skew-y': 3.4, + '--motion-skew-y-24': '23.8deg', + '--motion-skew-y-38': '-6.8deg', + '--motion-skew-y-58': '13.6deg', + '--motion-skew-y-80': '-6.8deg', + '--motion-skew-y-100': '0deg', }, keyframes: [ { offset: 0.1608, - transform: - 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(var(--motion-skew-y) * 7deg))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(var(--motion-skew-y-24))', }, { offset: 0.2546, - transform: - 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(var(--motion-skew-y) * -2deg))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(var(--motion-skew-y-38))', }, { offset: 0.3886, - transform: - 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(var(--motion-skew-y) * 4deg))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(var(--motion-skew-y-58))', }, { offset: 0.536, - transform: - 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(var(--motion-skew-y) * -2deg))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(var(--motion-skew-y-80))', }, { offset: 0.67, - transform: - 'rotateZ(var(--motion-rotate, 0deg)) skewY(calc(var(--motion-skew-y) * 0deg))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(var(--motion-skew-y-100))', + }, + { + offset: 1, + transform: 'rotateZ(var(--motion-rotate, 0deg)) skewY(var(--motion-skew-y-100))', }, ], }, @@ -195,3 +209,16 @@ describe('Jello.style()', () => { expect(result).toMatchObject(expectedResult); }); }); + +describe('Jello skew', () => { + test.each([12.25, 7, 28, -10])('the peak is the skew %s', (skew) => { + const skews = JelloAnimation.web({ + ...baseMockOptions, + namedEffect: { skew } as Jello, + })[0].keyframes.map(({ transform }) => + parseFloat((transform as string).match(/skewY\((-?[\d.]+)deg\)/)![1]), + ); + expect(skews[0]).toBe(skew); + expect(Math.max(...skews.map(Math.abs))).toBe(Math.abs(skew)); + }); +}); diff --git a/packages/motion-presets/src/library/ongoing/test/Poke.spec.ts b/packages/motion-presets/src/library/ongoing/test/Poke.spec.ts index 9336da83..0573b032 100644 --- a/packages/motion-presets/src/library/ongoing/test/Poke.spec.ts +++ b/packages/motion-presets/src/library/ongoing/test/Poke.spec.ts @@ -21,36 +21,41 @@ describe('Poke', () => { custom: { '--motion-translate-x': 1, '--motion-translate-y': 0, - '--motion-poke-unit': '2.5px', + '--motion-poke-travel-17': '17.5px', + '--motion-poke-travel-32': '62.5px', + '--motion-poke-travel-48': '20px', + '--motion-poke-travel-56': '27.5px', + '--motion-poke-travel-66': '62.5px', + '--motion-poke-travel-83': '10px', }, keyframes: [ { offset: 0.17, - translate: 'calc(1 * 7 * 2.5px) calc(0 * 7 * 2.5px)', + translate: 'calc(1 * 17.5px) calc(0 * 17.5px)', }, { offset: 0.32, - translate: 'calc(1 * 25 * 2.5px) calc(0 * 25 * 2.5px)', + translate: 'calc(1 * 62.5px) calc(0 * 62.5px)', }, { offset: 0.48, - translate: 'calc(1 * 8 * 2.5px) calc(0 * 8 * 2.5px)', + translate: 'calc(1 * 20px) calc(0 * 20px)', }, { offset: 0.56, - translate: 'calc(1 * 11 * 2.5px) calc(0 * 11 * 2.5px)', + translate: 'calc(1 * 27.5px) calc(0 * 27.5px)', }, { offset: 0.66, - translate: 'calc(1 * 25 * 2.5px) calc(0 * 25 * 2.5px)', + translate: 'calc(1 * 62.5px) calc(0 * 62.5px)', }, { offset: 0.83, - translate: 'calc(1 * 4 * 2.5px) calc(0 * 4 * 2.5px)', + translate: 'calc(1 * 10px) calc(0 * 10px)', }, { offset: 1, - translate: 'calc(1 * 0 * 2.5px) calc(0 * 0 * 2.5px)', + translate: '0px 0px', }, ], }, @@ -76,12 +81,18 @@ describe('Poke', () => { expect(result[0].custom).toEqual({ '--motion-translate-x': 1, '--motion-translate-y': 0, - '--motion-poke-unit': '2.5px', + '--motion-poke-travel-17': '17.5px', + '--motion-poke-travel-32': '62.5px', + '--motion-poke-travel-48': '20px', + '--motion-poke-travel-56': '27.5px', + '--motion-poke-travel-66': '62.5px', + '--motion-poke-travel-83': '10px', }); - expect(result[0].keyframes).toHaveLength(7); + expect(result[0].keyframes).toHaveLength(8); // Check approximate offset values due to floating point precision expect(result[0].keyframes[0].offset).toBeCloseTo(0.1139, 3); expect(result[0].keyframes[6].offset).toBeCloseTo(0.67, 2); + expect(result[0].keyframes[7]).toMatchObject({ offset: 1, translate: '0px 0px' }); }); test('Poke animation with custom travel', () => { @@ -94,9 +105,8 @@ describe('Poke', () => { const result = PokeAnimation.web(mockOptions); expect(result[0].name).toBe('motion-poke-1'); - // Check the intensity calculation with tolerance for floating point precision - expect(result[0].custom!['--motion-poke-unit']).toBe('3.4px'); - expect(result[0].keyframes[0].translate).toContain('3.4'); + expect(result[0].custom!['--motion-poke-travel-32']).toBe('85px'); + expect(result[0].keyframes[1].translate).toBe('calc(1 * 85px) calc(0 * 85px)'); }); test('Poke animation with custom direction - top', () => { @@ -112,36 +122,41 @@ describe('Poke', () => { custom: { '--motion-translate-x': 0, '--motion-translate-y': -1, - '--motion-poke-unit': '2.5px', + '--motion-poke-travel-17': '17.5px', + '--motion-poke-travel-32': '62.5px', + '--motion-poke-travel-48': '20px', + '--motion-poke-travel-56': '27.5px', + '--motion-poke-travel-66': '62.5px', + '--motion-poke-travel-83': '10px', }, keyframes: [ { offset: 0.17, - translate: 'calc(0 * 7 * 2.5px) calc(-1 * 7 * 2.5px)', + translate: 'calc(0 * 17.5px) calc(-1 * 17.5px)', }, { offset: 0.32, - translate: 'calc(0 * 25 * 2.5px) calc(-1 * 25 * 2.5px)', + translate: 'calc(0 * 62.5px) calc(-1 * 62.5px)', }, { offset: 0.48, - translate: 'calc(0 * 8 * 2.5px) calc(-1 * 8 * 2.5px)', + translate: 'calc(0 * 20px) calc(-1 * 20px)', }, { offset: 0.56, - translate: 'calc(0 * 11 * 2.5px) calc(-1 * 11 * 2.5px)', + translate: 'calc(0 * 27.5px) calc(-1 * 27.5px)', }, { offset: 0.66, - translate: 'calc(0 * 25 * 2.5px) calc(-1 * 25 * 2.5px)', + translate: 'calc(0 * 62.5px) calc(-1 * 62.5px)', }, { offset: 0.83, - translate: 'calc(0 * 4 * 2.5px) calc(-1 * 4 * 2.5px)', + translate: 'calc(0 * 10px) calc(-1 * 10px)', }, { offset: 1, - translate: 'calc(0 * 0 * 2.5px) calc(-1 * 0 * 2.5px)', + translate: '0px 0px', }, ], }, @@ -165,36 +180,41 @@ describe('Poke', () => { custom: { '--motion-translate-x': 0, '--motion-translate-y': 1, - '--motion-poke-unit': '2.5px', + '--motion-poke-travel-17': '17.5px', + '--motion-poke-travel-32': '62.5px', + '--motion-poke-travel-48': '20px', + '--motion-poke-travel-56': '27.5px', + '--motion-poke-travel-66': '62.5px', + '--motion-poke-travel-83': '10px', }, keyframes: [ { offset: 0.17, - translate: 'calc(0 * 7 * 2.5px) calc(1 * 7 * 2.5px)', + translate: 'calc(0 * 17.5px) calc(1 * 17.5px)', }, { offset: 0.32, - translate: 'calc(0 * 25 * 2.5px) calc(1 * 25 * 2.5px)', + translate: 'calc(0 * 62.5px) calc(1 * 62.5px)', }, { offset: 0.48, - translate: 'calc(0 * 8 * 2.5px) calc(1 * 8 * 2.5px)', + translate: 'calc(0 * 20px) calc(1 * 20px)', }, { offset: 0.56, - translate: 'calc(0 * 11 * 2.5px) calc(1 * 11 * 2.5px)', + translate: 'calc(0 * 27.5px) calc(1 * 27.5px)', }, { offset: 0.66, - translate: 'calc(0 * 25 * 2.5px) calc(1 * 25 * 2.5px)', + translate: 'calc(0 * 62.5px) calc(1 * 62.5px)', }, { offset: 0.83, - translate: 'calc(0 * 4 * 2.5px) calc(1 * 4 * 2.5px)', + translate: 'calc(0 * 10px) calc(1 * 10px)', }, { offset: 1, - translate: 'calc(0 * 0 * 2.5px) calc(1 * 0 * 2.5px)', + translate: '0px 0px', }, ], }, @@ -218,36 +238,41 @@ describe('Poke', () => { custom: { '--motion-translate-x': -1, '--motion-translate-y': 0, - '--motion-poke-unit': '2.5px', + '--motion-poke-travel-17': '17.5px', + '--motion-poke-travel-32': '62.5px', + '--motion-poke-travel-48': '20px', + '--motion-poke-travel-56': '27.5px', + '--motion-poke-travel-66': '62.5px', + '--motion-poke-travel-83': '10px', }, keyframes: [ { offset: 0.17, - translate: 'calc(-1 * 7 * 2.5px) calc(0 * 7 * 2.5px)', + translate: 'calc(-1 * 17.5px) calc(0 * 17.5px)', }, { offset: 0.32, - translate: 'calc(-1 * 25 * 2.5px) calc(0 * 25 * 2.5px)', + translate: 'calc(-1 * 62.5px) calc(0 * 62.5px)', }, { offset: 0.48, - translate: 'calc(-1 * 8 * 2.5px) calc(0 * 8 * 2.5px)', + translate: 'calc(-1 * 20px) calc(0 * 20px)', }, { offset: 0.56, - translate: 'calc(-1 * 11 * 2.5px) calc(0 * 11 * 2.5px)', + translate: 'calc(-1 * 27.5px) calc(0 * 27.5px)', }, { offset: 0.66, - translate: 'calc(-1 * 25 * 2.5px) calc(0 * 25 * 2.5px)', + translate: 'calc(-1 * 62.5px) calc(0 * 62.5px)', }, { offset: 0.83, - translate: 'calc(-1 * 4 * 2.5px) calc(0 * 4 * 2.5px)', + translate: 'calc(-1 * 10px) calc(0 * 10px)', }, { offset: 1, - translate: 'calc(-1 * 0 * 2.5px) calc(0 * 0 * 2.5px)', + translate: '0px 0px', }, ], }, @@ -275,43 +300,47 @@ describe('Poke', () => { custom: { '--motion-translate-x': 1, '--motion-translate-y': 0, - '--motion-poke-unit': '2.5px', + '--motion-poke-travel-17': '17.5px', + '--motion-poke-travel-32': '62.5px', + '--motion-poke-travel-48': '20px', + '--motion-poke-travel-56': '27.5px', + '--motion-poke-travel-66': '62.5px', + '--motion-poke-travel-83': '10px', }, keyframes: [ { offset: 0.17, translate: - 'calc(var(--motion-translate-x) * 7 * var(--motion-poke-unit)) calc(var(--motion-translate-y) * 7 * var(--motion-poke-unit))', + 'calc(var(--motion-translate-x) * var(--motion-poke-travel-17)) calc(var(--motion-translate-y) * var(--motion-poke-travel-17))', }, { offset: 0.32, translate: - 'calc(var(--motion-translate-x) * 25 * var(--motion-poke-unit)) calc(var(--motion-translate-y) * 25 * var(--motion-poke-unit))', + 'calc(var(--motion-translate-x) * var(--motion-poke-travel-32)) calc(var(--motion-translate-y) * var(--motion-poke-travel-32))', }, { offset: 0.48, translate: - 'calc(var(--motion-translate-x) * 8 * var(--motion-poke-unit)) calc(var(--motion-translate-y) * 8 * var(--motion-poke-unit))', + 'calc(var(--motion-translate-x) * var(--motion-poke-travel-48)) calc(var(--motion-translate-y) * var(--motion-poke-travel-48))', }, { offset: 0.56, translate: - 'calc(var(--motion-translate-x) * 11 * var(--motion-poke-unit)) calc(var(--motion-translate-y) * 11 * var(--motion-poke-unit))', + 'calc(var(--motion-translate-x) * var(--motion-poke-travel-56)) calc(var(--motion-translate-y) * var(--motion-poke-travel-56))', }, { offset: 0.66, translate: - 'calc(var(--motion-translate-x) * 25 * var(--motion-poke-unit)) calc(var(--motion-translate-y) * 25 * var(--motion-poke-unit))', + 'calc(var(--motion-translate-x) * var(--motion-poke-travel-66)) calc(var(--motion-translate-y) * var(--motion-poke-travel-66))', }, { offset: 0.83, translate: - 'calc(var(--motion-translate-x) * 4 * var(--motion-poke-unit)) calc(var(--motion-translate-y) * 4 * var(--motion-poke-unit))', + 'calc(var(--motion-translate-x) * var(--motion-poke-travel-83)) calc(var(--motion-translate-y) * var(--motion-poke-travel-83))', }, { offset: 1, - translate: - 'calc(var(--motion-translate-x) * 0 * var(--motion-poke-unit)) calc(var(--motion-translate-y) * 0 * var(--motion-poke-unit))', + translate: '0px 0px', }, ], }, @@ -337,13 +366,19 @@ describe('Poke', () => { expect(result[0].custom).toEqual({ '--motion-translate-x': 1, '--motion-translate-y': 0, - '--motion-poke-unit': '2.5px', + '--motion-poke-travel-17': '17.5px', + '--motion-poke-travel-32': '62.5px', + '--motion-poke-travel-48': '20px', + '--motion-poke-travel-56': '27.5px', + '--motion-poke-travel-66': '62.5px', + '--motion-poke-travel-83': '10px', }); - expect(result[0].keyframes).toHaveLength(7); + expect(result[0].keyframes).toHaveLength(8); // Check approximate offset values due to floating point precision expect(result[0].keyframes[0].offset).toBeCloseTo(0.136, 3); expect(result[0].keyframes[3].offset).toBeCloseTo(0.448, 3); expect(result[0].keyframes[6].offset).toBe(0.8); + expect(result[0].keyframes[7]).toMatchObject({ offset: 1, translate: '0px 0px' }); }); test('Poke.style animation with counter-clockwise direction', () => { @@ -359,43 +394,47 @@ describe('Poke', () => { custom: { '--motion-translate-x': 0, '--motion-translate-y': -1, - '--motion-poke-unit': '2.5px', + '--motion-poke-travel-17': '17.5px', + '--motion-poke-travel-32': '62.5px', + '--motion-poke-travel-48': '20px', + '--motion-poke-travel-56': '27.5px', + '--motion-poke-travel-66': '62.5px', + '--motion-poke-travel-83': '10px', }, keyframes: [ { offset: 0.17, translate: - 'calc(var(--motion-translate-x) * 7 * var(--motion-poke-unit)) calc(var(--motion-translate-y) * 7 * var(--motion-poke-unit))', + 'calc(var(--motion-translate-x) * var(--motion-poke-travel-17)) calc(var(--motion-translate-y) * var(--motion-poke-travel-17))', }, { offset: 0.32, translate: - 'calc(var(--motion-translate-x) * 25 * var(--motion-poke-unit)) calc(var(--motion-translate-y) * 25 * var(--motion-poke-unit))', + 'calc(var(--motion-translate-x) * var(--motion-poke-travel-32)) calc(var(--motion-translate-y) * var(--motion-poke-travel-32))', }, { offset: 0.48, translate: - 'calc(var(--motion-translate-x) * 8 * var(--motion-poke-unit)) calc(var(--motion-translate-y) * 8 * var(--motion-poke-unit))', + 'calc(var(--motion-translate-x) * var(--motion-poke-travel-48)) calc(var(--motion-translate-y) * var(--motion-poke-travel-48))', }, { offset: 0.56, translate: - 'calc(var(--motion-translate-x) * 11 * var(--motion-poke-unit)) calc(var(--motion-translate-y) * 11 * var(--motion-poke-unit))', + 'calc(var(--motion-translate-x) * var(--motion-poke-travel-56)) calc(var(--motion-translate-y) * var(--motion-poke-travel-56))', }, { offset: 0.66, translate: - 'calc(var(--motion-translate-x) * 25 * var(--motion-poke-unit)) calc(var(--motion-translate-y) * 25 * var(--motion-poke-unit))', + 'calc(var(--motion-translate-x) * var(--motion-poke-travel-66)) calc(var(--motion-translate-y) * var(--motion-poke-travel-66))', }, { offset: 0.83, translate: - 'calc(var(--motion-translate-x) * 4 * var(--motion-poke-unit)) calc(var(--motion-translate-y) * 4 * var(--motion-poke-unit))', + 'calc(var(--motion-translate-x) * var(--motion-poke-travel-83)) calc(var(--motion-translate-y) * var(--motion-poke-travel-83))', }, { offset: 1, - translate: - 'calc(var(--motion-translate-x) * 0 * var(--motion-poke-unit)) calc(var(--motion-translate-y) * 0 * var(--motion-poke-unit))', + translate: '0px 0px', }, ], }, @@ -433,3 +472,16 @@ describe('Poke', () => { }); }); }); + +describe('Poke travel', () => { + test.each([62.5, 25, 85])('the peak is the travel %spx', (travel) => { + const { keyframes } = PokeAnimation.web({ + ...baseMockOptions, + namedEffect: { travel: { value: travel, unit: 'px' } } as Poke, + })[0]; + const xs = keyframes.map(({ translate }) => + parseFloat((translate as string).match(/^calc\(1 \* (-?[\d.]+)px\)/)?.[1] ?? '0'), + ); + expect(Math.max(...xs)).toBe(travel); + }); +}); diff --git a/packages/motion-presets/src/library/ongoing/test/Pulse.spec.ts b/packages/motion-presets/src/library/ongoing/test/Pulse.spec.ts index 5be9057b..127ba660 100644 --- a/packages/motion-presets/src/library/ongoing/test/Pulse.spec.ts +++ b/packages/motion-presets/src/library/ongoing/test/Pulse.spec.ts @@ -17,24 +17,25 @@ describe('Pulse', () => { name: 'motion-pulse-1', duration: 1, custom: { - '--motion-pulse-offset': 0, + '--motion-scale-27': 0.96, + '--motion-scale-72': 0.93, }, keyframes: [ { offset: 0.27, - transform: 'scale(calc(0.96 - 0))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(0.96)', }, { offset: 0.45, - transform: 'scale(1)', + transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(1)', }, { offset: 0.72, - transform: 'scale(calc(0.93 - 0))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(0.93)', }, { offset: 1, - transform: 'scale(1)', + transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(1)', }, ], }, @@ -57,28 +58,29 @@ describe('Pulse', () => { name: 'motion-pulse-067', duration: 1500, custom: { - '--motion-pulse-offset': 0.096, + '--motion-scale-27': 0.9051, + '--motion-scale-72': 0.834, }, keyframes: [ { offset: 0.18090000000000003, - transform: 'scale(calc(0.96 - 0.096))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(0.9051)', }, { offset: 0.30150000000000005, - transform: 'scale(1)', + transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(1)', }, { offset: 0.4824, - transform: 'scale(calc(0.93 - 0.096))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(0.834)', }, { offset: 0.67, - transform: 'scale(1)', + transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(1)', }, { offset: 1, - transform: 'scale(1)', + transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(1)', }, ], }, @@ -102,24 +104,25 @@ describe('Pulse', () => { name: 'motion-pulse-1', duration: 1, custom: { - '--motion-pulse-offset': 0, + '--motion-scale-27': 0.96, + '--motion-scale-72': 0.93, }, keyframes: [ { offset: 0.27, - transform: 'scale(calc(0.96 - var(--motion-pulse-offset)))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(var(--motion-scale-27))', }, { offset: 0.45, - transform: 'scale(1)', + transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(1)', }, { offset: 0.72, - transform: 'scale(calc(0.93 - var(--motion-pulse-offset)))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(var(--motion-scale-72))', }, { offset: 1, - transform: 'scale(1)', + transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(1)', }, ], }, @@ -142,28 +145,29 @@ describe('Pulse', () => { name: 'motion-pulse-067', duration: 1500, custom: { - '--motion-pulse-offset': 0.096, + '--motion-scale-27': 0.9051, + '--motion-scale-72': 0.834, }, keyframes: [ { offset: 0.18090000000000003, - transform: 'scale(calc(0.96 - var(--motion-pulse-offset)))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(var(--motion-scale-27))', }, { offset: 0.30150000000000005, - transform: 'scale(1)', + transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(1)', }, { offset: 0.4824, - transform: 'scale(calc(0.93 - var(--motion-pulse-offset)))', + transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(var(--motion-scale-72))', }, { offset: 0.67, - transform: 'scale(1)', + transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(1)', }, { offset: 1, - transform: 'scale(1)', + transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(1)', }, ], }, @@ -183,11 +187,33 @@ describe('Pulse', () => { const result = PulseAnimation.style(mockOptions); expect(result[0]).toHaveProperty('custom'); - expect(result[0].custom).toHaveProperty('--motion-pulse-offset'); - - // Verify the custom property contains the expected calculated value - expect(typeof result[0].custom!['--motion-pulse-offset']).toBe('number'); - expect(result[0].custom!['--motion-pulse-offset']).toBe(0.06); // scale 0.87 maps to 0.06 offset + expect(result[0].custom).toEqual({ + '--motion-scale-27': 0.9257, + '--motion-scale-72': 0.87, + }); }); }); }); + +describe('Pulse scale', () => { + const scalesOf = (scale: number) => + PulseAnimation.web({ ...baseMockOptions, namedEffect: { scale } as Pulse })[0].keyframes.map( + ({ transform }) => parseFloat((transform as string).match(/scale\(([^)]+)\)/)![1]), + ); + + test.each([0.93, 0.87, 0.5, 1.2])('the deeper beat is the scale %s', (scale) => { + const scales = scalesOf(scale); + const deepest = scales.reduce((a, b) => (Math.abs(b - 1) > Math.abs(a - 1) ? b : a)); + expect(deepest).toBe(scale); + expect(scales[0]).toBeCloseTo(1 - (4 / 7) * (1 - scale), 4); + }); + + test('keeps the layout rotation', () => { + for (const { transform } of PulseAnimation.style({ + ...baseMockOptions, + namedEffect: {} as Pulse, + })[0].keyframes) { + expect(transform).toMatch(/^rotateZ\(var\(--motion-rotate, 0deg\)\) scale\(/); + } + }); +}); diff --git a/packages/motion-presets/src/library/ongoing/test/Rubber.spec.ts b/packages/motion-presets/src/library/ongoing/test/Rubber.spec.ts index 5f41d04c..701dc60d 100644 --- a/packages/motion-presets/src/library/ongoing/test/Rubber.spec.ts +++ b/packages/motion-presets/src/library/ongoing/test/Rubber.spec.ts @@ -16,20 +16,20 @@ describe('Rubber', () => { { id: 'test-id', duration: 1000, - easing: 'linear', namedEffect: {}, name: 'motion-rubber-1', + easing: 'linear', custom: { - '--motion-scale-x-45': 1.08, - '--motion-scale-y-45': 0.88, - '--motion-scale-x-56': 0.875, - '--motion-scale-y-56': 1.055, - '--motion-scale-x-66': 1.07, - '--motion-scale-y-66': 0.91, + '--motion-scale-x-45': 1.1, + '--motion-scale-y-45': 0.9, + '--motion-scale-x-56': 0.91, + '--motion-scale-y-56': 1.09, + '--motion-scale-x-66': 1.08, + '--motion-scale-y-66': 0.92, '--motion-scale-x-78': 0.955, '--motion-scale-y-78': 1.045, - '--motion-scale-x-89': 1.055, - '--motion-scale-y-89': 0.9495, + '--motion-scale-x-89': 1.0528, + '--motion-scale-y-89': 0.9473, '--motion-scale-x-100': 1, '--motion-scale-y-100': 1, }, @@ -84,35 +84,35 @@ describe('Rubber', () => { { id: 'test-id', duration: 1000, - easing: 'linear', namedEffect: {}, name: 'motion-rubber-1', + easing: 'linear', custom: { - '--motion-scale-x-45': 1.08, - '--motion-scale-y-45': 0.88, - '--motion-scale-x-56': 0.875, - '--motion-scale-y-56': 1.055, - '--motion-scale-x-66': 1.07, - '--motion-scale-y-66': 0.91, + '--motion-scale-x-45': 1.1, + '--motion-scale-y-45': 0.9, + '--motion-scale-x-56': 0.91, + '--motion-scale-y-56': 1.09, + '--motion-scale-x-66': 1.08, + '--motion-scale-y-66': 0.92, '--motion-scale-x-78': 0.955, '--motion-scale-y-78': 1.045, - '--motion-scale-x-89': 1.055, - '--motion-scale-y-89': 0.9495, + '--motion-scale-x-89': 1.0528, + '--motion-scale-y-89': 0.9473, '--motion-scale-x-100': 1, '--motion-scale-y-100': 1, }, keyframes: [ { offset: 0.45, - transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(1.08, 0.88)', + transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(1.1, 0.9)', }, { offset: 0.56, - transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(0.875, 1.055)', + transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(0.91, 1.09)', }, { offset: 0.66, - transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(1.07, 0.91)', + transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(1.08, 0.92)', }, { offset: 0.78, @@ -120,7 +120,7 @@ describe('Rubber', () => { }, { offset: 0.89, - transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(1.055, 0.9495)', + transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(1.0528, 0.9473)', }, { offset: 1, @@ -148,16 +148,16 @@ describe('Rubber', () => { duration: 2000, easing: 'linear', custom: { - '--motion-scale-x-45': 1.11, - '--motion-scale-y-45': 0.85, - '--motion-scale-x-56': 0.86, - '--motion-scale-y-56': 1.07, - '--motion-scale-x-66': 1.1, - '--motion-scale-y-66': 0.88, - '--motion-scale-x-78': 0.94, - '--motion-scale-y-78': 1.06, - '--motion-scale-x-89': 1.085, - '--motion-scale-y-89': 0.9195, + '--motion-scale-x-45': 1.13, + '--motion-scale-y-45': 0.87, + '--motion-scale-x-56': 0.883, + '--motion-scale-y-56': 1.117, + '--motion-scale-x-66': 1.104, + '--motion-scale-y-66': 0.896, + '--motion-scale-x-78': 0.9415, + '--motion-scale-y-78': 1.0585, + '--motion-scale-x-89': 1.0686, + '--motion-scale-y-89': 0.9314, '--motion-scale-x-100': 1, '--motion-scale-y-100': 1, }, @@ -192,6 +192,11 @@ describe('Rubber', () => { transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(var(--motion-scale-x-100), var(--motion-scale-y-100))', }, + { + offset: 1, + transform: + 'rotateZ(var(--motion-rotate, 0deg)) scale(var(--motion-scale-x-100), var(--motion-scale-y-100))', + }, ], }, ]; @@ -214,44 +219,48 @@ describe('Rubber', () => { duration: 2000, easing: 'linear', custom: { - '--motion-scale-x-45': 1.11, - '--motion-scale-y-45': 0.85, - '--motion-scale-x-56': 0.86, - '--motion-scale-y-56': 1.07, - '--motion-scale-x-66': 1.1, - '--motion-scale-y-66': 0.88, - '--motion-scale-x-78': 0.94, - '--motion-scale-y-78': 1.06, - '--motion-scale-x-89': 1.085, - '--motion-scale-y-89': 0.9195, + '--motion-scale-x-45': 1.13, + '--motion-scale-y-45': 0.87, + '--motion-scale-x-56': 0.883, + '--motion-scale-y-56': 1.117, + '--motion-scale-x-66': 1.104, + '--motion-scale-y-66': 0.896, + '--motion-scale-x-78': 0.9415, + '--motion-scale-y-78': 1.0585, + '--motion-scale-x-89': 1.0686, + '--motion-scale-y-89': 0.9314, '--motion-scale-x-100': 1, '--motion-scale-y-100': 1, }, keyframes: [ { offset: 0.225, - transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(1.11, 0.85)', + transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(1.13, 0.87)', }, { offset: 0.28, - transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(0.86, 1.07)', + transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(0.883, 1.117)', }, { offset: 0.33, - transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(1.1, 0.88)', + transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(1.104, 0.896)', }, { offset: 0.39, - transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(0.94, 1.06)', + transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(0.9415, 1.0585)', }, { offset: 0.445, - transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(1.085, 0.9195)', + transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(1.0686, 0.9314)', }, { offset: 0.5, transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(1, 1)', }, + { + offset: 1, + transform: 'rotateZ(var(--motion-rotate, 0deg)) scale(1, 1)', + }, ], }, ]; @@ -261,3 +270,23 @@ describe('Rubber', () => { expect(result).toMatchObject(expectedResult); }); }); + +describe('Rubber stretch', () => { + test.each([0.1, 0.13, 0.05, -0.2])('the first squash is 1 ± %s', (stretch) => { + const scales = Rubber.web({ + ...baseMockOptions, + namedEffect: { stretch } as RubberType, + })[0].keyframes.map(({ transform }) => + (transform as string) + .match(/scale\(([^,]+), ([^)]+)\)/)! + .slice(1) + .map(parseFloat), + ); + const [peakX, peakY] = scales.reduce((a, b) => + Math.abs(b[0] - 1) > Math.abs(a[0] - 1) ? b : a, + ); + expect(scales[0]).toEqual([peakX, peakY]); + expect(peakX).toBeCloseTo(1 + stretch, 10); + expect(peakY).toBeCloseTo(1 - stretch, 10); + }); +}); diff --git a/packages/motion-presets/src/library/ongoing/test/Spin.spec.ts b/packages/motion-presets/src/library/ongoing/test/Spin.spec.ts index ce1023ec..df98d283 100644 --- a/packages/motion-presets/src/library/ongoing/test/Spin.spec.ts +++ b/packages/motion-presets/src/library/ongoing/test/Spin.spec.ts @@ -20,17 +20,17 @@ describe('Spin', () => { easing: 'linear', duration: 1000, custom: { - '--motion-rotate-start': 'calc(var(--motion-rotate, 0deg) + -360deg)', + '--motion-rotate-start': '-360deg', }, keyframes: [ { offset: 0, easing: 'linear', - rotate: 'calc(var(--motion-rotate, 0deg) + -360deg)', + rotate: '-360deg', }, { offset: 1, - rotate: 'var(--motion-rotate, 0deg)', + rotate: '0deg', }, ], }, @@ -52,22 +52,28 @@ describe('Spin', () => { const expectedResult: Partial[] = [ { - name: 'motion-spin-067', + name: 'motion-spin-067-quintInOut', easing: 'linear', - namedEffect: { iterationDelay: 500 } as Spin, + namedEffect: { + iterationDelay: 500, + } as Spin, duration: 1500, custom: { - '--motion-rotate-start': 'calc(var(--motion-rotate, 0deg) + -360deg)', + '--motion-rotate-start': '-360deg', }, keyframes: [ { offset: 0, easing: 'cubic-bezier(0.86, 0, 0.07, 1)', - rotate: 'calc(var(--motion-rotate, 0deg) + -360deg)', + rotate: '-360deg', }, { offset: 0.67, - rotate: 'var(--motion-rotate, 0deg)', + rotate: '0deg', + }, + { + offset: 1, + rotate: '0deg', }, ], }, @@ -89,17 +95,17 @@ describe('Spin', () => { { name: 'motion-spin-1', custom: { - '--motion-rotate-start': 'calc(var(--motion-rotate, 0deg) + 360deg)', + '--motion-rotate-start': '360deg', }, keyframes: [ { offset: 0, easing: 'linear', - rotate: 'calc(var(--motion-rotate, 0deg) + 360deg)', + rotate: '360deg', }, { offset: 1, - rotate: 'var(--motion-rotate, 0deg)', + rotate: '0deg', }, ], }, @@ -126,7 +132,7 @@ describe('Spin', () => { easing: 'linear', duration: 1000, custom: { - '--motion-rotate-start': 'calc(var(--motion-rotate, 0deg) + -360deg)', + '--motion-rotate-start': '-360deg', }, keyframes: [ { @@ -136,7 +142,7 @@ describe('Spin', () => { }, { offset: 1, - rotate: 'var(--motion-rotate, 0deg)', + rotate: '0deg', }, ], }, @@ -159,10 +165,12 @@ describe('Spin', () => { { name: 'motion-spin-08', easing: 'linear', - namedEffect: { iterationDelay: 200 } as Spin, + namedEffect: { + iterationDelay: 200, + } as Spin, duration: 1000, custom: { - '--motion-rotate-start': 'calc(var(--motion-rotate, 0deg) + -360deg)', + '--motion-rotate-start': '-360deg', }, keyframes: [ { @@ -171,8 +179,12 @@ describe('Spin', () => { rotate: 'var(--motion-rotate-start)', }, { - offset: 0.8, // duration / (duration + delay) - rotate: 'var(--motion-rotate, 0deg)', + offset: 0.8, + rotate: '0deg', + }, + { + offset: 1, + rotate: '0deg', }, ], }, @@ -194,7 +206,7 @@ describe('Spin', () => { { name: 'motion-spin-1', custom: { - '--motion-rotate-start': 'calc(var(--motion-rotate, 0deg) + 360deg)', + '--motion-rotate-start': '360deg', }, keyframes: [ { @@ -204,7 +216,7 @@ describe('Spin', () => { }, { offset: 1, - rotate: 'var(--motion-rotate, 0deg)', + rotate: '0deg', }, ], }, @@ -216,3 +228,23 @@ describe('Spin', () => { }); }); }); + +describe('Spin rotation', () => { + test.each(['clockwise', 'counter-clockwise'])( + 'turns %s without adding the layout rotation', + (direction) => { + for (const method of [SpinAnimation.web, SpinAnimation.style]) { + const [animation] = method({ + ...baseMockOptions, + namedEffect: { direction } as Spin, + }); + expect(JSON.stringify(animation)).not.toContain('--motion-rotate,'); + expect(animation.keyframes.at(-1)!.rotate).toBe('0deg'); + } + expect( + SpinAnimation.web({ ...baseMockOptions, namedEffect: { direction } as Spin })[0] + .keyframes[0].rotate, + ).toBe(direction === 'clockwise' ? '-360deg' : '360deg'); + }, + ); +}); diff --git a/packages/motion-presets/src/library/ongoing/test/Swing.spec.ts b/packages/motion-presets/src/library/ongoing/test/Swing.spec.ts index 062225c5..c94aab55 100644 --- a/packages/motion-presets/src/library/ongoing/test/Swing.spec.ts +++ b/packages/motion-presets/src/library/ongoing/test/Swing.spec.ts @@ -20,26 +20,23 @@ describe('Swing', () => { '--motion-swing-deg': '20deg', '--motion-trans-x': '0%', '--motion-trans-y': '-50%', - '--motion-ease-in': 'cubic-bezier(0.47, 0, 0.745, 0.715)', - '--motion-ease-inout': 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', - '--motion-ease-out': 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, keyframes: [ { - easing: 'var(--motion-ease-out)', offset: 0, + easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', transform: 'rotateZ(var(--motion-rotate, 0deg)) translate(var(--motion-trans-x), var(--motion-trans-y)) rotate(0deg) translate(calc(var(--motion-trans-x) * -1), calc(var(--motion-trans-y) * -1))', }, { - easing: 'var(--motion-ease-inout)', offset: 0.25, + easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', transform: 'rotate(var(--motion-rotate, 0deg)) translate(var(--motion-trans-x), var(--motion-trans-y)) rotate(var(--motion-swing-deg)) translate(calc(var(--motion-trans-x) * -1), calc(var(--motion-trans-y) * -1))', }, { - easing: 'var(--motion-ease-in)', offset: 0.75, + easing: 'cubic-bezier(0.47, 0, 0.745, 0.715)', transform: 'rotate(var(--motion-rotate, 0deg)) translate(var(--motion-trans-x), var(--motion-trans-y)) rotate(calc(var(--motion-swing-deg) * -1)) translate(calc(var(--motion-trans-x) * -1), calc(var(--motion-trans-y) * -1))', }, @@ -68,21 +65,23 @@ describe('Swing', () => { const result = Swing.style(mockOptions); expect(result[0]).toMatchObject({ - name: 'motion-swing-067', + name: 'motion-swing-067-linear', duration: 1500, custom: { '--motion-swing-deg': '30deg', '--motion-trans-x': '0%', '--motion-trans-y': '-50%', - '--motion-ease-in': 'linear', - '--motion-ease-inout': 'linear', - '--motion-ease-out': 'linear', }, }); - // Check that delay sequence is used (more keyframes) - expect(result[0].keyframes.length).toBe(9); // 7 delay sequence + start + end - expect(result[0].keyframes[0].easing).toBe('var(--motion-ease-out)'); + expect(result[0].keyframes.length).toBe(5); + expect(result[0].keyframes.map(({ easing }: any) => easing)).toEqual([ + 'linear', + 'linear', + 'linear', + undefined, + undefined, + ]); }); test('custom duration and delay - offsets and name', () => { @@ -100,15 +99,13 @@ describe('Swing', () => { }); const keyframes = result[0].keyframes; - expect(keyframes[0].offset).toBe(0); - expect(keyframes[1].offset).toBe(0.062578); - expect(keyframes[2].offset).toBe(0.18760000000000002); - expect(keyframes[3].offset).toBe(0.31222000000000005); - expect(keyframes[4].offset).toBe(0.43751000000000007); - expect(keyframes[5].offset).toBe(0.56213); - expect(keyframes[6].offset).toBe(0.68742); - expect(keyframes[7].offset).toBe(0.7872500000000001); - expect(keyframes[8].offset).toBe(1); + expect(keyframes.map(({ offset }: any) => offset)).toEqual([ + 0, + 0.25 * 0.67, + 0.75 * 0.67, + 0.67, + 1, + ]); }); test('custom pivot - right', () => { @@ -173,26 +170,23 @@ describe('Swing', () => { '--motion-swing-deg': '20deg', '--motion-trans-x': '0%', '--motion-trans-y': '-50%', - '--motion-ease-in': 'cubic-bezier(0.47, 0, 0.745, 0.715)', - '--motion-ease-inout': 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', - '--motion-ease-out': 'cubic-bezier(0.39, 0.575, 0.565, 1)', }, keyframes: [ { - easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0, + easing: 'cubic-bezier(0.39, 0.575, 0.565, 1)', transform: 'rotateZ(var(--motion-rotate, 0deg)) translate(0%, -50%) rotate(0deg) translate(calc(0% * -1), calc(-50% * -1))', }, { - easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', offset: 0.25, + easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', transform: 'rotate(var(--motion-rotate, 0deg)) translate(0%, -50%) rotate(20deg) translate(calc(0% * -1), calc(-50% * -1))', }, { - easing: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.75, + easing: 'cubic-bezier(0.47, 0, 0.745, 0.715)', transform: 'rotate(var(--motion-rotate, 0deg)) translate(0%, -50%) rotate(calc(20deg * -1)) translate(calc(0% * -1), calc(-50% * -1))', }, @@ -221,15 +215,12 @@ describe('Swing', () => { const result = Swing.web(mockOptions); expect(result[0]).toMatchObject({ - name: 'motion-swing-067', + name: 'motion-swing-067-linear', duration: 1500, custom: { '--motion-swing-deg': '30deg', '--motion-trans-x': '0%', '--motion-trans-y': '-50%', - '--motion-ease-in': 'linear', - '--motion-ease-inout': 'linear', - '--motion-ease-out': 'linear', }, }); @@ -240,10 +231,9 @@ describe('Swing', () => { expect(firstKeyframe.transform).not.toContain('var(--motion-trans-x)'); expect(firstKeyframe.transform).not.toContain('var(--motion-trans-y)'); - // Check a delay sequence keyframe contains computed swing degree values - const delayKeyframe = result[0].keyframes[1]; // First delay sequence keyframe - expect(delayKeyframe.transform).toContain('rotate(calc(30deg * 1))'); // factor 1 for first delay keyframe - expect(delayKeyframe.transform).not.toContain('var(--motion-swing-deg)'); + const peakKeyframe = result[0].keyframes[1]; + expect(peakKeyframe.transform).toContain('rotate(30deg)'); + expect(peakKeyframe.transform).not.toContain('var(--motion-swing-deg)'); // Check that all easing values are actual values, not CSS variables result[0].keyframes.forEach((keyframe: any) => { @@ -268,15 +258,13 @@ describe('Swing', () => { }); const keyframes = result[0].keyframes; - expect(keyframes[0].offset).toBe(0); - expect(keyframes[1].offset).toBe(0.062578); - expect(keyframes[2].offset).toBe(0.18760000000000002); - expect(keyframes[3].offset).toBe(0.31222000000000005); - expect(keyframes[4].offset).toBe(0.43751000000000007); - expect(keyframes[5].offset).toBe(0.56213); - expect(keyframes[6].offset).toBe(0.68742); - expect(keyframes[7].offset).toBe(0.7872500000000001); - expect(keyframes[8].offset).toBe(1); + expect(keyframes.map(({ offset }: any) => offset)).toEqual([ + 0, + 0.25 * 0.67, + 0.75 * 0.67, + 0.67, + 1, + ]); }); test('custom pivot - right with web output', () => { @@ -294,3 +282,17 @@ describe('Swing', () => { }); }); }); + +describe('Swing easings', () => { + test.each([undefined, 'linear', 'quadInOut'])('CSS uses real easings for %s', (easing) => { + const [animation] = Swing.style({ + ...baseMockOptions, + easing, + namedEffect: { iterationDelay: 500 } as SwingType, + }); + const easings = animation.keyframes.map(({ easing }) => easing).filter(Boolean); + expect(easings).toHaveLength(3); + for (const value of easings) expect(value).not.toContain('var('); + expect(Object.keys(animation.custom!).some((key) => key.includes('ease'))).toBe(false); + }); +}); diff --git a/packages/motion-presets/src/library/ongoing/test/Wiggle.spec.ts b/packages/motion-presets/src/library/ongoing/test/Wiggle.spec.ts index d3c32daa..1dd28bf4 100644 --- a/packages/motion-presets/src/library/ongoing/test/Wiggle.spec.ts +++ b/packages/motion-presets/src/library/ongoing/test/Wiggle.spec.ts @@ -78,6 +78,10 @@ describe('Wiggle', () => { offset: 0.67, transform: 'rotate(calc(var(--motion-rotate, 0deg) + 0deg)) translateY(0px)', }, + { + offset: 1, + transform: 'rotate(calc(var(--motion-rotate, 0deg) + 0deg)) translateY(0px)', + }, ], }, ]; @@ -187,6 +191,11 @@ describe('Wiggle', () => { transform: 'rotate(var(--motion-rotate-100)) translateY(var(--motion-translate-y-100))', }, + { + offset: 1, + transform: + 'rotate(var(--motion-rotate-100)) translateY(var(--motion-translate-y-100))', + }, ], }, ]; @@ -222,3 +231,26 @@ describe('Wiggle', () => { }); }); }); + +describe('Wiggle angle and travel', () => { + test.each([ + [25, 25], + [10, 34], + [-40, 7], + ])('peaks at angle %s and lift %s', (angle, travel) => { + const { keyframes } = WiggleAnimation.web({ + ...baseMockOptions, + namedEffect: { angle, travel: { value: travel, unit: 'px' } } as Wiggle, + })[0]; + const rotations = keyframes.map(({ transform }) => + parseFloat((transform as string).match(/\+ (-?[\d.]+)deg/)![1]), + ); + const lifts = keyframes.map(({ transform }) => + parseFloat((transform as string).match(/translateY\((-?[\d.]+)px\)/)![1]), + ); + expect(rotations[0]).toBe(angle); + expect(Math.max(...rotations.map(Math.abs))).toBe(Math.abs(angle)); + expect(lifts[0]).toBe(-travel); + expect(Math.max(...lifts.map(Math.abs))).toBe(travel); + }); +}); diff --git a/packages/motion-presets/src/library/scroll/GrowScroll.ts b/packages/motion-presets/src/library/scroll/GrowScroll.ts index 51613d55..85bf6aee 100644 --- a/packages/motion-presets/src/library/scroll/GrowScroll.ts +++ b/packages/motion-presets/src/library/scroll/GrowScroll.ts @@ -5,10 +5,9 @@ import type { DomApi, EffectNineDirections, } from '../../types'; -import { toKeyframeValue, parseDirection } from '../../utils'; +import { toKeyframeValue, parseDirection, getScrollParallax } from '../../utils'; import { NINE_DIRECTIONS, SCROLL_RANGES } from '../../consts'; -const MAX_Y_TRAVEL = 40; const EPSILON = 0.01; export const schema = { @@ -60,29 +59,22 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { const scale = range === 'in' ? Math.min(inputScale, invScale) : Math.max(inputScale, invScale); const scaleFrom = scale; const scaleTo = scale; - const travelY = 1 / Math.max(speed, EPSILON) - 1; - const travel = travelY * MAX_Y_TRAVEL; + const { from, to, ...scrollRange } = getScrollParallax(range, options, speed); const fromValues = { scale: range === 'out' ? 1 : scaleFrom, - travel: range === 'out' ? 0 : -travel, }; const toValues = { scale: range === 'in' ? 1 : scaleTo, - travel: range === 'in' ? 0 : travel, }; - const offset = Math.abs(travel); - const startOffsetAdd = range === 'out' ? '0px' : `${-offset}vh`; - const endOffsetAdd = range === 'in' ? '0px' : `${offset}vh`; - const [trnsX, trnsY] = directionMap[direction] || [0, 0]; const [growScroll] = getNames(options); const custom = { - [`--motion-travel-from${suffix}`]: `${fromValues.travel}vh`, - [`--motion-travel-to${suffix}`]: `${toValues.travel}vh`, + [`--motion-travel-from${suffix}`]: from, + [`--motion-travel-to${suffix}`]: to, [`--motion-grow-from${suffix}`]: fromValues.scale, [`--motion-grow-to${suffix}`]: toValues.scale, [`--motion-trans-x${suffix}`]: `${trnsX}%`, @@ -95,8 +87,7 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { name: growScroll, fill, easing, - startOffsetAdd, - endOffsetAdd, + ...scrollRange, custom, keyframes: [ { diff --git a/packages/motion-presets/src/library/scroll/ParallaxScroll.ts b/packages/motion-presets/src/library/scroll/ParallaxScroll.ts index b4b35fe3..abdfe3d2 100644 --- a/packages/motion-presets/src/library/scroll/ParallaxScroll.ts +++ b/packages/motion-presets/src/library/scroll/ParallaxScroll.ts @@ -1,8 +1,5 @@ import type { ScrubAnimationOptions, ParallaxScroll, AnimationFillMode, DomApi } from '../../types'; -import { toKeyframeValue } from '../../utils'; - -const PARALLAX_FACTOR_AT_DEFAULT_SPEED = 0.5; -const EPSILON = 0.01; +import { getScrollParallax, toKeyframeValue } from '../../utils'; export const schema = { center: { type: 'number', min: 0, max: 1, default: 0.5 }, @@ -20,22 +17,16 @@ export function web(options: ScrubAnimationOptions, _dom?: DomApi) { export function style(options: ScrubAnimationOptions, asWeb = false) { const { suffix = '' } = options; const namedEffect = options.namedEffect as ParallaxScroll; - const { speed = schema.speed.default, center: inputCenter = schema.center.default } = namedEffect; + const { speed = schema.speed.default, center = schema.center.default } = namedEffect; const easing = 'linear'; - const parallaxFactor = - (PARALLAX_FACTOR_AT_DEFAULT_SPEED * (1 / Math.max(speed, EPSILON) - 1)) / - (1 / schema.speed.default - 1); - const center = Math.min(1 - EPSILON, Math.max(0, inputCenter)); - - const start = `${-100 * parallaxFactor * center}vh`; - const end = `${100 * parallaxFactor * (1 - center)}vh`; + const { from, to, ...scrollRange } = getScrollParallax('continuous', options, speed, center); const [parallaxScroll] = getNames(options); const custom = { - [`--motion-parallax-from${suffix}`]: start, - [`--motion-parallax-to${suffix}`]: end, + [`--motion-parallax-from${suffix}`]: from, + [`--motion-parallax-to${suffix}`]: to, }; // use transform: translateY() and not translate: 0 because of WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=276281 @@ -45,8 +36,7 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { name: parallaxScroll, fill: 'both' as AnimationFillMode, easing, - startOffsetAdd: start, - endOffsetAdd: end, + ...scrollRange, custom, keyframes: [ { diff --git a/packages/motion-presets/src/library/scroll/ShrinkScroll.ts b/packages/motion-presets/src/library/scroll/ShrinkScroll.ts index f6a5c201..050c43f1 100644 --- a/packages/motion-presets/src/library/scroll/ShrinkScroll.ts +++ b/packages/motion-presets/src/library/scroll/ShrinkScroll.ts @@ -5,10 +5,9 @@ import type { DomApi, EffectNineDirections, } from '../../types'; -import { toKeyframeValue, parseDirection } from '../../utils'; +import { toKeyframeValue, parseDirection, getScrollParallax } from '../../utils'; import { NINE_DIRECTIONS, SCROLL_RANGES } from '../../consts'; -const MAX_Y_TRAVEL = 40; const EPSILON = 0.01; export const schema = { @@ -60,29 +59,22 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { const scale = range === 'in' ? Math.max(inputScale, invScale) : Math.min(inputScale, invScale); const scaleFrom = scale; const scaleTo = scale; - const travelY = 1 / Math.max(speed, EPSILON) - 1; - const travel = travelY * MAX_Y_TRAVEL; + const { from, to, ...scrollRange } = getScrollParallax(range, options, speed); const fromValues = { scale: range === 'out' ? 1 : scaleFrom, - travel: range === 'out' ? 0 : -travel, }; const toValues = { scale: range === 'in' ? 1 : scaleTo, - travel: range === 'in' ? 0 : travel, }; - const offset = Math.abs(travel); - const startOffsetAdd = range === 'out' ? '0px' : `${-offset}vh`; - const endOffsetAdd = range === 'in' ? '0px' : `${offset}vh`; - const [trnsX, trnsY] = directionMap[direction] || [0, 0]; const [shrinkScroll] = getNames(options); const custom = { - [`--motion-travel-from${suffix}`]: `${fromValues.travel}vh`, - [`--motion-travel-to${suffix}`]: `${toValues.travel}vh`, + [`--motion-travel-from${suffix}`]: from, + [`--motion-travel-to${suffix}`]: to, [`--motion-shrink-from${suffix}`]: fromValues.scale, [`--motion-shrink-to${suffix}`]: toValues.scale, [`--motion-trans-x${suffix}`]: `${trnsX}%`, @@ -96,8 +88,7 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { fill, easing, custom, - startOffsetAdd, - endOffsetAdd, + ...scrollRange, keyframes: [ { transform: `translateY(${toKeyframeValue( diff --git a/packages/motion-presets/src/library/scroll/Spin3dScroll.ts b/packages/motion-presets/src/library/scroll/Spin3dScroll.ts index 9465ae63..deb21ea5 100644 --- a/packages/motion-presets/src/library/scroll/Spin3dScroll.ts +++ b/packages/motion-presets/src/library/scroll/Spin3dScroll.ts @@ -1,10 +1,7 @@ import type { AnimationFillMode, ScrubAnimationOptions, Spin3dScroll, DomApi } from '../../types'; -import { toKeyframeValue } from '../../utils'; +import { toKeyframeValue, getScrollParallax } from '../../utils'; import { SCROLL_RANGES } from '../../consts'; -const MAX_Y_TRAVEL = 40; -const EPSILON = 0.01; - export const schema = { angle: { type: 'number', default: 100 }, perspective: { type: 'number', min: 0, default: 1000 }, @@ -34,31 +31,25 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { range === 'out' ? 'forwards' : range === 'in' ? 'backwards' : options.fill ) as AnimationFillMode; - const travel = (1 / Math.max(speed, EPSILON) - 1) * MAX_Y_TRAVEL; + const { from, to, ...scrollRange } = getScrollParallax(range, options, speed); const fromValues = { rotationX: range === 'out' ? 0 : -2 * rotate, rotationY: range === 'out' ? 0 : -rotate, rotationZ: range === 'out' ? 0 : -rotate, - travel: range === 'out' ? 0 : -travel, }; const toValues = { rotationX: rotate * (range === 'in' ? 0 : range === 'out' ? 3 : 1.8), rotationY: rotate * (range === 'in' ? 0 : range === 'out' ? 2 : 1), rotationZ: rotate * (range === 'in' ? 0 : range === 'out' ? 1 : 2), - travel: range === 'in' ? 0 : travel, }; - const offset = Math.abs(travel); - const startOffsetAdd = range === 'out' ? '0px' : `${-offset}vh`; - const endOffsetAdd = range === 'in' ? '0px' : `${offset}vh`; - const [spin3dScroll] = getNames(options); const custom = { [`--motion-perspective${suffix}`]: `${perspective}px`, - [`--motion-travel-from${suffix}`]: `${fromValues.travel}vh`, - [`--motion-travel-to${suffix}`]: `${toValues.travel}vh`, + [`--motion-travel-from${suffix}`]: from, + [`--motion-travel-to${suffix}`]: to, [`--motion-rot-x-from${suffix}`]: `${fromValues.rotationX}deg`, [`--motion-rot-x-to${suffix}`]: `${toValues.rotationX}deg`, [`--motion-rot-y-from${suffix}`]: `${fromValues.rotationY}deg`, @@ -74,8 +65,7 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { fill, easing, custom, - startOffsetAdd, - endOffsetAdd, + ...scrollRange, keyframes: [ { transform: `perspective(${toKeyframeValue(custom, `--motion-perspective${suffix}`, asWeb)}) translateY(${toKeyframeValue( diff --git a/packages/motion-presets/src/library/scroll/TiltScroll.ts b/packages/motion-presets/src/library/scroll/TiltScroll.ts index bbbc5bbc..717a3e91 100644 --- a/packages/motion-presets/src/library/scroll/TiltScroll.ts +++ b/packages/motion-presets/src/library/scroll/TiltScroll.ts @@ -6,11 +6,9 @@ import type { EffectSpinDirection, } from '../../types'; import { cssEasings as easings } from '@wix/motion'; -import { toKeyframeValue, parseDirection } from '../../utils'; +import { toKeyframeValue, parseDirection, getScrollParallax } from '../../utils'; import { SCROLL_RANGES, SPIN_DIRECTIONS } from '../../consts'; -const MAX_Y_TRAVEL = 40; -const EPSILON = 0.01; const [ROTATION_X, ROTATION_Y, ROTATION_Z] = [10, 25, 25]; export const schema = { @@ -48,31 +46,30 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { range === 'out' ? 'forwards' : range === 'in' ? 'backwards' : options.fill ) as AnimationFillMode; - const travelY = MAX_Y_TRAVEL * (1 - 1 / Math.max(speed, EPSILON)); + const { + from: travelFrom, + to: travelTo, + ...scrollRange + } = getScrollParallax(range, options, speed); const dir = DIRECTIONS_MAP[direction] * (range === 'in' ? 1 : -1); const from = { x: ROTATION_X * (range === 'out' ? 0 : -1), y: ROTATION_Y * (range === 'out' ? 0 : -1), z: ROTATION_Z * dir * (range === 'out' ? 0 : range === 'in' ? 1 : -1), - transY: range === 'out' ? 0 : travelY, }; const to = { x: ROTATION_X * (range === 'in' ? 0 : range === 'out' ? -1 : 1), y: ROTATION_Y * (range === 'in' ? 0 : range === 'out' ? -1 : 0.5), z: ROTATION_Z * dir * (range === 'in' ? 0 : range === 'out' ? 1 : 1.25), - transY: range === 'in' ? 0 : -1 * travelY, }; - const startOffsetAdd = range === 'out' ? '0px' : `${-1 * Math.abs(travelY)}vh`; - const endOffsetAdd = range === 'in' ? '0px' : `${Math.abs(travelY)}vh`; - const [tiltScrollTranslate, tiltScrollRotate] = getNames(options); const custom = { [`--motion-perspective${suffix}`]: `${perspective}px`, - [`--motion-tilt-y-from${suffix}`]: `${from.transY}vh`, - [`--motion-tilt-y-to${suffix}`]: `${to.transY}vh`, + [`--motion-tilt-y-from${suffix}`]: travelFrom, + [`--motion-tilt-y-to${suffix}`]: travelTo, [`--motion-tilt-x-from${suffix}`]: `${from.x}deg`, [`--motion-tilt-x-to${suffix}`]: `${to.x}deg`, [`--motion-tilt-y-rot-from${suffix}`]: `${from.y}deg`, @@ -87,8 +84,7 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { name: tiltScrollTranslate, fill, easing, - startOffsetAdd, - endOffsetAdd, + ...scrollRange, custom, keyframes: [ { @@ -120,8 +116,7 @@ export function style(options: ScrubAnimationOptions, asWeb = false) { name: tiltScrollRotate, fill, easing: easings.sineInOut, - startOffsetAdd, - endOffsetAdd, + ...scrollRange, composite: 'add' as const, // add this animation on top of the previous one custom, keyframes: [ diff --git a/packages/motion-presets/src/library/scroll/test/GrowScroll.spec.ts b/packages/motion-presets/src/library/scroll/test/GrowScroll.spec.ts index 0327dac0..bf9c2635 100644 --- a/packages/motion-presets/src/library/scroll/test/GrowScroll.spec.ts +++ b/packages/motion-presets/src/library/scroll/test/GrowScroll.spec.ts @@ -15,16 +15,16 @@ describe('GrowScroll', () => { const expectedResult = [ { fill: 'backwards', - startOffsetAdd: '0vh', + startOffsetAdd: '0px', endOffsetAdd: '0px', keyframes: [ { transform: - 'translateY(0vh) translate(0%, 0%) scale(0.25) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(0%, 0%) scale(0.25) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', }, { transform: - 'translateY(0vh) translate(0%, 0%) scale(1) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(0%, 0%) scale(1) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', }, ], }, @@ -45,15 +45,15 @@ describe('GrowScroll', () => { { fill: 'forwards', startOffsetAdd: '0px', - endOffsetAdd: '0vh', + endOffsetAdd: '0px', keyframes: [ { transform: - 'translateY(0vh) translate(0%, 0%) scale(1) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(0%, 0%) scale(1) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', }, { transform: - 'translateY(0vh) translate(0%, 0%) scale(4) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(0%, 0%) scale(4) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', }, ], }, @@ -75,11 +75,11 @@ describe('GrowScroll', () => { keyframes: [ { transform: - 'translateY(0vh) translate(50%, -50%) scale(0) translate(calc(-1 * 50%), calc(-1 * -50%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(50%, -50%) scale(0) translate(calc(-1 * 50%), calc(-1 * -50%)) rotate(var(--motion-rotate, 0))', }, { transform: - 'translateY(0vh) translate(50%, -50%) scale(1) translate(calc(-1 * 50%), calc(-1 * -50%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(50%, -50%) scale(1) translate(calc(-1 * 50%), calc(-1 * -50%)) rotate(var(--motion-rotate, 0))', }, ], }, @@ -101,11 +101,11 @@ describe('GrowScroll', () => { keyframes: [ { transform: - 'translateY(0vh) translate(-50%, 50%) scale(0) translate(calc(-1 * -50%), calc(-1 * 50%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(-50%, 50%) scale(0) translate(calc(-1 * -50%), calc(-1 * 50%)) rotate(var(--motion-rotate, 0))', }, { transform: - 'translateY(0vh) translate(-50%, 50%) scale(1) translate(calc(-1 * -50%), calc(-1 * 50%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(-50%, 50%) scale(1) translate(calc(-1 * -50%), calc(-1 * 50%)) rotate(var(--motion-rotate, 0))', }, ], }, @@ -127,11 +127,11 @@ describe('GrowScroll', () => { keyframes: [ { transform: - 'translateY(0vh) translate(50%, 50%) scale(0) translate(calc(-1 * 50%), calc(-1 * 50%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(50%, 50%) scale(0) translate(calc(-1 * 50%), calc(-1 * 50%)) rotate(var(--motion-rotate, 0))', }, { transform: - 'translateY(0vh) translate(50%, 50%) scale(1) translate(calc(-1 * 50%), calc(-1 * 50%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(50%, 50%) scale(1) translate(calc(-1 * 50%), calc(-1 * 50%)) rotate(var(--motion-rotate, 0))', }, ], }, @@ -153,11 +153,11 @@ describe('GrowScroll', () => { keyframes: [ { transform: - 'translateY(0vh) translate(-50%, -50%) scale(0) translate(calc(-1 * -50%), calc(-1 * -50%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(-50%, -50%) scale(0) translate(calc(-1 * -50%), calc(-1 * -50%)) rotate(var(--motion-rotate, 0))', }, { transform: - 'translateY(0vh) translate(-50%, -50%) scale(1) translate(calc(-1 * -50%), calc(-1 * -50%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(-50%, -50%) scale(1) translate(calc(-1 * -50%), calc(-1 * -50%)) rotate(var(--motion-rotate, 0))', }, ], }, @@ -179,11 +179,11 @@ describe('GrowScroll', () => { keyframes: [ { transform: - 'translateY(0vh) translate(0%, 50%) scale(0) translate(calc(-1 * 0%), calc(-1 * 50%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(0%, 50%) scale(0) translate(calc(-1 * 0%), calc(-1 * 50%)) rotate(var(--motion-rotate, 0))', }, { transform: - 'translateY(0vh) translate(0%, 50%) scale(1) translate(calc(-1 * 0%), calc(-1 * 50%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(0%, 50%) scale(1) translate(calc(-1 * 0%), calc(-1 * 50%)) rotate(var(--motion-rotate, 0))', }, ], }, @@ -205,11 +205,11 @@ describe('GrowScroll', () => { keyframes: [ { transform: - 'translateY(0vh) translate(0%, -50%) scale(0) translate(calc(-1 * 0%), calc(-1 * -50%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(0%, -50%) scale(0) translate(calc(-1 * 0%), calc(-1 * -50%)) rotate(var(--motion-rotate, 0))', }, { transform: - 'translateY(0vh) translate(0%, -50%) scale(1) translate(calc(-1 * 0%), calc(-1 * -50%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(0%, -50%) scale(1) translate(calc(-1 * 0%), calc(-1 * -50%)) rotate(var(--motion-rotate, 0))', }, ], }, @@ -231,11 +231,11 @@ describe('GrowScroll', () => { keyframes: [ { transform: - 'translateY(0vh) translate(-50%, 0%) scale(0) translate(calc(-1 * -50%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(-50%, 0%) scale(0) translate(calc(-1 * -50%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', }, { transform: - 'translateY(0vh) translate(-50%, 0%) scale(1) translate(calc(-1 * -50%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(-50%, 0%) scale(1) translate(calc(-1 * -50%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', }, ], }, @@ -257,11 +257,11 @@ describe('GrowScroll', () => { keyframes: [ { transform: - 'translateY(0vh) translate(50%, 0%) scale(0) translate(calc(-1 * 50%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(50%, 0%) scale(0) translate(calc(-1 * 50%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', }, { transform: - 'translateY(0vh) translate(50%, 0%) scale(1) translate(calc(-1 * 50%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(50%, 0%) scale(1) translate(calc(-1 * 50%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', }, ], }, @@ -276,20 +276,56 @@ describe('GrowScroll', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, namedEffect: { speed: 0.5, scale: 0 } as GrowScrollType, + startOffset: { name: 'cover', offset: { value: 0, unit: 'percentage' } }, + endOffset: { name: 'cover', offset: { value: 50, unit: 'percentage' } }, }; const expectedResult = [ { - startOffsetAdd: '-40vh', - endOffsetAdd: '0px', + startOffset: { name: 'entry-crossing', offset: { value: -50, unit: 'percentage' } }, + startOffsetAdd: '-50vh', + endOffset: { name: 'entry-crossing', offset: { value: 50, unit: 'percentage' } }, + endOffsetAdd: '50vh', keyframes: [ { transform: - 'translateY(-40vh) translate(0%, 0%) scale(0) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', + 'translateY(calc(-50% + -50vh)) translate(0%, 0%) scale(0) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', }, { transform: - 'translateY(0vh) translate(0%, 0%) scale(1) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(0%, 0%) scale(1) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', + }, + ], + }, + ]; + + const result = GrowScroll.web(mockOptions); + + expect(result).toMatchObject(expectedResult); + }); + + test('custom speed - out', () => { + const mockOptions: ScrubAnimationOptions = { + ...baseMockOptions, + namedEffect: { speed: 0.5, scale: 2, range: 'out' } as GrowScrollType, + startOffset: { name: 'cover', offset: { value: 50, unit: 'percentage' } }, + endOffset: { name: 'cover', offset: { value: 100, unit: 'percentage' } }, + }; + + const expectedResult = [ + { + startOffset: { name: 'entry-crossing', offset: { value: 50, unit: 'percentage' } }, + startOffsetAdd: '50vh', + endOffset: { name: 'entry-crossing', offset: { value: 150, unit: 'percentage' } }, + endOffsetAdd: '150vh', + keyframes: [ + { + transform: + 'translateY(0px) translate(0%, 0%) scale(1) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', + }, + { + transform: + 'translateY(calc(50% + 50vh)) translate(0%, 0%) scale(2) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', }, ], }, @@ -311,7 +347,7 @@ describe('GrowScroll', () => { const expectedResult = [ { fill: 'backwards', - startOffsetAdd: '0vh', + startOffsetAdd: '0px', endOffsetAdd: '0px', keyframes: [ { @@ -341,7 +377,7 @@ describe('GrowScroll', () => { { fill: 'forwards', startOffsetAdd: '0px', - endOffsetAdd: '0vh', + endOffsetAdd: '0px', keyframes: [ { transform: diff --git a/packages/motion-presets/src/library/scroll/test/ParallaxScroll.spec.ts b/packages/motion-presets/src/library/scroll/test/ParallaxScroll.spec.ts index 8bb497f7..49a28930 100644 --- a/packages/motion-presets/src/library/scroll/test/ParallaxScroll.spec.ts +++ b/packages/motion-presets/src/library/scroll/test/ParallaxScroll.spec.ts @@ -4,6 +4,8 @@ import * as ParallaxScroll from '../ParallaxScroll'; import type { ParallaxScroll as ParallaxScrollType, ScrubAnimationOptions } from '../../../types'; import { baseMockOptions } from './testUtils'; +const DEFAULT_SHIFT = 0.5 * (1 - 1 / 0.7159) * 100; + describe('ParallaxScroll', () => { describe('web', () => { test('default values', () => { @@ -16,14 +18,22 @@ describe('ParallaxScroll', () => { { fill: 'both', easing: 'linear', - startOffsetAdd: '-25vh', - endOffsetAdd: '25vh', + startOffset: { + name: 'entry-crossing', + offset: { value: DEFAULT_SHIFT, unit: 'percentage' }, + }, + startOffsetAdd: `${DEFAULT_SHIFT}vh`, + endOffset: { + name: 'entry-crossing', + offset: { value: 100 - DEFAULT_SHIFT, unit: 'percentage' }, + }, + endOffsetAdd: `${100 - DEFAULT_SHIFT}vh`, keyframes: [ { - transform: 'translateY(-25vh) rotate(var(--motion-rotate, 0))', + transform: `translateY(calc(${DEFAULT_SHIFT}% + ${DEFAULT_SHIFT}vh)) rotate(var(--motion-rotate, 0))`, }, { - transform: 'translateY(25vh) rotate(var(--motion-rotate, 0))', + transform: `translateY(calc(${-DEFAULT_SHIFT}% + ${-DEFAULT_SHIFT}vh)) rotate(var(--motion-rotate, 0))`, }, ], }, @@ -42,14 +52,16 @@ describe('ParallaxScroll', () => { const expectedResult = [ { - startOffsetAdd: '-62.9971840901091vh', - endOffsetAdd: '62.9971840901091vh', + startOffset: { name: 'entry-crossing', offset: { value: -50, unit: 'percentage' } }, + startOffsetAdd: '-50vh', + endOffset: { name: 'entry-crossing', offset: { value: 150, unit: 'percentage' } }, + endOffsetAdd: '150vh', keyframes: [ { - transform: 'translateY(-62.9971840901091vh) rotate(var(--motion-rotate, 0))', + transform: 'translateY(calc(-50% + -50vh)) rotate(var(--motion-rotate, 0))', }, { - transform: 'translateY(62.9971840901091vh) rotate(var(--motion-rotate, 0))', + transform: 'translateY(calc(50% + 50vh)) rotate(var(--motion-rotate, 0))', }, ], }, @@ -72,8 +84,12 @@ describe('ParallaxScroll', () => { { fill: 'both', easing: 'linear', - startOffsetAdd: '-25vh', - endOffsetAdd: '25vh', + startOffsetAdd: `${DEFAULT_SHIFT}vh`, + endOffsetAdd: `${100 - DEFAULT_SHIFT}vh`, + custom: { + '--motion-parallax-from': `calc(${DEFAULT_SHIFT}% + ${DEFAULT_SHIFT}vh)`, + '--motion-parallax-to': `calc(${-DEFAULT_SHIFT}% + ${-DEFAULT_SHIFT}vh)`, + }, keyframes: [ { transform: 'translateY(var(--motion-parallax-from)) rotate(var(--motion-rotate, 0))', @@ -93,13 +109,19 @@ describe('ParallaxScroll', () => { test('custom center', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { center: 0.25 } as ParallaxScrollType, + namedEffect: { center: 0.25, speed: 0.5 } as ParallaxScrollType, }; const expectedResult = [ { - startOffsetAdd: '-12.5vh', - endOffsetAdd: '37.5vh', + startOffset: { name: 'entry-crossing', offset: { value: -25, unit: 'percentage' } }, + startOffsetAdd: '-25vh', + endOffset: { name: 'entry-crossing', offset: { value: 175, unit: 'percentage' } }, + endOffsetAdd: '175vh', + custom: { + '--motion-parallax-from': 'calc(-25% + -25vh)', + '--motion-parallax-to': 'calc(75% + 75vh)', + }, keyframes: [ { transform: 'translateY(var(--motion-parallax-from)) rotate(var(--motion-rotate, 0))', diff --git a/packages/motion-presets/src/library/scroll/test/ShrinkScroll.spec.ts b/packages/motion-presets/src/library/scroll/test/ShrinkScroll.spec.ts index 594f4ffd..49a9e284 100644 --- a/packages/motion-presets/src/library/scroll/test/ShrinkScroll.spec.ts +++ b/packages/motion-presets/src/library/scroll/test/ShrinkScroll.spec.ts @@ -16,16 +16,16 @@ describe('ShrinkScroll', () => { { fill: 'backwards', easing: 'linear', - startOffsetAdd: '0vh', + startOffsetAdd: '0px', endOffsetAdd: '0px', keyframes: [ { transform: - 'translateY(0vh) translate(0%, 0%) scale(1.2) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(0%, 0%) scale(1.2) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', }, { transform: - 'translateY(0vh) translate(0%, 0%) scale(1) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(0%, 0%) scale(1) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', }, ], }, @@ -46,15 +46,15 @@ describe('ShrinkScroll', () => { { fill: 'forwards', startOffsetAdd: '0px', - endOffsetAdd: '0vh', + endOffsetAdd: '0px', keyframes: [ { transform: - 'translateY(0vh) translate(0%, 0%) scale(1) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(0%, 0%) scale(1) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', }, { transform: - 'translateY(0vh) translate(0%, 0%) scale(0.8) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(0%, 0%) scale(0.8) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', }, ], }, @@ -76,11 +76,11 @@ describe('ShrinkScroll', () => { keyframes: [ { transform: - 'translateY(0vh) translate(50%, -50%) scale(1.2) translate(calc(-1 * 50%), calc(-1 * -50%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(50%, -50%) scale(1.2) translate(calc(-1 * 50%), calc(-1 * -50%)) rotate(var(--motion-rotate, 0))', }, { transform: - 'translateY(0vh) translate(50%, -50%) scale(1) translate(calc(-1 * 50%), calc(-1 * -50%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(50%, -50%) scale(1) translate(calc(-1 * 50%), calc(-1 * -50%)) rotate(var(--motion-rotate, 0))', }, ], }, @@ -102,11 +102,11 @@ describe('ShrinkScroll', () => { keyframes: [ { transform: - 'translateY(0vh) translate(0%, 0%) scale(1.5) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(0%, 0%) scale(1.5) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', }, { transform: - 'translateY(0vh) translate(0%, 0%) scale(1) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(0%, 0%) scale(1) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', }, ], }, @@ -120,21 +120,25 @@ describe('ShrinkScroll', () => { test('custom speed', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { speed: 2 / 3 } as ShrinkScrollType, + namedEffect: { speed: 0.25 } as ShrinkScrollType, + startOffset: { name: 'cover', offset: { value: 0, unit: 'percentage' } }, + endOffset: { name: 'cover', offset: { value: 50, unit: 'percentage' } }, }; const expectedResult = [ { - startOffsetAdd: '-20vh', - endOffsetAdd: '0px', + startOffset: { name: 'entry-crossing', offset: { value: -150, unit: 'percentage' } }, + startOffsetAdd: '-150vh', + endOffset: { name: 'entry-crossing', offset: { value: 50, unit: 'percentage' } }, + endOffsetAdd: '50vh', keyframes: [ { transform: - 'translateY(-20vh) translate(0%, 0%) scale(1.2) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', + 'translateY(calc(-150% + -150vh)) translate(0%, 0%) scale(1.2) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', }, { transform: - 'translateY(0vh) translate(0%, 0%) scale(1) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', + 'translateY(0px) translate(0%, 0%) scale(1) translate(calc(-1 * 0%), calc(-1 * 0%)) rotate(var(--motion-rotate, 0))', }, ], }, @@ -157,7 +161,7 @@ describe('ShrinkScroll', () => { { fill: 'backwards', easing: 'linear', - startOffsetAdd: '0vh', + startOffsetAdd: '0px', endOffsetAdd: '0px', keyframes: [ { @@ -187,7 +191,7 @@ describe('ShrinkScroll', () => { { fill: 'forwards', startOffsetAdd: '0px', - endOffsetAdd: '0vh', + endOffsetAdd: '0px', keyframes: [ { transform: diff --git a/packages/motion-presets/src/library/scroll/test/Spin3dScroll.spec.ts b/packages/motion-presets/src/library/scroll/test/Spin3dScroll.spec.ts index 36078495..c69725dc 100644 --- a/packages/motion-presets/src/library/scroll/test/Spin3dScroll.spec.ts +++ b/packages/motion-presets/src/library/scroll/test/Spin3dScroll.spec.ts @@ -16,16 +16,16 @@ describe('Spin3dScroll', () => { { fill: 'backwards', easing: 'linear', - startOffsetAdd: '0vh', + startOffsetAdd: '0px', endOffsetAdd: '0px', keyframes: [ { transform: - 'perspective(1000px) translateY(0vh) rotateZ(calc(var(--motion-rotate, 0deg) + 100deg)) rotateY(100deg) rotateX(200deg)', + 'perspective(1000px) translateY(0px) rotateZ(calc(var(--motion-rotate, 0deg) + 100deg)) rotateY(100deg) rotateX(200deg)', }, { transform: - 'perspective(1000px) translateY(0vh) rotateZ(calc(var(--motion-rotate, 0deg) + 0deg)) rotateY(0deg) rotateX(0deg)', + 'perspective(1000px) translateY(0px) rotateZ(calc(var(--motion-rotate, 0deg) + 0deg)) rotateY(0deg) rotateX(0deg)', }, ], }, @@ -47,11 +47,11 @@ describe('Spin3dScroll', () => { keyframes: [ { transform: - 'perspective(1000px) translateY(0vh) rotateZ(calc(var(--motion-rotate, 0deg) + 50deg)) rotateY(50deg) rotateX(100deg)', + 'perspective(1000px) translateY(0px) rotateZ(calc(var(--motion-rotate, 0deg) + 50deg)) rotateY(50deg) rotateX(100deg)', }, { transform: - 'perspective(1000px) translateY(0vh) rotateZ(calc(var(--motion-rotate, 0deg) + 0deg)) rotateY(0deg) rotateX(0deg)', + 'perspective(1000px) translateY(0px) rotateZ(calc(var(--motion-rotate, 0deg) + 0deg)) rotateY(0deg) rotateX(0deg)', }, ], }, @@ -72,15 +72,15 @@ describe('Spin3dScroll', () => { { fill: 'forwards', startOffsetAdd: '0px', - endOffsetAdd: '0vh', + endOffsetAdd: '0px', keyframes: [ { transform: - 'perspective(1000px) translateY(0vh) rotateZ(calc(var(--motion-rotate, 0deg) + 0deg)) rotateY(0deg) rotateX(0deg)', + 'perspective(1000px) translateY(0px) rotateZ(calc(var(--motion-rotate, 0deg) + 0deg)) rotateY(0deg) rotateX(0deg)', }, { transform: - 'perspective(1000px) translateY(0vh) rotateZ(calc(var(--motion-rotate, 0deg) + -100deg)) rotateY(-200deg) rotateX(-300deg)', + 'perspective(1000px) translateY(0px) rotateZ(calc(var(--motion-rotate, 0deg) + -100deg)) rotateY(-200deg) rotateX(-300deg)', }, ], }, @@ -101,16 +101,16 @@ describe('Spin3dScroll', () => { const expectedResult = [ { fill: 'both', - startOffsetAdd: '0vh', - endOffsetAdd: '0vh', + startOffsetAdd: '0px', + endOffsetAdd: '0px', keyframes: [ { transform: - 'perspective(1000px) translateY(0vh) rotateZ(calc(var(--motion-rotate, 0deg) + 100deg)) rotateY(100deg) rotateX(200deg)', + 'perspective(1000px) translateY(0px) rotateZ(calc(var(--motion-rotate, 0deg) + 100deg)) rotateY(100deg) rotateX(200deg)', }, { transform: - 'perspective(1000px) translateY(0vh) rotateZ(calc(var(--motion-rotate, 0deg) + -200deg)) rotateY(-100deg) rotateX(-180deg)', + 'perspective(1000px) translateY(0px) rotateZ(calc(var(--motion-rotate, 0deg) + -200deg)) rotateY(-100deg) rotateX(-180deg)', }, ], }, @@ -124,21 +124,25 @@ describe('Spin3dScroll', () => { test('custom speed', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { speed: 2 / 3 } as Spin3dScrollType, + namedEffect: { speed: 0.25 } as Spin3dScrollType, + startOffset: { name: 'cover', offset: { value: 0, unit: 'percentage' } }, + endOffset: { name: 'cover', offset: { value: 50, unit: 'percentage' } }, }; const expectedResult = [ { - startOffsetAdd: '-20vh', - endOffsetAdd: '0px', + startOffset: { name: 'entry-crossing', offset: { value: -150, unit: 'percentage' } }, + startOffsetAdd: '-150vh', + endOffset: { name: 'entry-crossing', offset: { value: 50, unit: 'percentage' } }, + endOffsetAdd: '50vh', keyframes: [ { transform: - 'perspective(1000px) translateY(-20vh) rotateZ(calc(var(--motion-rotate, 0deg) + 100deg)) rotateY(100deg) rotateX(200deg)', + 'perspective(1000px) translateY(calc(-150% + -150vh)) rotateZ(calc(var(--motion-rotate, 0deg) + 100deg)) rotateY(100deg) rotateX(200deg)', }, { transform: - 'perspective(1000px) translateY(0vh) rotateZ(calc(var(--motion-rotate, 0deg) + 0deg)) rotateY(0deg) rotateX(0deg)', + 'perspective(1000px) translateY(0px) rotateZ(calc(var(--motion-rotate, 0deg) + 0deg)) rotateY(0deg) rotateX(0deg)', }, ], }, @@ -161,7 +165,7 @@ describe('Spin3dScroll', () => { { fill: 'backwards', easing: 'linear', - startOffsetAdd: '0vh', + startOffsetAdd: '0px', endOffsetAdd: '0px', custom: { '--motion-perspective': '1000px', @@ -223,7 +227,7 @@ describe('Spin3dScroll', () => { { fill: 'forwards', startOffsetAdd: '0px', - endOffsetAdd: '0vh', + endOffsetAdd: '0px', custom: { '--motion-perspective': '1000px', }, @@ -255,8 +259,8 @@ describe('Spin3dScroll', () => { const expectedResult = [ { fill: 'both', - startOffsetAdd: '0vh', - endOffsetAdd: '0vh', + startOffsetAdd: '0px', + endOffsetAdd: '0px', custom: { '--motion-perspective': '1000px', }, @@ -281,15 +285,21 @@ describe('Spin3dScroll', () => { test('custom speed', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, - namedEffect: { speed: 2 / 3 } as Spin3dScrollType, + namedEffect: { speed: 0.25 } as Spin3dScrollType, + startOffset: { name: 'cover', offset: { value: 0, unit: 'percentage' } }, + endOffset: { name: 'cover', offset: { value: 50, unit: 'percentage' } }, }; const expectedResult = [ { - startOffsetAdd: '-20vh', - endOffsetAdd: '0px', + startOffset: { name: 'entry-crossing', offset: { value: -150, unit: 'percentage' } }, + startOffsetAdd: '-150vh', + endOffset: { name: 'entry-crossing', offset: { value: 50, unit: 'percentage' } }, + endOffsetAdd: '50vh', custom: { '--motion-perspective': '1000px', + '--motion-travel-from': 'calc(-150% + -150vh)', + '--motion-travel-to': '0px', }, keyframes: [ { diff --git a/packages/motion-presets/src/library/scroll/test/TiltScroll.spec.ts b/packages/motion-presets/src/library/scroll/test/TiltScroll.spec.ts index e68b7d48..d29d41ff 100644 --- a/packages/motion-presets/src/library/scroll/test/TiltScroll.spec.ts +++ b/packages/motion-presets/src/library/scroll/test/TiltScroll.spec.ts @@ -15,21 +15,21 @@ describe('TiltScroll', () => { const expectedResult = [ { fill: 'backwards', - startOffsetAdd: '0vh', + startOffsetAdd: '0px', endOffsetAdd: '0px', keyframes: [ { - transform: 'perspective(400px) translateY(0vh) rotateX(-10deg) rotateY(-25deg)', + transform: 'perspective(400px) translateY(0px) rotateX(-10deg) rotateY(-25deg)', }, { - transform: 'perspective(400px) translateY(0vh) rotateX(0deg) rotateY(0deg)', + transform: 'perspective(400px) translateY(0px) rotateX(0deg) rotateY(0deg)', }, ], }, { fill: 'backwards', easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', - startOffsetAdd: '0vh', + startOffsetAdd: '0px', endOffsetAdd: '0px', composite: 'add', keyframes: [ @@ -58,20 +58,20 @@ describe('TiltScroll', () => { { fill: 'forwards', startOffsetAdd: '0px', - endOffsetAdd: '0vh', + endOffsetAdd: '0px', keyframes: [ { - transform: 'perspective(400px) translateY(0vh) rotateX(0deg) rotateY(0deg)', + transform: 'perspective(400px) translateY(0px) rotateX(0deg) rotateY(0deg)', }, { - transform: 'perspective(400px) translateY(0vh) rotateX(-10deg) rotateY(-25deg)', + transform: 'perspective(400px) translateY(0px) rotateX(-10deg) rotateY(-25deg)', }, ], }, { fill: 'forwards', startOffsetAdd: '0px', - endOffsetAdd: '0vh', + endOffsetAdd: '0px', keyframes: [ { transform: 'rotate(calc(var(--motion-rotate, 0deg) + 0deg))', @@ -98,21 +98,21 @@ describe('TiltScroll', () => { const expectedResult = [ { fill: 'both', - startOffsetAdd: '0vh', - endOffsetAdd: '0vh', + startOffsetAdd: '0px', + endOffsetAdd: '0px', keyframes: [ { - transform: 'perspective(400px) translateY(0vh) rotateX(-10deg) rotateY(-25deg)', + transform: 'perspective(400px) translateY(0px) rotateX(-10deg) rotateY(-25deg)', }, { - transform: 'perspective(400px) translateY(0vh) rotateX(10deg) rotateY(12.5deg)', + transform: 'perspective(400px) translateY(0px) rotateX(10deg) rotateY(12.5deg)', }, ], }, { fill: 'both', - startOffsetAdd: '0vh', - endOffsetAdd: '0vh', + startOffsetAdd: '0px', + endOffsetAdd: '0px', keyframes: [ { transform: 'rotate(calc(var(--motion-rotate, 0deg) + -25deg))', @@ -139,10 +139,10 @@ describe('TiltScroll', () => { { keyframes: [ { - transform: 'perspective(400px) translateY(0vh) rotateX(-10deg) rotateY(-25deg)', + transform: 'perspective(400px) translateY(0px) rotateX(-10deg) rotateY(-25deg)', }, { - transform: 'perspective(400px) translateY(0vh) rotateX(0deg) rotateY(0deg)', + transform: 'perspective(400px) translateY(0px) rotateX(0deg) rotateY(0deg)', }, ], }, @@ -167,24 +167,31 @@ describe('TiltScroll', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, namedEffect: { speed: 2 } as TiltScrollType, + startOffset: { name: 'cover', offset: { value: 0, unit: 'percentage' } }, + endOffset: { name: 'cover', offset: { value: 50, unit: 'percentage' } }, }; const expectedResult = [ { - startOffsetAdd: '-20vh', - endOffsetAdd: '0px', + startOffset: { name: 'entry-crossing', offset: { value: 25, unit: 'percentage' } }, + startOffsetAdd: '25vh', + endOffset: { name: 'entry-crossing', offset: { value: 50, unit: 'percentage' } }, + endOffsetAdd: '50vh', keyframes: [ { - transform: 'perspective(400px) translateY(20vh) rotateX(-10deg) rotateY(-25deg)', + transform: + 'perspective(400px) translateY(calc(25% + 25vh)) rotateX(-10deg) rotateY(-25deg)', }, { - transform: 'perspective(400px) translateY(0vh) rotateX(0deg) rotateY(0deg)', + transform: 'perspective(400px) translateY(0px) rotateX(0deg) rotateY(0deg)', }, ], }, { - startOffsetAdd: '-20vh', - endOffsetAdd: '0px', + startOffset: { name: 'entry-crossing', offset: { value: 25, unit: 'percentage' } }, + startOffsetAdd: '25vh', + endOffset: { name: 'entry-crossing', offset: { value: 50, unit: 'percentage' } }, + endOffsetAdd: '50vh', }, ]; @@ -204,7 +211,7 @@ describe('TiltScroll', () => { const expectedResult = [ { fill: 'backwards', - startOffsetAdd: '0vh', + startOffsetAdd: '0px', endOffsetAdd: '0px', custom: { '--motion-perspective': '400px', @@ -223,7 +230,7 @@ describe('TiltScroll', () => { { fill: 'backwards', easing: 'cubic-bezier(0.445, 0.05, 0.55, 0.95)', - startOffsetAdd: '0vh', + startOffsetAdd: '0px', endOffsetAdd: '0px', composite: 'add', keyframes: [ @@ -252,7 +259,7 @@ describe('TiltScroll', () => { { fill: 'forwards', startOffsetAdd: '0px', - endOffsetAdd: '0vh', + endOffsetAdd: '0px', custom: { '--motion-perspective': '400px', }, @@ -270,7 +277,7 @@ describe('TiltScroll', () => { { fill: 'forwards', startOffsetAdd: '0px', - endOffsetAdd: '0vh', + endOffsetAdd: '0px', keyframes: [ { transform: 'rotate(calc(var(--motion-rotate, 0deg) + var(--motion-tilt-z-from)))', @@ -297,8 +304,8 @@ describe('TiltScroll', () => { const expectedResult = [ { fill: 'both', - startOffsetAdd: '0vh', - endOffsetAdd: '0vh', + startOffsetAdd: '0px', + endOffsetAdd: '0px', custom: { '--motion-perspective': '400px', }, @@ -315,8 +322,8 @@ describe('TiltScroll', () => { }, { fill: 'both', - startOffsetAdd: '0vh', - endOffsetAdd: '0vh', + startOffsetAdd: '0px', + endOffsetAdd: '0px', keyframes: [ { transform: 'rotate(calc(var(--motion-rotate, 0deg) + var(--motion-tilt-z-from)))', @@ -376,14 +383,20 @@ describe('TiltScroll', () => { const mockOptions: ScrubAnimationOptions = { ...baseMockOptions, namedEffect: { speed: 2 } as TiltScrollType, + startOffset: { name: 'cover', offset: { value: 0, unit: 'percentage' } }, + endOffset: { name: 'cover', offset: { value: 50, unit: 'percentage' } }, }; const expectedResult = [ { - startOffsetAdd: '-20vh', - endOffsetAdd: '0px', + startOffset: { name: 'entry-crossing', offset: { value: 25, unit: 'percentage' } }, + startOffsetAdd: '25vh', + endOffset: { name: 'entry-crossing', offset: { value: 50, unit: 'percentage' } }, + endOffsetAdd: '50vh', custom: { '--motion-perspective': '400px', + '--motion-tilt-y-from': 'calc(25% + 25vh)', + '--motion-tilt-y-to': '0px', }, keyframes: [ { @@ -397,8 +410,10 @@ describe('TiltScroll', () => { ], }, { - startOffsetAdd: '-20vh', - endOffsetAdd: '0px', + startOffset: { name: 'entry-crossing', offset: { value: 25, unit: 'percentage' } }, + startOffsetAdd: '25vh', + endOffset: { name: 'entry-crossing', offset: { value: 50, unit: 'percentage' } }, + endOffsetAdd: '50vh', }, ]; diff --git a/packages/motion-presets/src/test/presets.spec.ts b/packages/motion-presets/src/test/presets.spec.ts index 58269135..c5e477fe 100644 --- a/packages/motion-presets/src/test/presets.spec.ts +++ b/packages/motion-presets/src/test/presets.spec.ts @@ -70,7 +70,7 @@ const expectValidDefaults = (schema: Record) => { describe.each(presets)('$group $name', ({ group, name, module }) => { const schema = module.schema!; - const render = module.style ?? module.web; + const render = module.style!; test('implicit defaults equal the schema defaults', () => { const implicit = render(optionsFor(group, { type: name })); @@ -83,7 +83,7 @@ describe.each(presets)('$group $name', ({ group, name, module }) => { expect(namesOf(render(structuredClone(options)))).toEqual(module.getNames(options)); }); - test.skipIf(!module.style)('web and style return the same animations', () => { + test('web and style return the same animations', () => { const webAnimations = module.web(optionsFor(group, { type: name })); const styleAnimations = module.style!(optionsFor(group, { type: name })); expect(namesOf(webAnimations)).toEqual(namesOf(styleAnimations)); @@ -171,6 +171,8 @@ const withoutVarFallbacks = (css: string) => { return result; }; +const EASINGS = [undefined, 'linear', 'quadIn', 'cubic-bezier(0.1, 0.2, 0.3, 0.4)']; + test('each keyframes name always has the same keyframes', () => { const groups = { entrance, scroll, ongoing, backgroundScroll }; const groupOptions: Record[]> = { @@ -187,17 +189,18 @@ test('each keyframes name always has the same keyframes', () => { for (const [type, module] of Object.entries(modules as Record)) { if (!module.style) continue; for (const options of groupOptions[group] ?? [{}]) - for (const params of sampleParams(module.schema)) - for (const extra of extraParams[group] ?? [{}]) { - const namedEffect = { type, ...params, ...extra }; - for (const { name, keyframes } of module.style( - structuredClone({ ...options, namedEffect }), - )) { - const css = withoutVarFallbacks(JSON.stringify(keyframes)); - if (!keyframesByName.has(name)) keyframesByName.set(name, css); - else if (keyframesByName.get(name) !== css) collisions.add(name); + for (const easing of EASINGS) + for (const params of sampleParams(module.schema)) + for (const extra of extraParams[group] ?? [{}]) { + const namedEffect = { type, ...params, ...extra }; + for (const { name, keyframes } of module.style( + structuredClone({ ...options, easing, namedEffect }), + )) { + const css = withoutVarFallbacks(JSON.stringify(keyframes)); + if (!keyframesByName.has(name)) keyframesByName.set(name, css); + else if (keyframesByName.get(name) !== css) collisions.add(name); + } } - } } } @@ -206,19 +209,50 @@ test('each keyframes name always has the same keyframes', () => { const EXTERNAL_PROPERTIES = ['--motion-rotate', '--motion-left']; -describe.each(presets.filter(({ module }) => module.style))( - '$group $name custom properties', - ({ group, name, module }) => { - test.each(sampleParams(module.schema))('are suffixed for %o', (params) => { - const options = { ...optionsFor(group, { type: name, ...params }), suffix: '-s1' }; - for (const { custom = {}, keyframes } of module.style!(options)) { - for (const key of Object.keys(custom)) expect(key).toContain('-s1'); - const referenced = [...JSON.stringify(keyframes).matchAll(/var\((--[\w-]+)/g)].map( - ([, property]) => property, +describe.each(presets)('$group $name custom properties', ({ group, name, module }) => { + test.each(sampleParams(module.schema))('are suffixed for %o', (params) => { + const options = { ...optionsFor(group, { type: name, ...params }), suffix: '-s1' }; + for (const { custom = {}, keyframes } of module.style!(options)) { + for (const key of Object.keys(custom)) expect(key).toContain('-s1'); + const referenced = [...JSON.stringify(keyframes).matchAll(/var\((--[\w-]+)/g)].map( + ([, property]) => property, + ); + for (const property of referenced) + expect([...Object.keys(custom), ...EXTERNAL_PROPERTIES]).toContain(property); + } + }); +}); + +describe.each(presets.filter(({ group }) => group === 'ongoing'))( + 'ongoing $name iteration delay', + ({ name, module }) => { + const { iterationDelay: _, ...schema } = module.schema!; + test.each( + sampleParams(schema).flatMap((params) => [ + { params, duration: 1000, iterationDelay: 500, timingFactor: 0.67 }, + { params, duration: 500, iterationDelay: 1000, timingFactor: 0.33 }, + ]), + )( + 'holds the rest after the undelayed motion for $params, $duration + $iterationDelay', + ({ params, duration, iterationDelay, timingFactor }) => { + const [undelayed] = module.style!( + structuredClone({ duration, namedEffect: { type: name, ...params } }), ); - for (const property of referenced) - expect([...Object.keys(custom), ...EXTERNAL_PROPERTIES]).toContain(property); - } - }); + const [delayed] = module.style!( + structuredClone({ duration, namedEffect: { type: name, ...params, iterationDelay } }), + ); + const keyframes = undelayed.keyframes.map((keyframe: { offset: number }) => ({ + ...keyframe, + offset: keyframe.offset * timingFactor, + })); + + expect(delayed.duration).toBe(duration + iterationDelay); + expect(delayed.custom).toEqual(undelayed.custom); + expect(delayed.keyframes).toEqual([ + ...keyframes, + { ...undelayed.keyframes[undelayed.keyframes.length - 1], offset: 1 }, + ]); + }, + ); }, ); diff --git a/packages/motion-presets/src/test/scrollParallax.spec.ts b/packages/motion-presets/src/test/scrollParallax.spec.ts new file mode 100644 index 00000000..541864cb --- /dev/null +++ b/packages/motion-presets/src/test/scrollParallax.spec.ts @@ -0,0 +1,176 @@ +import { describe, expect, test } from 'vitest'; + +import { getScrollParallax } from '../utils'; +import type { EffectScrollRange, RangeOffset } from '../types'; + +type Geometry = { viewport: number; height: number }; + +function toPx(css: string, { viewport, height }: Geometry) { + const units: Record = { '%': height / 100, vh: viewport / 100, px: 1 }; + return [...css.matchAll(/(-?\d*\.?\d+(?:e[+-]?\d+)?)(%|vh|px)/g)].reduce( + (sum, [, value, unit]) => sum + Number(value) * units[unit], + 0, + ); +} + +// scroll positions from the start of cover, per the CSS spec for an element smaller than the viewport +function getNamedRange(name: RangeOffset['name'] = 'cover', { viewport, height }: Geometry) { + return { + cover: [0, viewport + height], + entry: [0, height], + 'entry-crossing': [0, height], + contain: [height, viewport], + exit: [viewport, viewport + height], + 'exit-crossing': [viewport, viewport + height], + }[name]; +} + +function resolveOffset( + { name, offset = { value: 0, unit: 'percentage' } }: RangeOffset, + add: string | undefined, + isEnd: boolean, + geometry: Geometry, +) { + const [start, end] = getNamedRange(name, geometry); + const position = + offset.unit === 'percentage' + ? start + (offset.value / 100) * (end - start) + : (isEnd ? end : start) + toPx(`${offset.value}${offset.unit}`, geometry); + return position + toPx(add || '0px', geometry); +} + +function simulate( + range: EffectScrollRange, + offsets: { startOffset: RangeOffset; endOffset: RangeOffset }, + speed: number, + center: number | undefined, + geometry: Geometry, +) { + const parallax = getScrollParallax(range, offsets, speed, center); + const animationOffsets = { ...offsets, ...parallax }; + const start = resolveOffset( + animationOffsets.startOffset, + animationOffsets.startOffsetAdd, + false, + geometry, + ); + const end = resolveOffset( + animationOffsets.endOffset, + animationOffsets.endOffsetAdd, + true, + geometry, + ); + const from = toPx(parallax.from, geometry); + const to = toPx(parallax.to, geometry); + + const translateAt = (scroll: number) => { + const progress = Math.min(1, Math.max(0, (scroll - start) / (end - start))); + return from + (to - from) * progress; + }; + const screenTopAt = (scroll: number) => geometry.viewport - scroll + translateAt(scroll); + + return { start, end, translateAt, screenTopAt }; +} + +const cover = (value: number): RangeOffset => ({ + name: 'cover', + offset: { value, unit: 'percentage' }, +}); + +const RANGES: [string, EffectScrollRange, { startOffset: RangeOffset; endOffset: RangeOffset }][] = + [ + ['in over cover 0% - 50%', 'in', { startOffset: cover(0), endOffset: cover(50) }], + ['out over cover 50% - 100%', 'out', { startOffset: cover(50), endOffset: cover(100) }], + ['continuous over cover', 'continuous', { startOffset: cover(0), endOffset: cover(100) }], + [ + 'continuous over contain', + 'continuous', + { + startOffset: { name: 'contain', offset: { value: 0, unit: 'percentage' } }, + endOffset: { name: 'contain', offset: { value: 100, unit: 'percentage' } }, + }, + ], + [ + 'in over entry 20% - contain 50%', + 'in', + { + startOffset: { name: 'entry', offset: { value: 20, unit: 'percentage' } }, + endOffset: { name: 'contain', offset: { value: 50, unit: 'percentage' } }, + }, + ], + [ + 'continuous over cover 100px - exit -10vh', + 'continuous', + { + startOffset: { name: 'cover', offset: { value: 100, unit: 'px' } }, + endOffset: { name: 'exit', offset: { value: -10, unit: 'vh' } }, + }, + ], + ]; + +const GEOMETRIES: Geometry[] = [ + { viewport: 800, height: 100 }, + { viewport: 800, height: 600 }, + { viewport: 1000, height: 260 }, +]; + +describe('getScrollParallax', () => { + describe.each(RANGES)('%s', (_, range, offsets) => { + describe.each(GEOMETRIES)('viewport $viewport, element $height', (geometry) => { + test.each([0.25, 0.5, 0.7159, 2, 3])('moves at speed %s', (speed) => { + const { start, end, screenTopAt } = simulate(range, offsets, speed, undefined, geometry); + const [s1, s2] = [start + (end - start) * 0.2, start + (end - start) * 0.9]; + + expect(-(screenTopAt(s2) - screenTopAt(s1)) / (s2 - s1)).toBeCloseTo(speed, 10); + }); + + test.each([0, 0.3, 0.5, 1])('is at rest at center %s', (center) => { + const { translateAt } = simulate(range, offsets, 0.5, center, geometry); + const start = resolveOffset(offsets.startOffset, undefined, false, geometry); + const end = resolveOffset(offsets.endOffset, undefined, true, geometry); + + expect(translateAt(start + center * (end - start))).toBeCloseTo(0, 10); + }); + }); + }); + + test.each([ + ['in', 1], + ['out', 0], + ['continuous', 0.5], + ] as const)('defaults the center of %s to %s', (range, center) => { + const geometry = GEOMETRIES[0]; + const offsets = { startOffset: cover(0), endOffset: cover(100) }; + const { translateAt } = simulate(range, offsets, 0.5, undefined, geometry); + + expect(translateAt(center * (geometry.viewport + geometry.height))).toBeCloseTo(0, 10); + }); + + test('keeps the scroll range at speed 1', () => { + expect(getScrollParallax('in', { startOffset: cover(0), endOffset: cover(50) }, 1)).toEqual({ + from: '0px', + to: '0px', + startOffsetAdd: '0px', + endOffsetAdd: '0px', + }); + }); + + test('keeps the element nearly in place at speed 0', () => { + const geometry = GEOMETRIES[0]; + const { start, end, screenTopAt } = simulate( + 'continuous', + { startOffset: cover(0), endOffset: cover(100) }, + 0, + undefined, + geometry, + ); + + expect((screenTopAt(start) - screenTopAt(end)) / (end - start)).toBeCloseTo(0.01, 10); + }); + + test('defaults the scroll range to cover', () => { + expect(getScrollParallax('continuous', {}, 0.5)).toEqual( + getScrollParallax('continuous', { startOffset: cover(0), endOffset: cover(100) }, 0.5), + ); + }); +}); diff --git a/packages/motion-presets/src/utils.ts b/packages/motion-presets/src/utils.ts index eec52ad4..2f957b70 100644 --- a/packages/motion-presets/src/utils.ts +++ b/packages/motion-presets/src/utils.ts @@ -6,6 +6,7 @@ import type { ScrubTransitionEasing, AnimationFillMode, TimeAnimationOptions, + ScrubAnimationOptions, } from '@wix/motion'; import type { LengthInput, LengthValue } from './types'; @@ -528,3 +529,107 @@ export function parseDirection( return defaultValue; } + +type ScrollOffsets = Pick; +type ScrollOffset = NonNullable; +type LinearLength = Record; + +// '%' is of the element's height, as in its translateY and in its entry-crossing range +const RANGE_START_AND_SIZE: Record, LinearLength[]> = { + cover: [{}, { '%': 100, vh: 100 }], + entry: [{}, { '%': 100 }], + 'entry-crossing': [{}, { '%': 100 }], + contain: [{ '%': 100 }, { '%': -100, vh: 100 }], + exit: [{ vh: 100 }, { '%': 100 }], + 'exit-crossing': [{ vh: 100 }, { '%': 100 }], +}; + +function combineLengths(...terms: [number, LinearLength][]) { + const result: LinearLength = {}; + terms.forEach(([factor, length]) => + Object.entries(length).forEach(([unit, value]) => { + result[unit] = (result[unit] || 0) + factor * value; + }), + ); + return result; +} + +function linearLengthToCss(length: LinearLength) { + const terms = Object.entries(length) + .filter(([, value]) => value) + .map(([unit, value]) => `${value}${unit}`); + return terms.length > 1 ? `calc(${terms.join(' + ')})` : terms[0] || '0px'; +} + +// an end offset is from the end of its named range, see getRange() in @wix/motion +function getRangePosition({ name = 'cover', offset }: ScrollOffset, isEnd: boolean) { + const { value = 0, unit = 'percentage' } = offset || {}; + const [start, size] = RANGE_START_AND_SIZE[name]; + return unit === 'percentage' + ? combineLengths([1, start], [value / 100, size]) + : combineLengths([1, start], [Number(isEnd), size], [value, { [unit]: 1 }]); +} + +// the translateY for moving at `speed` times the scroll, at rest at `center` of the scroll range, +// and the scroll range widened by 1 / speed around `center` so the speed is exact. +// assumes an element smaller than the viewport, larger ones drift over entry, exit and contain ranges +export function getScrollParallax( + range: EffectScrollRange, + { + startOffset = { offset: { value: 0, unit: 'percentage' } }, + endOffset = { offset: { value: 100, unit: 'percentage' } }, + }: ScrollOffsets, + speed: number, + center: number = range === 'continuous' ? 0.5 : range === 'out' ? 0 : 1, +) { + const shrink = 1 - 1 / Math.max(speed, 0.01); + if (!shrink) { + return { from: '0px', to: '0px', startOffsetAdd: '0px', endOffsetAdd: '0px' }; + } + + const c = Math.min(1, Math.max(0, center)); + const start = getRangePosition(startOffset, false); + const end = getRangePosition(endOffset, true); + const travel = combineLengths([1, end], [-1, start]); + const from = combineLengths([c * shrink, travel]); + const to = combineLengths([(c - 1) * shrink, travel]); + const { '%': startPercentage = 0, ...startOffsetAdd } = combineLengths([1, start], [1, from]); + const { '%': endPercentage = 0, ...endOffsetAdd } = combineLengths([1, end], [1, to]); + + return { + from: linearLengthToCss(from), + to: linearLengthToCss(to), + startOffset: { + name: 'entry-crossing' as const, + offset: { value: startPercentage, unit: 'percentage' as const }, + }, + startOffsetAdd: linearLengthToCss(startOffsetAdd), + endOffset: { + name: 'entry-crossing' as const, + offset: { value: endPercentage, unit: 'percentage' as const }, + }, + endOffsetAdd: linearLengthToCss(endOffsetAdd), + }; +} + +export function holdAtRest(keyframes: T[], timingFactor: number) { + const active = keyframes.map((keyframe) => ({ + ...keyframe, + offset: keyframe.offset * timingFactor, + })); + return timingFactor < 1 ? [...active, { ...keyframes[keyframes.length - 1], offset: 1 }] : active; +} + +export function getEasingKey(easing?: string) { + if (!easing) { + return ''; + } + if (/^[a-zA-Z][a-zA-Z0-9-]*$/.test(easing)) { + return `-${easing}`; + } + let hash = 0; + for (const char of easing) { + hash = (Math.imul(31, hash) + char.charCodeAt(0)) | 0; + } + return `-${(hash >>> 0).toString(36)}`; +} diff --git a/packages/motion/docs/api/types.md b/packages/motion/docs/api/types.md index 095e0d48..3a83330a 100644 --- a/packages/motion/docs/api/types.md +++ b/packages/motion/docs/api/types.md @@ -65,7 +65,6 @@ type ScrubAnimationOptions = { alternate?: boolean; reversed?: boolean; transitionDuration?: number; // pointer smoothing (ms) - transitionDelay?: number; transitionEasing?: ScrubTransitionEasing; // 'linear' | 'hardBackOut' | 'easeOut' | 'elastic' | 'bounce' centeredToTarget?: boolean; duration?: LengthPercentage; // NOTE: length/percentage, not ms @@ -74,7 +73,7 @@ type ScrubAnimationOptions = { - **`duration` here is a [`LengthPercentage`](#length--percentage--lengthpercentage), not a millisecond number** — unlike `TimeAnimationOptions.duration`. It expresses a portion of the scroll/scrub range. - `startOffset` / `endOffset` live on these options, not on the trigger. -- `transitionDuration` / `transitionDelay` / `transitionEasing` / `centeredToTarget` only apply to pointer-driven (`pointer-move`) scrubbing. +- `transitionDuration` / `transitionEasing` / `centeredToTarget` only apply to pointer-driven (`pointer-move`) scrubbing. ### `AnimationExtraOptions` diff --git a/packages/motion/docs/core-concepts.md b/packages/motion/docs/core-concepts.md index d5ee9de2..c817b7ba 100644 --- a/packages/motion/docs/core-concepts.md +++ b/packages/motion/docs/core-concepts.md @@ -25,7 +25,7 @@ getWebAnimation(element, { **Time-based options** (used when no `trigger` is passed): `duration` (ms), `delay`, `endDelay`, `easing`, `iterations` (`0` ⇒ `Infinity`, `undefined` ⇒ `1`), `alternate`, `fill`, `reversed` — plus one of the three effect fields below. -**Scrub options** (used with a `view-progress` or `pointer-move` trigger): `startOffset` / `endOffset` (scroll range), `playbackRate`, `transitionDuration` / `transitionDelay` / `transitionEasing` / `centeredToTarget` (pointer smoothing), `easing`, `iterations`, `fill`, `alternate`, `reversed` — plus one of the three effect fields. Note `duration` here is a `{ value, unit }` length/percentage, not milliseconds. +**Scrub options** (used with a `view-progress` or `pointer-move` trigger): `startOffset` / `endOffset` (scroll range), `playbackRate`, `transitionDuration` / `transitionEasing` / `centeredToTarget` (pointer smoothing), `easing`, `iterations`, `fill`, `alternate`, `reversed` — plus one of the three effect fields. Note `duration` here is a `{ value, unit }` length/percentage, not milliseconds. ## The three effect-definition modes diff --git a/packages/motion/rules/waapi.md b/packages/motion/rules/waapi.md index dc31bfc3..4c78c89f 100644 --- a/packages/motion/rules/waapi.md +++ b/packages/motion/rules/waapi.md @@ -114,7 +114,6 @@ type ScrubAnimationOptions = { alternate?: boolean; reversed?: boolean; transitionDuration?: number; // pointer smoothing (ms) - transitionDelay?: number; transitionEasing?: ScrubTransitionEasing; // 'linear' | 'hardBackOut' | 'easeOut' | 'elastic' | 'bounce' centeredToTarget?: boolean; duration?: LengthPercentage; // NOTE: length/percentage, NOT ms — unlike TimeAnimationOptions.duration diff --git a/packages/motion/src/AnimationGroup.ts b/packages/motion/src/AnimationGroup.ts index 5dab3992..7d0d44ff 100644 --- a/packages/motion/src/AnimationGroup.ts +++ b/packages/motion/src/AnimationGroup.ts @@ -84,6 +84,11 @@ export class AnimationGroup { } } + // keeps finished, filling animations from being auto-removed when later animations cover their properties + persist() { + this.animations.forEach((animation) => animation.persist?.()); + } + cancel() { for (const animation of this.animations) { animation.cancel(); diff --git a/packages/motion/src/api/webAnimations.ts b/packages/motion/src/api/webAnimations.ts index bc7e7ecb..e6bc6174 100644 --- a/packages/motion/src/api/webAnimations.ts +++ b/packages/motion/src/api/webAnimations.ts @@ -196,6 +196,11 @@ function getWebAnimation( return animation; }); + // layers of the same effect always cover the ones below them, which the browser would remove once they finish + if (animations.length > 1) { + animations.forEach((animation) => animation.persist?.()); + } + // create an AnimationGroup with the generate animations return new AnimationGroup(animations, { ...animationOptions, diff --git a/packages/motion/src/types.ts b/packages/motion/src/types.ts index b8d389e0..eb1b0bb9 100644 --- a/packages/motion/src/types.ts +++ b/packages/motion/src/types.ts @@ -173,7 +173,6 @@ type ScrubAnimationDataBase = { alternate?: boolean; reversed?: boolean; transitionDuration?: number; - transitionDelay?: number; transitionEasing?: ScrubTransitionEasing; centeredToTarget?: boolean; }; diff --git a/packages/motion/test/AnimationGroup.spec.ts b/packages/motion/test/AnimationGroup.spec.ts index b0760597..d8d2369a 100644 --- a/packages/motion/test/AnimationGroup.spec.ts +++ b/packages/motion/test/AnimationGroup.spec.ts @@ -776,6 +776,18 @@ describe('AnimationGroup', () => { }); }); + describe('persist()', () => { + test('should call persist on all animations', () => { + const mockAnimation1 = createMockAnimation({ persist: vi.fn() }); + const mockAnimation2 = createMockAnimation({ persist: vi.fn() }); + + new AnimationGroup([mockAnimation1, mockAnimation2]).persist(); + + expect(mockAnimation1.persist).toHaveBeenCalledTimes(1); + expect(mockAnimation2.persist).toHaveBeenCalledTimes(1); + }); + }); + describe('cancel()', () => { test('should call cancel on all animations', () => { const mockAnimation1 = createMockAnimation(); diff --git a/packages/motion/test/motion.spec.ts b/packages/motion/test/motion.spec.ts index 1b5e2b1c..ea5b3179 100644 --- a/packages/motion/test/motion.spec.ts +++ b/packages/motion/test/motion.spec.ts @@ -584,6 +584,31 @@ describe('motion.ts', () => { (AnimationGroup as Mock).mockRestore(); }); + test('should persist the animations of layered effects only', async () => { + const { AnimationGroup } = await import('../src/AnimationGroup'); + (AnimationGroup as Mock).mockImplementation(function () { + return mockAnimationGroup; + }); + const persist = vi.fn(); + const OriginalAnimation = (globalThis as any).Animation; + (globalThis as any).Animation = vi.fn(function (this: any, ...args: any[]) { + return { ...new OriginalAnimation(...args), persist }; + }); + const layer = { keyframes: [{ opacity: 0 }, { opacity: 1 }], duration: 1000 }; + registerEffects({ + TwoLayers: { ...mockFadeInPreset, web: vi.fn(() => [layer, layer]) }, + } as any); + + getWebAnimation(mockElement, { namedEffect: { type: 'FadeIn' }, duration: 1000 }); + expect(persist).not.toHaveBeenCalled(); + + getWebAnimation(mockElement, { namedEffect: { type: 'TwoLayers' }, duration: 1000 }); + expect(persist).toHaveBeenCalledTimes(2); + + (globalThis as any).Animation = OriginalAnimation; + (AnimationGroup as Mock).mockRestore(); + }); + test('should handle HTMLElement target', async () => { const animationOptions: AnimationOptions = { namedEffect: { type: 'FadeIn', id: 'fade' }, diff --git a/skills/interactor/references/config-schema.md b/skills/interactor/references/config-schema.md index 0e7c745d..bb7d32da 100644 --- a/skills/interactor/references/config-schema.md +++ b/skills/interactor/references/config-schema.md @@ -188,7 +188,6 @@ directly. The pointer-relevant fields are `transitionDuration`/`transitionEasing composite?: 'replace' | 'add' | 'accumulate'; centeredToTarget?: boolean; // remap 0.5 progress to the target's center transitionDuration?: number; // ms — smoothing on progress jumps (mainly pointerMove) - transitionDelay?: number; // ms transitionEasing?: 'linear' | 'hardBackOut' | 'easeOut' | 'elastic' | 'bounce'; // + exactly one payload } diff --git a/skills/interactor/references/motion-engine.md b/skills/interactor/references/motion-engine.md index 4f42c41d..aae2f06e 100644 --- a/skills/interactor/references/motion-engine.md +++ b/skills/interactor/references/motion-engine.md @@ -39,7 +39,7 @@ trigger + which effect field is present — **not** by a `type` field. { keyframeEffect? | namedEffect? | customEffect?; startOffset?: RangeOffset; endOffset?: RangeOffset; playbackRate?; easing?; iterations?; alternate?; fill?; reversed?; - transitionDuration?; transitionDelay?; transitionEasing?; centeredToTarget?; + transitionDuration?; transitionEasing?; centeredToTarget?; duration?: LengthPercentage /* scrub duration is length/%, NOT ms */ } ``` diff --git a/skills/interactor/references/presets.md b/skills/interactor/references/presets.md index fc16fd90..45e2f766 100644 --- a/skills/interactor/references/presets.md +++ b/skills/interactor/references/presets.md @@ -92,7 +92,7 @@ text into spans, then stagger them with a sequence — see `plugins.md`. **All share `range: 'in' | 'out' | 'continuous'`** (in = animate in on enter, out = animate out on exit, continuous = pass through idle across the full range; prefer `'continuous'`) — **except `ParallaxScroll`**. Use `fill: 'both'`. `direction` is -where the motion goes; `speed` is the approximate visual speed relative to the +where the motion goes; `speed` is the visual speed relative to the scroll (`1` = moves with the page). > **Two different "range"s — don't confuse them.** The `range` above is a **preset @@ -131,7 +131,7 @@ range: 'continuous' }`). It is unrelated to the effect-level **`rangeStart`/ Run with `iterations: Infinity` (or large) for a perpetual loop, or attach to a trigger. **All accept `iterationDelay`** (ms, default `0`) inside `namedEffect` — an -idle pause appended after each cycle. +idle hold at rest appended after each cycle (the motion itself is unchanged). | Preset | Params (default) | | :-------- | :------------------------------------------------------------------------------------------------------- | From a162c5ba7381d8a4ce86881999bbc88fa8b0feed Mon Sep 17 00:00:00 2001 From: ameerf-wix Date: Wed, 21 Oct 2026 14:24:22 +0300 Subject: [PATCH 4/4] bounceIn --- .../src/library/entrance/BounceIn.ts | 20 +- .../library/entrance/test/BounceIn.spec.ts | 256 +++++++++--------- 2 files changed, 136 insertions(+), 140 deletions(-) diff --git a/packages/motion-presets/src/library/entrance/BounceIn.ts b/packages/motion-presets/src/library/entrance/BounceIn.ts index 124404a1..b6c87283 100644 --- a/packages/motion-presets/src/library/entrance/BounceIn.ts +++ b/packages/motion-presets/src/library/entrance/BounceIn.ts @@ -5,7 +5,6 @@ import { parseDirection, parseLengthOrCalc, scaleLength, - roundNumber, getEntranceFill, } from '../../utils'; import type { BounceIn, TimeAnimationOptions } from '../../types'; @@ -45,8 +44,6 @@ const TRANSLATE_DIRECTION_MAP = { back: { x: 0, y: 0, z: -1 }, }; -const roundUnit = (value: number) => roundNumber(value, 6); - export function web(options: TimeAnimationOptions) { return style(options, true); } @@ -59,8 +56,7 @@ export function style(options: TimeAnimationOptions, asWeb = false) { DIRECTIONS, schema.from.default, ); - const travel = parseLengthOrCalc(namedEffect?.travel, schema.travel.default); - const distanceUnit = scaleLength(travel, 1 / schema.travel.default.value, roundUnit); + const travel = scaleLength(parseLengthOrCalc(namedEffect?.travel, schema.travel.default)); const { perspective = schema.perspective.default } = namedEffect || {}; const [fadeIn, bounceIn] = getNames(options); const perspectiveValue = direction === 'back' ? `perspective(${perspective}px)` : ' '; @@ -70,7 +66,7 @@ export function style(options: TimeAnimationOptions, asWeb = false) { [`--motion-direction-x${suffix}`]: x, [`--motion-direction-y${suffix}`]: y, [`--motion-direction-z${suffix}`]: z, - [`--motion-distance-unit${suffix}`]: distanceUnit, + [`--motion-distance${suffix}`]: travel, [`--motion-perspective${suffix}`]: perspectiveValue, [`--motion-ease-in${suffix}`]: getEasing(easeOut), [`--motion-ease-out${suffix}`]: getEasing(easeIn), @@ -78,7 +74,7 @@ export function style(options: TimeAnimationOptions, asWeb = false) { const easeIn_ = toKeyframeValue(custom, `--motion-ease-in${suffix}`, asWeb); const easeOut_ = toKeyframeValue(custom, `--motion-ease-out${suffix}`, asWeb); - const distanceUnit_ = toKeyframeValue(custom, `--motion-distance-unit${suffix}`, asWeb); + const distance = toKeyframeValue(custom, `--motion-distance${suffix}`, asWeb); const perspective_ = toKeyframeValue(custom, `--motion-perspective${suffix}`, asWeb, ''); const directionX = toKeyframeValue(custom, `--motion-direction-x${suffix}`, asWeb); const directionY = toKeyframeValue(custom, `--motion-direction-y${suffix}`, asWeb); @@ -89,11 +85,11 @@ export function style(options: TimeAnimationOptions, asWeb = false) { animationTimingFunction: index % 2 ? easeIn_ : easeOut_, transform: `${( perspective_ as string - ).trim()} translate3d(calc(${directionX} * ${distanceUnit_} * ${ - translate / 2 - }), calc(${directionY} * ${distanceUnit_} * ${ - translate / 2 - }), calc(${directionZ} * ${distanceUnit_} * ${translate / 2})) rotateZ(var(--motion-rotate, 0deg))`, + ).trim()} translate3d(calc(${directionX} * ${distance} * ${ + translate / 100 + }), calc(${directionY} * ${distance} * ${ + translate / 100 + }), calc(${directionZ} * ${distance} * ${translate / 100})) rotateZ(var(--motion-rotate, 0deg))`, })); return [ diff --git a/packages/motion-presets/src/library/entrance/test/BounceIn.spec.ts b/packages/motion-presets/src/library/entrance/test/BounceIn.spec.ts index 487ff7ee..cb918455 100644 --- a/packages/motion-presets/src/library/entrance/test/BounceIn.spec.ts +++ b/packages/motion-presets/src/library/entrance/test/BounceIn.spec.ts @@ -23,61 +23,61 @@ describe('BounceIn', () => { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0, transform: - ' translate3d(calc(0 * 1px * 50), calc(1 * 1px * 50), calc(0 * 1px * 50)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 50px * 1), calc(1 * 50px * 1), calc(0 * 50px * 1)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.3, transform: - ' translate3d(calc(0 * 1px * 0), calc(1 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 50px * 0), calc(1 * 50px * 0), calc(0 * 50px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.42, transform: - ' translate3d(calc(0 * 1px * 17.5), calc(1 * 1px * 17.5), calc(0 * 1px * 17.5)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 50px * 0.35), calc(1 * 50px * 0.35), calc(0 * 50px * 0.35)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.54, transform: - ' translate3d(calc(0 * 1px * 0), calc(1 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 50px * 0), calc(1 * 50px * 0), calc(0 * 50px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.62, transform: - ' translate3d(calc(0 * 1px * 10.5), calc(1 * 1px * 10.5), calc(0 * 1px * 10.5)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 50px * 0.21), calc(1 * 50px * 0.21), calc(0 * 50px * 0.21)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.74, transform: - ' translate3d(calc(0 * 1px * 0), calc(1 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 50px * 0), calc(1 * 50px * 0), calc(0 * 50px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.82, transform: - ' translate3d(calc(0 * 1px * 4.5), calc(1 * 1px * 4.5), calc(0 * 1px * 4.5)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 50px * 0.09), calc(1 * 50px * 0.09), calc(0 * 50px * 0.09)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.9, transform: - ' translate3d(calc(0 * 1px * 0), calc(1 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 50px * 0), calc(1 * 50px * 0), calc(0 * 50px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.95, transform: - ' translate3d(calc(0 * 1px * 1), calc(1 * 1px * 1), calc(0 * 1px * 1)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 50px * 0.02), calc(1 * 50px * 0.02), calc(0 * 50px * 0.02)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 1, transform: - ' translate3d(calc(0 * 1px * 0), calc(1 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 50px * 0), calc(1 * 50px * 0), calc(0 * 50px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, ], }, @@ -104,61 +104,61 @@ describe('BounceIn', () => { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0, transform: - ' translate3d(calc(0 * 2px * 50), calc(1 * 2px * 50), calc(0 * 2px * 50)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 100px * 1), calc(1 * 100px * 1), calc(0 * 100px * 1)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.3, transform: - ' translate3d(calc(0 * 2px * 0), calc(1 * 2px * 0), calc(0 * 2px * 0)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 100px * 0), calc(1 * 100px * 0), calc(0 * 100px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.42, transform: - ' translate3d(calc(0 * 2px * 17.5), calc(1 * 2px * 17.5), calc(0 * 2px * 17.5)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 100px * 0.35), calc(1 * 100px * 0.35), calc(0 * 100px * 0.35)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.54, transform: - ' translate3d(calc(0 * 2px * 0), calc(1 * 2px * 0), calc(0 * 2px * 0)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 100px * 0), calc(1 * 100px * 0), calc(0 * 100px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.62, transform: - ' translate3d(calc(0 * 2px * 10.5), calc(1 * 2px * 10.5), calc(0 * 2px * 10.5)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 100px * 0.21), calc(1 * 100px * 0.21), calc(0 * 100px * 0.21)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.74, transform: - ' translate3d(calc(0 * 2px * 0), calc(1 * 2px * 0), calc(0 * 2px * 0)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 100px * 0), calc(1 * 100px * 0), calc(0 * 100px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.82, transform: - ' translate3d(calc(0 * 2px * 4.5), calc(1 * 2px * 4.5), calc(0 * 2px * 4.5)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 100px * 0.09), calc(1 * 100px * 0.09), calc(0 * 100px * 0.09)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.9, transform: - ' translate3d(calc(0 * 2px * 0), calc(1 * 2px * 0), calc(0 * 2px * 0)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 100px * 0), calc(1 * 100px * 0), calc(0 * 100px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.95, transform: - ' translate3d(calc(0 * 2px * 1), calc(1 * 2px * 1), calc(0 * 2px * 1)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 100px * 0.02), calc(1 * 100px * 0.02), calc(0 * 100px * 0.02)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 1, transform: - ' translate3d(calc(0 * 2px * 0), calc(1 * 2px * 0), calc(0 * 2px * 0)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 100px * 0), calc(1 * 100px * 0), calc(0 * 100px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, ], }, @@ -185,61 +185,61 @@ describe('BounceIn', () => { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0, transform: - ' translate3d(calc(0 * 1px * 50), calc(-1 * 1px * 50), calc(0 * 1px * 50)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 50px * 1), calc(-1 * 50px * 1), calc(0 * 50px * 1)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.3, transform: - ' translate3d(calc(0 * 1px * 0), calc(-1 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 50px * 0), calc(-1 * 50px * 0), calc(0 * 50px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.42, transform: - ' translate3d(calc(0 * 1px * 17.5), calc(-1 * 1px * 17.5), calc(0 * 1px * 17.5)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 50px * 0.35), calc(-1 * 50px * 0.35), calc(0 * 50px * 0.35)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.54, transform: - ' translate3d(calc(0 * 1px * 0), calc(-1 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 50px * 0), calc(-1 * 50px * 0), calc(0 * 50px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.62, transform: - ' translate3d(calc(0 * 1px * 10.5), calc(-1 * 1px * 10.5), calc(0 * 1px * 10.5)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 50px * 0.21), calc(-1 * 50px * 0.21), calc(0 * 50px * 0.21)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.74, transform: - ' translate3d(calc(0 * 1px * 0), calc(-1 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 50px * 0), calc(-1 * 50px * 0), calc(0 * 50px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.82, transform: - ' translate3d(calc(0 * 1px * 4.5), calc(-1 * 1px * 4.5), calc(0 * 1px * 4.5)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 50px * 0.09), calc(-1 * 50px * 0.09), calc(0 * 50px * 0.09)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.9, transform: - ' translate3d(calc(0 * 1px * 0), calc(-1 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 50px * 0), calc(-1 * 50px * 0), calc(0 * 50px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.95, transform: - ' translate3d(calc(0 * 1px * 1), calc(-1 * 1px * 1), calc(0 * 1px * 1)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 50px * 0.02), calc(-1 * 50px * 0.02), calc(0 * 50px * 0.02)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 1, transform: - ' translate3d(calc(0 * 1px * 0), calc(-1 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(0 * 50px * 0), calc(-1 * 50px * 0), calc(0 * 50px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, ], }, @@ -266,61 +266,61 @@ describe('BounceIn', () => { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0, transform: - ' translate3d(calc(1 * 1px * 50), calc(0 * 1px * 50), calc(0 * 1px * 50)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(1 * 50px * 1), calc(0 * 50px * 1), calc(0 * 50px * 1)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.3, transform: - ' translate3d(calc(1 * 1px * 0), calc(0 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(1 * 50px * 0), calc(0 * 50px * 0), calc(0 * 50px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.42, transform: - ' translate3d(calc(1 * 1px * 17.5), calc(0 * 1px * 17.5), calc(0 * 1px * 17.5)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(1 * 50px * 0.35), calc(0 * 50px * 0.35), calc(0 * 50px * 0.35)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.54, transform: - ' translate3d(calc(1 * 1px * 0), calc(0 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(1 * 50px * 0), calc(0 * 50px * 0), calc(0 * 50px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.62, transform: - ' translate3d(calc(1 * 1px * 10.5), calc(0 * 1px * 10.5), calc(0 * 1px * 10.5)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(1 * 50px * 0.21), calc(0 * 50px * 0.21), calc(0 * 50px * 0.21)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.74, transform: - ' translate3d(calc(1 * 1px * 0), calc(0 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(1 * 50px * 0), calc(0 * 50px * 0), calc(0 * 50px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.82, transform: - ' translate3d(calc(1 * 1px * 4.5), calc(0 * 1px * 4.5), calc(0 * 1px * 4.5)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(1 * 50px * 0.09), calc(0 * 50px * 0.09), calc(0 * 50px * 0.09)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.9, transform: - ' translate3d(calc(1 * 1px * 0), calc(0 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(1 * 50px * 0), calc(0 * 50px * 0), calc(0 * 50px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.95, transform: - ' translate3d(calc(1 * 1px * 1), calc(0 * 1px * 1), calc(0 * 1px * 1)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(1 * 50px * 0.02), calc(0 * 50px * 0.02), calc(0 * 50px * 0.02)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 1, transform: - ' translate3d(calc(1 * 1px * 0), calc(0 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(1 * 50px * 0), calc(0 * 50px * 0), calc(0 * 50px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, ], }, @@ -347,61 +347,61 @@ describe('BounceIn', () => { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0, transform: - ' translate3d(calc(-1 * 1px * 50), calc(0 * 1px * 50), calc(0 * 1px * 50)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(-1 * 50px * 1), calc(0 * 50px * 1), calc(0 * 50px * 1)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.3, transform: - ' translate3d(calc(-1 * 1px * 0), calc(0 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(-1 * 50px * 0), calc(0 * 50px * 0), calc(0 * 50px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.42, transform: - ' translate3d(calc(-1 * 1px * 17.5), calc(0 * 1px * 17.5), calc(0 * 1px * 17.5)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(-1 * 50px * 0.35), calc(0 * 50px * 0.35), calc(0 * 50px * 0.35)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.54, transform: - ' translate3d(calc(-1 * 1px * 0), calc(0 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(-1 * 50px * 0), calc(0 * 50px * 0), calc(0 * 50px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.62, transform: - ' translate3d(calc(-1 * 1px * 10.5), calc(0 * 1px * 10.5), calc(0 * 1px * 10.5)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(-1 * 50px * 0.21), calc(0 * 50px * 0.21), calc(0 * 50px * 0.21)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.74, transform: - ' translate3d(calc(-1 * 1px * 0), calc(0 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(-1 * 50px * 0), calc(0 * 50px * 0), calc(0 * 50px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.82, transform: - ' translate3d(calc(-1 * 1px * 4.5), calc(0 * 1px * 4.5), calc(0 * 1px * 4.5)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(-1 * 50px * 0.09), calc(0 * 50px * 0.09), calc(0 * 50px * 0.09)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.9, transform: - ' translate3d(calc(-1 * 1px * 0), calc(0 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(-1 * 50px * 0), calc(0 * 50px * 0), calc(0 * 50px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.95, transform: - ' translate3d(calc(-1 * 1px * 1), calc(0 * 1px * 1), calc(0 * 1px * 1)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(-1 * 50px * 0.02), calc(0 * 50px * 0.02), calc(0 * 50px * 0.02)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 1, transform: - ' translate3d(calc(-1 * 1px * 0), calc(0 * 1px * 0), calc(0 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', + ' translate3d(calc(-1 * 50px * 0), calc(0 * 50px * 0), calc(0 * 50px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, ], }, @@ -428,61 +428,61 @@ describe('BounceIn', () => { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0, transform: - 'perspective(800px) translate3d(calc(0 * 1px * 50), calc(0 * 1px * 50), calc(-1 * 1px * 50)) rotateZ(var(--motion-rotate, 0deg))', + 'perspective(800px) translate3d(calc(0 * 50px * 1), calc(0 * 50px * 1), calc(-1 * 50px * 1)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.3, transform: - 'perspective(800px) translate3d(calc(0 * 1px * 0), calc(0 * 1px * 0), calc(-1 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'perspective(800px) translate3d(calc(0 * 50px * 0), calc(0 * 50px * 0), calc(-1 * 50px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.42, transform: - 'perspective(800px) translate3d(calc(0 * 1px * 17.5), calc(0 * 1px * 17.5), calc(-1 * 1px * 17.5)) rotateZ(var(--motion-rotate, 0deg))', + 'perspective(800px) translate3d(calc(0 * 50px * 0.35), calc(0 * 50px * 0.35), calc(-1 * 50px * 0.35)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.54, transform: - 'perspective(800px) translate3d(calc(0 * 1px * 0), calc(0 * 1px * 0), calc(-1 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'perspective(800px) translate3d(calc(0 * 50px * 0), calc(0 * 50px * 0), calc(-1 * 50px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.62, transform: - 'perspective(800px) translate3d(calc(0 * 1px * 10.5), calc(0 * 1px * 10.5), calc(-1 * 1px * 10.5)) rotateZ(var(--motion-rotate, 0deg))', + 'perspective(800px) translate3d(calc(0 * 50px * 0.21), calc(0 * 50px * 0.21), calc(-1 * 50px * 0.21)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.74, transform: - 'perspective(800px) translate3d(calc(0 * 1px * 0), calc(0 * 1px * 0), calc(-1 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'perspective(800px) translate3d(calc(0 * 50px * 0), calc(0 * 50px * 0), calc(-1 * 50px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.82, transform: - 'perspective(800px) translate3d(calc(0 * 1px * 4.5), calc(0 * 1px * 4.5), calc(-1 * 1px * 4.5)) rotateZ(var(--motion-rotate, 0deg))', + 'perspective(800px) translate3d(calc(0 * 50px * 0.09), calc(0 * 50px * 0.09), calc(-1 * 50px * 0.09)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 0.9, transform: - 'perspective(800px) translate3d(calc(0 * 1px * 0), calc(0 * 1px * 0), calc(-1 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'perspective(800px) translate3d(calc(0 * 50px * 0), calc(0 * 50px * 0), calc(-1 * 50px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.47, 0, 0.745, 0.715)', offset: 0.95, transform: - 'perspective(800px) translate3d(calc(0 * 1px * 1), calc(0 * 1px * 1), calc(-1 * 1px * 1)) rotateZ(var(--motion-rotate, 0deg))', + 'perspective(800px) translate3d(calc(0 * 50px * 0.02), calc(0 * 50px * 0.02), calc(-1 * 50px * 0.02)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'cubic-bezier(0.39, 0.575, 0.565, 1)', offset: 1, transform: - 'perspective(800px) translate3d(calc(0 * 1px * 0), calc(0 * 1px * 0), calc(-1 * 1px * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'perspective(800px) translate3d(calc(0 * 50px * 0), calc(0 * 50px * 0), calc(-1 * 50px * 0)) rotateZ(var(--motion-rotate, 0deg))', }, ], }, @@ -518,7 +518,7 @@ describe('BounceIn style method', () => { '--motion-direction-x': 0, '--motion-direction-y': 1, '--motion-direction-z': 0, - '--motion-distance-unit': '1px', + '--motion-distance': '50px', '--motion-perspective': ' ', '--motion-ease-in': 'cubic-bezier(0.39, 0.575, 0.565, 1)', '--motion-ease-out': 'cubic-bezier(0.47, 0, 0.745, 0.715)', @@ -528,61 +528,61 @@ describe('BounceIn style method', () => { animationTimingFunction: 'var(--motion-ease-out)', offset: 0, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 50), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 50), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 50)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 1), calc(var(--motion-direction-y) * var(--motion-distance) * 1), calc(var(--motion-direction-z) * var(--motion-distance) * 1)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.3, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.42, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 17.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 17.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 17.5)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0.35), calc(var(--motion-direction-y) * var(--motion-distance) * 0.35), calc(var(--motion-direction-z) * var(--motion-distance) * 0.35)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.54, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.62, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 10.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 10.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 10.5)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0.21), calc(var(--motion-direction-y) * var(--motion-distance) * 0.21), calc(var(--motion-direction-z) * var(--motion-distance) * 0.21)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.74, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.82, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 4.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 4.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 4.5)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0.09), calc(var(--motion-direction-y) * var(--motion-distance) * 0.09), calc(var(--motion-direction-z) * var(--motion-distance) * 0.09)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.9, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.95, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 1), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 1), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 1)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0.02), calc(var(--motion-direction-y) * var(--motion-distance) * 0.02), calc(var(--motion-direction-z) * var(--motion-distance) * 0.02)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 1, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, ], }, @@ -612,7 +612,7 @@ describe('BounceIn style method', () => { '--motion-direction-x': 0, '--motion-direction-y': 1, '--motion-direction-z': 0, - '--motion-distance-unit': '2px', + '--motion-distance': '100px', '--motion-perspective': ' ', '--motion-ease-in': 'cubic-bezier(0.39, 0.575, 0.565, 1)', '--motion-ease-out': 'cubic-bezier(0.47, 0, 0.745, 0.715)', @@ -622,61 +622,61 @@ describe('BounceIn style method', () => { animationTimingFunction: 'var(--motion-ease-out)', offset: 0, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 50), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 50), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 50)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 1), calc(var(--motion-direction-y) * var(--motion-distance) * 1), calc(var(--motion-direction-z) * var(--motion-distance) * 1)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.3, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.42, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 17.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 17.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 17.5)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0.35), calc(var(--motion-direction-y) * var(--motion-distance) * 0.35), calc(var(--motion-direction-z) * var(--motion-distance) * 0.35)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.54, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.62, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 10.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 10.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 10.5)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0.21), calc(var(--motion-direction-y) * var(--motion-distance) * 0.21), calc(var(--motion-direction-z) * var(--motion-distance) * 0.21)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.74, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.82, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 4.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 4.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 4.5)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0.09), calc(var(--motion-direction-y) * var(--motion-distance) * 0.09), calc(var(--motion-direction-z) * var(--motion-distance) * 0.09)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.9, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.95, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 1), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 1), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 1)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0.02), calc(var(--motion-direction-y) * var(--motion-distance) * 0.02), calc(var(--motion-direction-z) * var(--motion-distance) * 0.02)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 1, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, ], }, @@ -706,7 +706,7 @@ describe('BounceIn style method', () => { '--motion-direction-x': 0, '--motion-direction-y': -1, '--motion-direction-z': 0, - '--motion-distance-unit': '1px', + '--motion-distance': '50px', '--motion-perspective': ' ', '--motion-ease-in': 'cubic-bezier(0.39, 0.575, 0.565, 1)', '--motion-ease-out': 'cubic-bezier(0.47, 0, 0.745, 0.715)', @@ -716,61 +716,61 @@ describe('BounceIn style method', () => { animationTimingFunction: 'var(--motion-ease-out)', offset: 0, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 50), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 50), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 50)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 1), calc(var(--motion-direction-y) * var(--motion-distance) * 1), calc(var(--motion-direction-z) * var(--motion-distance) * 1)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.3, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.42, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 17.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 17.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 17.5)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0.35), calc(var(--motion-direction-y) * var(--motion-distance) * 0.35), calc(var(--motion-direction-z) * var(--motion-distance) * 0.35)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.54, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.62, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 10.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 10.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 10.5)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0.21), calc(var(--motion-direction-y) * var(--motion-distance) * 0.21), calc(var(--motion-direction-z) * var(--motion-distance) * 0.21)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.74, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.82, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 4.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 4.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 4.5)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0.09), calc(var(--motion-direction-y) * var(--motion-distance) * 0.09), calc(var(--motion-direction-z) * var(--motion-distance) * 0.09)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.9, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.95, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 1), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 1), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 1)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0.02), calc(var(--motion-direction-y) * var(--motion-distance) * 0.02), calc(var(--motion-direction-z) * var(--motion-distance) * 0.02)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 1, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, ], }, @@ -800,7 +800,7 @@ describe('BounceIn style method', () => { '--motion-direction-x': 1, '--motion-direction-y': 0, '--motion-direction-z': 0, - '--motion-distance-unit': '1px', + '--motion-distance': '50px', '--motion-perspective': ' ', '--motion-ease-in': 'cubic-bezier(0.39, 0.575, 0.565, 1)', '--motion-ease-out': 'cubic-bezier(0.47, 0, 0.745, 0.715)', @@ -810,61 +810,61 @@ describe('BounceIn style method', () => { animationTimingFunction: 'var(--motion-ease-out)', offset: 0, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 50), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 50), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 50)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 1), calc(var(--motion-direction-y) * var(--motion-distance) * 1), calc(var(--motion-direction-z) * var(--motion-distance) * 1)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.3, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.42, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 17.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 17.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 17.5)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0.35), calc(var(--motion-direction-y) * var(--motion-distance) * 0.35), calc(var(--motion-direction-z) * var(--motion-distance) * 0.35)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.54, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.62, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 10.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 10.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 10.5)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0.21), calc(var(--motion-direction-y) * var(--motion-distance) * 0.21), calc(var(--motion-direction-z) * var(--motion-distance) * 0.21)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.74, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.82, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 4.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 4.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 4.5)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0.09), calc(var(--motion-direction-y) * var(--motion-distance) * 0.09), calc(var(--motion-direction-z) * var(--motion-distance) * 0.09)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.9, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.95, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 1), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 1), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 1)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0.02), calc(var(--motion-direction-y) * var(--motion-distance) * 0.02), calc(var(--motion-direction-z) * var(--motion-distance) * 0.02)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 1, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, ], }, @@ -894,7 +894,7 @@ describe('BounceIn style method', () => { '--motion-direction-x': -1, '--motion-direction-y': 0, '--motion-direction-z': 0, - '--motion-distance-unit': '1px', + '--motion-distance': '50px', '--motion-perspective': ' ', '--motion-ease-in': 'cubic-bezier(0.39, 0.575, 0.565, 1)', '--motion-ease-out': 'cubic-bezier(0.47, 0, 0.745, 0.715)', @@ -904,61 +904,61 @@ describe('BounceIn style method', () => { animationTimingFunction: 'var(--motion-ease-out)', offset: 0, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 50), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 50), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 50)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 1), calc(var(--motion-direction-y) * var(--motion-distance) * 1), calc(var(--motion-direction-z) * var(--motion-distance) * 1)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.3, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.42, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 17.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 17.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 17.5)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0.35), calc(var(--motion-direction-y) * var(--motion-distance) * 0.35), calc(var(--motion-direction-z) * var(--motion-distance) * 0.35)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.54, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.62, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 10.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 10.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 10.5)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0.21), calc(var(--motion-direction-y) * var(--motion-distance) * 0.21), calc(var(--motion-direction-z) * var(--motion-distance) * 0.21)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.74, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.82, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 4.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 4.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 4.5)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0.09), calc(var(--motion-direction-y) * var(--motion-distance) * 0.09), calc(var(--motion-direction-z) * var(--motion-distance) * 0.09)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.9, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.95, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 1), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 1), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 1)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0.02), calc(var(--motion-direction-y) * var(--motion-distance) * 0.02), calc(var(--motion-direction-z) * var(--motion-distance) * 0.02)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 1, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, ], }, @@ -988,7 +988,7 @@ describe('BounceIn style method', () => { '--motion-direction-x': 0, '--motion-direction-y': 0, '--motion-direction-z': -1, - '--motion-distance-unit': '1px', + '--motion-distance': '50px', '--motion-perspective': 'perspective(800px)', '--motion-ease-in': 'cubic-bezier(0.39, 0.575, 0.565, 1)', '--motion-ease-out': 'cubic-bezier(0.47, 0, 0.745, 0.715)', @@ -998,61 +998,61 @@ describe('BounceIn style method', () => { animationTimingFunction: 'var(--motion-ease-out)', offset: 0, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 50), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 50), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 50)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 1), calc(var(--motion-direction-y) * var(--motion-distance) * 1), calc(var(--motion-direction-z) * var(--motion-distance) * 1)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.3, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.42, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 17.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 17.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 17.5)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0.35), calc(var(--motion-direction-y) * var(--motion-distance) * 0.35), calc(var(--motion-direction-z) * var(--motion-distance) * 0.35)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.54, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.62, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 10.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 10.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 10.5)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0.21), calc(var(--motion-direction-y) * var(--motion-distance) * 0.21), calc(var(--motion-direction-z) * var(--motion-distance) * 0.21)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.74, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.82, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 4.5), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 4.5), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 4.5)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0.09), calc(var(--motion-direction-y) * var(--motion-distance) * 0.09), calc(var(--motion-direction-z) * var(--motion-distance) * 0.09)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 0.9, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-out)', offset: 0.95, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 1), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 1), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 1)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0.02), calc(var(--motion-direction-y) * var(--motion-distance) * 0.02), calc(var(--motion-direction-z) * var(--motion-distance) * 0.02)) rotateZ(var(--motion-rotate, 0deg))', }, { animationTimingFunction: 'var(--motion-ease-in)', offset: 1, transform: - 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-y) * var(--motion-distance-unit) * 0), calc(var(--motion-direction-z) * var(--motion-distance-unit) * 0)) rotateZ(var(--motion-rotate, 0deg))', + 'var(--motion-perspective, ) translate3d(calc(var(--motion-direction-x) * var(--motion-distance) * 0), calc(var(--motion-direction-y) * var(--motion-distance) * 0), calc(var(--motion-direction-z) * var(--motion-distance) * 0)) rotateZ(var(--motion-rotate, 0deg))', }, ], }, @@ -1075,11 +1075,11 @@ describe('BounceIn style method', () => { expect(result[1].keyframes.slice(0, 3)).toMatchObject([ { - transform: ` translate3d(calc(0 * calc(0.02 * ${travel}) * 50), calc(1 * calc(0.02 * ${travel}) * 50), calc(0 * calc(0.02 * ${travel}) * 50)) rotateZ(var(--motion-rotate, 0deg))`, + transform: ` translate3d(calc(0 * ${travel} * 1), calc(1 * ${travel} * 1), calc(0 * ${travel} * 1)) rotateZ(var(--motion-rotate, 0deg))`, }, {}, { - transform: ` translate3d(calc(0 * calc(0.02 * ${travel}) * 17.5), calc(1 * calc(0.02 * ${travel}) * 17.5), calc(0 * calc(0.02 * ${travel}) * 17.5)) rotateZ(var(--motion-rotate, 0deg))`, + transform: ` translate3d(calc(0 * ${travel} * 0.35), calc(1 * ${travel} * 0.35), calc(0 * ${travel} * 0.35)) rotateZ(var(--motion-rotate, 0deg))`, }, ]); });