@wix/interact 2.1.4 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/react.js +1 -1
- package/dist/cjs/web.js +1 -1
- package/dist/cjs/web.js.map +1 -1
- package/dist/es/index.js +1 -1
- package/dist/es/react.js +2 -2
- package/dist/es/web.js +15 -15
- package/dist/es/web.js.map +1 -1
- package/dist/{index-DHqlFmW8.mjs → index-ByLXasWO.mjs} +491 -485
- package/dist/index-ByLXasWO.mjs.map +1 -0
- package/dist/index-CzRuJxn8.js +18 -0
- package/dist/index-CzRuJxn8.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/types/core/Interact.d.ts +2 -2
- package/dist/types/core/InteractionController.d.ts +2 -2
- package/dist/types/core/InteractionController.d.ts.map +1 -1
- package/dist/types/core/add.d.ts.map +1 -1
- package/dist/types/core/css.d.ts.map +1 -1
- package/dist/types/handlers/effectHandlers.d.ts +4 -4
- package/dist/types/handlers/effectHandlers.d.ts.map +1 -1
- package/dist/types/handlers/eventTrigger.d.ts +2 -2
- package/dist/types/handlers/eventTrigger.d.ts.map +1 -1
- package/dist/types/handlers/index.d.ts.map +1 -1
- package/dist/types/handlers/viewEnter.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/react/index.d.ts +1 -1
- package/dist/types/react/index.d.ts.map +1 -1
- package/dist/types/types/config.d.ts +47 -0
- package/dist/types/types/config.d.ts.map +1 -0
- package/dist/types/types/controller.d.ts +34 -0
- package/dist/types/types/controller.d.ts.map +1 -0
- package/dist/types/types/effects.d.ts +75 -0
- package/dist/types/types/effects.d.ts.map +1 -0
- package/dist/types/types/external.d.ts +6 -0
- package/dist/types/types/external.d.ts.map +1 -0
- package/dist/types/types/global.d.ts +11 -0
- package/dist/types/types/global.d.ts.map +1 -0
- package/dist/types/types/handlers.d.ts +41 -0
- package/dist/types/types/handlers.d.ts.map +1 -0
- package/dist/types/types/index.d.ts +8 -0
- package/dist/types/types/index.d.ts.map +1 -0
- package/dist/types/types/internal.d.ts +36 -0
- package/dist/types/types/internal.d.ts.map +1 -0
- package/dist/types/types/triggers.d.ts +28 -0
- package/dist/types/types/triggers.d.ts.map +1 -0
- package/dist/types/web/InteractElement.d.ts +2 -2
- package/dist/types/web/InteractElement.d.ts.map +1 -1
- package/dist/types/web/index.d.ts +1 -1
- package/dist/types/web/index.d.ts.map +1 -1
- package/docs/api/README.md +2 -3
- package/docs/api/functions.md +4 -4
- package/docs/api/interact-class.md +2 -3
- package/docs/api/interact-element.md +2 -2
- package/docs/api/interaction-controller.md +4 -4
- package/docs/api/types.md +38 -69
- package/docs/examples/README.md +1 -1
- package/docs/examples/click-interactions.md +0 -7
- package/docs/examples/entrance-animations.md +28 -27
- package/docs/examples/list-patterns.md +17 -16
- package/docs/guides/conditions-and-media-queries.md +2 -3
- package/docs/guides/configuration-structure.md +5 -7
- package/docs/guides/effects-and-animations.md +2 -4
- package/docs/guides/getting-started.md +0 -1
- package/docs/guides/lists-and-dynamic-content.md +10 -9
- package/docs/guides/sequences.md +3 -4
- package/docs/guides/state-management.md +0 -2
- package/docs/guides/understanding-triggers.md +9 -13
- package/package.json +2 -2
- package/rules/click.md +96 -560
- package/rules/full-lean.md +536 -360
- package/rules/hover.md +107 -530
- package/rules/integration.md +212 -261
- package/rules/pointermove.md +154 -1407
- package/rules/viewenter.md +128 -863
- package/rules/viewprogress.md +88 -322
- package/dist/index-DHqlFmW8.mjs.map +0 -1
- package/dist/index-DYEvpIGz.js +0 -18
- package/dist/index-DYEvpIGz.js.map +0 -1
- package/dist/types/types.d.ts +0 -256
- package/dist/types/types.d.ts.map +0 -1
- package/rules/MASTER-CLEANUP-PLAN.md +0 -286
- package/rules/scroll-list.md +0 -748
package/rules/viewenter.md
CHANGED
|
@@ -1,959 +1,224 @@
|
|
|
1
1
|
# ViewEnter Trigger Rules for @wix/interact
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This document contains rules for generating interactions that respond to elements entering the viewport using the `@wix/interact`. ViewEnter triggers use IntersectionObserver to detect when elements become visible and are ideal for entrance animations, content reveals, and lazy-loading effects.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
**Use Case**: One-time entrance animations that play when elements first become visible (e.g., hero sections, content blocks, images, cards)
|
|
8
|
-
|
|
9
|
-
**When to Apply**:
|
|
10
|
-
|
|
11
|
-
- For entrance animations that should only happen once
|
|
12
|
-
- When you want elements to stay in their final animated state
|
|
13
|
-
- For progressive content reveal as user scrolls
|
|
14
|
-
- When implementing lazy-loading visual effects
|
|
15
|
-
|
|
16
|
-
**Pattern**:
|
|
17
|
-
|
|
18
|
-
```typescript
|
|
19
|
-
{
|
|
20
|
-
key: '[SOURCE_KEY]',
|
|
21
|
-
trigger: 'viewEnter',
|
|
22
|
-
params: {
|
|
23
|
-
type: 'once',
|
|
24
|
-
threshold: [VISIBILITY_THRESHOLD],
|
|
25
|
-
inset: '[VIEWPORT_INSETS]'
|
|
26
|
-
},
|
|
27
|
-
effects: [
|
|
28
|
-
{
|
|
29
|
-
key: '[TARGET_KEY]',
|
|
30
|
-
[EFFECT_TYPE]: [EFFECT_DEFINITION],
|
|
31
|
-
duration: [DURATION_MS],
|
|
32
|
-
easing: '[EASING_FUNCTION]',
|
|
33
|
-
delay: [DELAY_MS],
|
|
34
|
-
effectId: '[UNIQUE_EFFECT_ID]'
|
|
35
|
-
}
|
|
36
|
-
]
|
|
37
|
-
}
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
**Variables**:
|
|
41
|
-
|
|
42
|
-
- `[SOURCE_KEY]`: Unique identifier for element that triggers when visible (often same as target key)
|
|
43
|
-
- `[TARGET_KEY]`: Unique identifier for element to animate (can be same as source or different)
|
|
44
|
-
- `[VISIBILITY_THRESHOLD]`: Number between 0-1 indicating how much of element must be visible (e.g., 0.3 = 30%)
|
|
45
|
-
- `[VIEWPORT_INSETS]`: String insets around viewport (e.g., '50px', '10%', '-100px')
|
|
46
|
-
- `[EFFECT_TYPE]`: Either `namedEffect` or `keyframeEffect`
|
|
47
|
-
- `[EFFECT_DEFINITION]`: Named effect string (e.g., 'FadeIn', 'SlideIn') or keyframe object
|
|
48
|
-
- `[DURATION_MS]`: Animation duration in milliseconds (typically 500-1200ms for entrances)
|
|
49
|
-
- `[EASING_FUNCTION]`: Timing function (recommended: 'ease-out', 'cubic-bezier(0.16, 1, 0.3, 1)')
|
|
50
|
-
- `[DELAY_MS]`: Optional delay before animation starts
|
|
51
|
-
- `[UNIQUE_EFFECT_ID]`: Optional unique identifier for animation chaining
|
|
5
|
+
---
|
|
52
6
|
|
|
53
|
-
**
|
|
7
|
+
> **CRITICAL:** When the source (trigger) and target (effect) elements are the **same element**, use ONLY `triggerType: 'once'`. For all other types (`'repeat'`, `'alternate'`, `'state'`), MUST use **separate** source and target elements — animating the observed element itself can cause it to leave/re-enter the viewport, leading to rapid re-triggers or the animation never firing.
|
|
54
8
|
|
|
55
|
-
|
|
56
|
-
{
|
|
57
|
-
key: 'hero-section',
|
|
58
|
-
trigger: 'viewEnter',
|
|
59
|
-
params: {
|
|
60
|
-
type: 'once',
|
|
61
|
-
threshold: 0.3,
|
|
62
|
-
inset: '-100px'
|
|
63
|
-
},
|
|
64
|
-
effects: [
|
|
65
|
-
{
|
|
66
|
-
key: 'hero-section',
|
|
67
|
-
keyframeEffect: {
|
|
68
|
-
name: 'hero-entrance',
|
|
69
|
-
keyframes: [
|
|
70
|
-
{ opacity: '0', transform: 'translateY(60px) scale(0.95)' },
|
|
71
|
-
{ opacity: '1', transform: 'translateY(0) scale(1)' }
|
|
72
|
-
]
|
|
73
|
-
},
|
|
74
|
-
duration: 1000,
|
|
75
|
-
easing: 'cubic-bezier(0.16, 1, 0.3, 1)',
|
|
76
|
-
fill: 'backwards',
|
|
77
|
-
effectId: 'hero-entrance'
|
|
78
|
-
}
|
|
79
|
-
]
|
|
80
|
-
}
|
|
81
|
-
```
|
|
9
|
+
## Table of Contents
|
|
82
10
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
key: 'content-block',
|
|
88
|
-
trigger: 'viewEnter',
|
|
89
|
-
params: {
|
|
90
|
-
type: 'once',
|
|
91
|
-
threshold: 0.5
|
|
92
|
-
},
|
|
93
|
-
effects: [
|
|
94
|
-
{
|
|
95
|
-
key: 'content-block',
|
|
96
|
-
namedEffect: {
|
|
97
|
-
type: 'FadeIn'
|
|
98
|
-
},
|
|
99
|
-
duration: 800,
|
|
100
|
-
easing: 'ease-out',
|
|
101
|
-
fill: 'backwards'
|
|
102
|
-
}
|
|
103
|
-
]
|
|
104
|
-
}
|
|
105
|
-
```
|
|
11
|
+
- [Preventing Flash of Unstyled Content (FOUC)](#preventing-flash-of-unstyled-content-fouc)
|
|
12
|
+
- [Rule 1: keyframeEffect / namedEffect (TimeEffect)](#rule-1-keyframeeffect--namedeffect-timeeffect)
|
|
13
|
+
- [Rule 2: customEffect (TimeEffect)](#rule-2-customeffect-timeeffect)
|
|
14
|
+
- [Rule 3: Sequences](#rule-3-sequences)
|
|
106
15
|
|
|
107
16
|
---
|
|
108
17
|
|
|
109
|
-
##
|
|
110
|
-
|
|
111
|
-
**Use Case**: Animations that retrigger every time elements enter the viewport, often with separate trigger and target elements (e.g., scroll-triggered counters, image reveals, interactive sections)
|
|
112
|
-
|
|
113
|
-
**When to Apply**:
|
|
18
|
+
## Preventing Flash of Unstyled Content (FOUC)
|
|
114
19
|
|
|
115
|
-
|
|
116
|
-
- For scroll-triggered interactive elements
|
|
117
|
-
- When using separate observer and animation targets
|
|
118
|
-
- For elements that might leave and re-enter viewport
|
|
20
|
+
**Problem:** Elements with entrance animations (e.g. `FadeIn`) start in their final visible state (e.g. `opacity: 1`). Before the animation framework initializes and applies the starting keyframe (e.g. `opacity: 0`), the element is briefly visible at full opacity — a flash of un-animated content.
|
|
119
21
|
|
|
120
|
-
**
|
|
22
|
+
**Solution:** Two things are required — **both** MUST be present for FOUC prevention to work:
|
|
121
23
|
|
|
122
|
-
|
|
123
|
-
{
|
|
124
|
-
key: '[OBSERVER_KEY]',
|
|
125
|
-
trigger: 'viewEnter',
|
|
126
|
-
params: {
|
|
127
|
-
type: 'repeat',
|
|
128
|
-
threshold: [VISIBILITY_THRESHOLD],
|
|
129
|
-
inset: '[VIEWPORT_INSETS]'
|
|
130
|
-
},
|
|
131
|
-
effects: [
|
|
132
|
-
{
|
|
133
|
-
key: '[ANIMATION_TARGET_KEY]',
|
|
134
|
-
[EFFECT_TYPE]: [EFFECT_DEFINITION],
|
|
135
|
-
duration: [DURATION_MS],
|
|
136
|
-
easing: '[EASING_FUNCTION]',
|
|
137
|
-
delay: [DELAY_MS],
|
|
138
|
-
effectId: '[UNIQUE_EFFECT_ID]'
|
|
139
|
-
}
|
|
140
|
-
]
|
|
141
|
-
}
|
|
142
|
-
```
|
|
24
|
+
1. **Generate critical CSS** using `generate(config)` — produces CSS rules that hide entrance-animated elements from the moment the page renders, before JavaScript runs.
|
|
25
|
+
2. **Mark elements with `initial`** — set `data-interact-initial="true"` on `<interact-element>`, or `initial={true}` on the `<Interaction>` React component. This tells the runtime which elements have critical CSS applied.
|
|
143
26
|
|
|
144
|
-
**
|
|
27
|
+
If only one of these is present, FOUC prevention will **not** work. Both the CSS and the `initial` attribute are required.
|
|
145
28
|
|
|
146
|
-
|
|
147
|
-
- `[ANIMATION_TARGET_KEY]`: Unique identifier for element that gets animated (different from observer)
|
|
148
|
-
- Other variables same as Rule 1
|
|
29
|
+
### Step 1: Generate CSS and inject into `<head>` (preferred), or beginning of `<body>`
|
|
149
30
|
|
|
150
|
-
|
|
31
|
+
Call `generate(config)` server-side or at build time. Inject the resulting CSS into the document `<head>` (or in `<body>` before your content) so it loads before the page content is painted:
|
|
151
32
|
|
|
152
33
|
```typescript
|
|
153
|
-
{
|
|
154
|
-
key: 'image-trigger-zone',
|
|
155
|
-
trigger: 'viewEnter',
|
|
156
|
-
params: {
|
|
157
|
-
type: 'repeat',
|
|
158
|
-
threshold: 0.1,
|
|
159
|
-
inset: '-50px'
|
|
160
|
-
},
|
|
161
|
-
effects: [
|
|
162
|
-
{
|
|
163
|
-
key: 'background-image',
|
|
164
|
-
keyframeEffect: {
|
|
165
|
-
name: 'image-reveal',
|
|
166
|
-
keyframes: [
|
|
167
|
-
{ filter: 'blur(20px) brightness(0.7)', transform: 'scale(1.1)' },
|
|
168
|
-
{ filter: 'blur(0) brightness(1)', transform: 'scale(1)' }
|
|
169
|
-
]
|
|
170
|
-
},
|
|
171
|
-
duration: 600,
|
|
172
|
-
easing: 'ease-out',
|
|
173
|
-
fill: 'backwards'
|
|
174
|
-
}
|
|
175
|
-
]
|
|
176
|
-
}
|
|
177
|
-
```
|
|
34
|
+
import { generate } from '@wix/interact';
|
|
178
35
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
36
|
+
const config: InteractConfig = {
|
|
37
|
+
interactions: [
|
|
38
|
+
{
|
|
39
|
+
key: '[SOURCE_KEY]',
|
|
40
|
+
trigger: 'viewEnter',
|
|
41
|
+
params: {
|
|
42
|
+
threshold: [VIEW_TRIGGER_THRESHOLD],
|
|
43
|
+
inset: [VIEW_TRIGGER_INSET],
|
|
44
|
+
},
|
|
45
|
+
effects: [EFFECT_DEFINITIONS],
|
|
46
|
+
// and/or
|
|
47
|
+
sequences: [SEQUENCE_DEFINITIONS],
|
|
188
48
|
},
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
key: 'counter-display',
|
|
192
|
-
customEffect: (element, progress) => {
|
|
193
|
-
const targetValue = 1000;
|
|
194
|
-
const currentValue = Math.floor(targetValue * progress);
|
|
195
|
-
element.textContent = currentValue.toLocaleString();
|
|
196
|
-
},
|
|
197
|
-
duration: 2000,
|
|
198
|
-
easing: 'ease-out',
|
|
199
|
-
effectId: 'counter-animation'
|
|
200
|
-
}
|
|
201
|
-
]
|
|
202
|
-
}
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
---
|
|
49
|
+
],
|
|
50
|
+
};
|
|
206
51
|
|
|
207
|
-
|
|
52
|
+
const css = generate(config);
|
|
53
|
+
```
|
|
208
54
|
|
|
209
|
-
**
|
|
55
|
+
**Append to `<head>` or beginning of `<body>`:**
|
|
210
56
|
|
|
211
|
-
|
|
57
|
+
```html
|
|
58
|
+
<style>
|
|
59
|
+
${css}
|
|
60
|
+
</style>
|
|
61
|
+
```
|
|
212
62
|
|
|
213
|
-
|
|
214
|
-
- When creating scroll-responsive reveals
|
|
215
|
-
- For elements that animate in and out smoothly
|
|
216
|
-
- When observer element is different from animated element
|
|
63
|
+
### Step 2: Mark elements with `initial`
|
|
217
64
|
|
|
218
|
-
**
|
|
65
|
+
**Web (Custom Elements):**
|
|
219
66
|
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
params: {
|
|
225
|
-
type: 'alternate',
|
|
226
|
-
threshold: [VISIBILITY_THRESHOLD],
|
|
227
|
-
inset: '[VIEWPORT_INSETS]'
|
|
228
|
-
},
|
|
229
|
-
effects: [
|
|
230
|
-
{
|
|
231
|
-
key: '[ANIMATION_TARGET_KEY]',
|
|
232
|
-
[EFFECT_TYPE]: [EFFECT_DEFINITION],
|
|
233
|
-
duration: [DURATION_MS],
|
|
234
|
-
easing: '[EASING_FUNCTION]',
|
|
235
|
-
effectId: '[UNIQUE_EFFECT_ID]'
|
|
236
|
-
}
|
|
237
|
-
]
|
|
238
|
-
}
|
|
67
|
+
```html
|
|
68
|
+
<interact-element data-interact-key="[SOURCE_KEY]" data-interact-initial="true">
|
|
69
|
+
<section>...</section>
|
|
70
|
+
</interact-element>
|
|
239
71
|
```
|
|
240
72
|
|
|
241
|
-
**
|
|
242
|
-
Same as Rule 2
|
|
73
|
+
**React:**
|
|
243
74
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
key: 'content-trigger',
|
|
249
|
-
trigger: 'viewEnter',
|
|
250
|
-
params: {
|
|
251
|
-
type: 'alternate',
|
|
252
|
-
threshold: 0.3,
|
|
253
|
-
inset: '-20px'
|
|
254
|
-
},
|
|
255
|
-
effects: [
|
|
256
|
-
{
|
|
257
|
-
key: 'sidebar-content',
|
|
258
|
-
keyframeEffect: {
|
|
259
|
-
name: 'content-reveal-hide',
|
|
260
|
-
keyframes: [
|
|
261
|
-
{ opacity: '0', transform: 'translateX(-50px)' },
|
|
262
|
-
{ opacity: '1', transform: 'translateX(0)' }
|
|
263
|
-
]
|
|
264
|
-
},
|
|
265
|
-
duration: 400,
|
|
266
|
-
easing: 'ease-in-out',
|
|
267
|
-
fill: 'backwards'
|
|
268
|
-
}
|
|
269
|
-
]
|
|
270
|
-
}
|
|
75
|
+
```tsx
|
|
76
|
+
<Interaction tagName="section" interactKey="[SOURCE_KEY]" initial={true}>
|
|
77
|
+
...
|
|
78
|
+
</Interaction>
|
|
271
79
|
```
|
|
272
80
|
|
|
273
|
-
**
|
|
81
|
+
**Vanilla:**
|
|
274
82
|
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
key: 'page-content',
|
|
278
|
-
trigger: 'viewEnter',
|
|
279
|
-
params: {
|
|
280
|
-
type: 'alternate',
|
|
281
|
-
threshold: 0.1
|
|
282
|
-
},
|
|
283
|
-
effects: [
|
|
284
|
-
{
|
|
285
|
-
key: 'floating-nav',
|
|
286
|
-
keyframeEffect: {
|
|
287
|
-
name: 'nav-reveal',
|
|
288
|
-
keyframes: [
|
|
289
|
-
{ opacity: '0', transform: 'translateY(-100%)' },
|
|
290
|
-
{ opacity: '1', transform: 'translateY(0)' }
|
|
291
|
-
]
|
|
292
|
-
},
|
|
293
|
-
duration: 300,
|
|
294
|
-
easing: 'ease-out',
|
|
295
|
-
fill: 'backwards',
|
|
296
|
-
effectId: 'nav-reveal'
|
|
297
|
-
}
|
|
298
|
-
]
|
|
299
|
-
}
|
|
83
|
+
```html
|
|
84
|
+
<section data-interact-key="[SOURCE_KEY]" data-interact-initial="true">...</section>
|
|
300
85
|
```
|
|
301
86
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
## Rule 4: ViewEnter with State Type for Loop Animations
|
|
87
|
+
### Rules
|
|
305
88
|
|
|
306
|
-
|
|
89
|
+
- `generate()` should be called server-side or at build time. Can also be called on the client if the page content is initially hidden (e.g. behind a loader/splash screen).
|
|
90
|
+
- `initial` is only valid for `viewEnter` + `triggerType: 'once'` (or no `triggerType`, which defaults to `'once'`) where source and target are the same element.
|
|
91
|
+
- Do NOT use `initial` for `viewEnter` with `triggerType: 'repeat'`/`'alternate'`/`'state'`. For those, manually apply the initial keyframe as inline styles on the target element and use `fill: 'both'`.
|
|
92
|
+
- If other interactions in the config also need FOUC prevention, `generate(config)` covers them all — set `initial` only on the relevant `viewEnter` + `triggerType: 'once'` elements.
|
|
307
93
|
|
|
308
|
-
|
|
94
|
+
## Rule 1: keyframeEffect / namedEffect (TimeEffect)
|
|
309
95
|
|
|
310
|
-
|
|
311
|
-
- When you need pause/resume control over scroll-triggered loops
|
|
312
|
-
- For ambient or decorative animations
|
|
313
|
-
- When creating scroll-activated background effects
|
|
96
|
+
Use `keyframeEffect` or `namedEffect` when the viewEnter should play an animation (CSS or WAAPI). Set `triggerType` on each effect to control playback behavior. Use `params` only for observer configuration (`threshold`, `inset`).
|
|
314
97
|
|
|
315
|
-
**
|
|
98
|
+
**Multiple effects:** The `effects` array can contain multiple effects — all share the same viewEnter trigger and fire together when the element enters the viewport. Each effect can have its own `triggerType`. Use this to animate different targets from a single viewport entry event.
|
|
316
99
|
|
|
317
100
|
```typescript
|
|
318
101
|
{
|
|
319
102
|
key: '[SOURCE_KEY]',
|
|
320
103
|
trigger: 'viewEnter',
|
|
321
104
|
params: {
|
|
322
|
-
type: 'state',
|
|
323
105
|
threshold: [VISIBILITY_THRESHOLD],
|
|
324
106
|
inset: '[VIEWPORT_INSETS]'
|
|
325
107
|
},
|
|
326
108
|
effects: [
|
|
327
109
|
{
|
|
328
110
|
key: '[TARGET_KEY]',
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
easing: '[EASING_FUNCTION]',
|
|
332
|
-
iterations: [ITERATION_COUNT],
|
|
333
|
-
alternate: [ALTERNATE_BOOLEAN],
|
|
334
|
-
effectId: '[UNIQUE_EFFECT_ID]'
|
|
335
|
-
}
|
|
336
|
-
]
|
|
337
|
-
}
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
**Variables**:
|
|
111
|
+
selector: '[TARGET_SELECTOR]',
|
|
112
|
+
triggerType: '[TRIGGER_TYPE]',
|
|
341
113
|
|
|
342
|
-
|
|
343
|
-
- `[ALTERNATE_BOOLEAN]`: true/false - whether to reverse on alternate iterations
|
|
344
|
-
- Other variables same as Rule 1
|
|
345
|
-
|
|
346
|
-
**Example - Floating Animation Loop**:
|
|
347
|
-
|
|
348
|
-
```typescript
|
|
349
|
-
{
|
|
350
|
-
key: 'floating-elements',
|
|
351
|
-
trigger: 'viewEnter',
|
|
352
|
-
params: {
|
|
353
|
-
type: 'state',
|
|
354
|
-
threshold: 0.4
|
|
355
|
-
},
|
|
356
|
-
effects: [
|
|
357
|
-
{
|
|
358
|
-
key: 'floating-icon',
|
|
114
|
+
// --- pick ONE of the two effect types ---
|
|
359
115
|
keyframeEffect: {
|
|
360
|
-
name: '
|
|
361
|
-
keyframes: [
|
|
362
|
-
{ transform: 'translateY(0)' },
|
|
363
|
-
{ transform: 'translateY(-20px)' },
|
|
364
|
-
{ transform: 'translateY(0)' }
|
|
365
|
-
]
|
|
116
|
+
name: '[EFFECT_NAME]',
|
|
117
|
+
keyframes: [KEYFRAMES],
|
|
366
118
|
},
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
iterations: Infinity,
|
|
370
|
-
alternate: false,
|
|
371
|
-
effectId: 'floating-loop'
|
|
372
|
-
}
|
|
373
|
-
]
|
|
374
|
-
}
|
|
375
|
-
```
|
|
119
|
+
// OR
|
|
120
|
+
namedEffect: [NAMED_EFFECT_DEFINITION],
|
|
376
121
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
122
|
+
fill: '[FILL_MODE]',
|
|
123
|
+
duration: [DURATION_MS],
|
|
124
|
+
easing: '[EASING_FUNCTION]',
|
|
125
|
+
delay: [DELAY_MS],
|
|
126
|
+
iterations: [ITERATIONS],
|
|
127
|
+
alternate: [ALTERNATE_BOOL],
|
|
128
|
+
effectId: '[UNIQUE_EFFECT_ID]'
|
|
129
|
+
},
|
|
130
|
+
// additional effects targeting other elements can be added here
|
|
131
|
+
]
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Variables
|
|
136
|
+
|
|
137
|
+
- `[SOURCE_KEY]` — identifier matching the element's key (`data-interact-key` for web/vanilla, `interactKey` for React). The **source element** is observed for viewport intersection. This is the element the IntersectionObserver watches.
|
|
138
|
+
- `[TARGET_KEY]` — identifier matching the element's key on the element that animates.
|
|
139
|
+
- `[TARGET_SELECTOR]` - optional. Selector for the child element to select inside the root element. For `triggerType` of `'alternate'`/`'repeat'`/`'state'` MUST either use a separate `[TARGET_KEY]` from `[SOURCE_KEY]` or `selector` for selecting a child element as target.
|
|
140
|
+
- `[TRIGGER_TYPE]` — `triggerType` on the effect. One of:
|
|
141
|
+
- `'once'` (default) — plays once when the source element first enters the viewport and never again. Source and target may be the same element.
|
|
142
|
+
- `'repeat'` — restarts the animation every time the source element enters the viewport. Use separate source and target.
|
|
143
|
+
- `'alternate'` — plays forward when the source element enters the viewport, reverses when it leaves. Use separate source and target.
|
|
144
|
+
- `'state'` — resumes on enter, pauses on leave. Useful for continuous loops (`iterations: Infinity`). Use separate source and target.
|
|
145
|
+
- `[VISIBILITY_THRESHOLD]` — optional. Number between 0–1 indicating how much of the source element must be visible to trigger (e.g. `0.3` = 30%).
|
|
146
|
+
- `[VIEWPORT_INSETS]` — optional. String adjusting the viewport detection area (e.g. `'-100px'` extends it, `'50px'` shrinks it).
|
|
147
|
+
- `[KEYFRAMES]` — array of keyframe objects (e.g. `[{ opacity: 0 }, { opacity: 1 }]`). Property names in camelCase.
|
|
148
|
+
- `[EFFECT_NAME]` — unique string identifier for a `keyframeEffect`.
|
|
149
|
+
- `[NAMED_EFFECT_DEFINITION]` — object with properties of pre-built effect from `@wix/motion-presets`. Refer to motion-presets rules for available presets and their options.
|
|
150
|
+
- `[FILL_MODE]` — `'both'` for `triggerType: 'alternate'`, `'repeat'`, or `'state'`. For `triggerType: 'once'`: use `'backwards'` when the animation's final keyframe has no additional effect (over element's base style); use `'both'` otherwise.
|
|
151
|
+
- `[DURATION_MS]` — animation duration in milliseconds.
|
|
152
|
+
- `[EASING_FUNCTION]` — CSS easing string or named easing from `@wix/motion`.
|
|
153
|
+
- `[DELAY_MS]` — optional delay before the effect starts, in milliseconds.
|
|
154
|
+
- `[ITERATIONS]` — optional. Number of iterations, or `Infinity` for continuous loops. Primarily useful with `triggerType: 'state'`.
|
|
155
|
+
- `[ALTERNATE_BOOL]` — optional. `true` to alternate direction on every other iteration (within a single playback).
|
|
156
|
+
- `[UNIQUE_EFFECT_ID]` — optional. String identifier used by `animationEnd` triggers for chaining, and by sequences for referencing effects.
|
|
402
157
|
|
|
403
158
|
---
|
|
404
159
|
|
|
405
|
-
## Rule
|
|
406
|
-
|
|
407
|
-
**Use Case**: Fine-tuning when animations trigger based on element visibility and viewport positioning (e.g., early triggers, late triggers, precise timing)
|
|
160
|
+
## Rule 2: customEffect (TimeEffect)
|
|
408
161
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
- When default triggering timing isn't optimal
|
|
412
|
-
- For elements that need early or late animation triggers
|
|
413
|
-
- When working with very tall or very short elements
|
|
414
|
-
- For precise scroll timing control
|
|
415
|
-
|
|
416
|
-
**Pattern**:
|
|
162
|
+
Use `customEffect` when you need imperative control over the animation (e.g. counters, canvas drawing, custom DOM manipulation). The callback receives the target element and a `progress` value (0–1) driven by the animation timeline.
|
|
417
163
|
|
|
418
164
|
```typescript
|
|
419
165
|
{
|
|
420
166
|
key: '[SOURCE_KEY]',
|
|
421
167
|
trigger: 'viewEnter',
|
|
422
168
|
params: {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
inset: '[VIEWPORT_ADJUSTMENT]'
|
|
169
|
+
threshold: [VISIBILITY_THRESHOLD],
|
|
170
|
+
inset: '[VIEWPORT_INSETS]'
|
|
426
171
|
},
|
|
427
172
|
effects: [
|
|
428
173
|
{
|
|
429
174
|
key: '[TARGET_KEY]',
|
|
430
|
-
|
|
175
|
+
triggerType: '[TRIGGER_TYPE]',
|
|
176
|
+
customEffect: [CUSTOM_EFFECT_CALLBACK],
|
|
431
177
|
duration: [DURATION_MS],
|
|
432
|
-
easing: '[EASING_FUNCTION]'
|
|
433
|
-
|
|
434
|
-
]
|
|
435
|
-
}
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
**Variables**:
|
|
439
|
-
|
|
440
|
-
- `[PRECISE_THRESHOLD]`: Decimal between 0-1 for exact visibility percentage
|
|
441
|
-
- `[VIEWPORT_ADJUSTMENT]`: Pixel or percentage adjustment to viewport detection area
|
|
442
|
-
- `[BEHAVIOR_TYPE]`: 'once', 'repeat', 'alternate', or 'state'
|
|
443
|
-
- Other variables same as Rule 1
|
|
444
|
-
|
|
445
|
-
**Example - Early Trigger for Tall Elements**:
|
|
446
|
-
|
|
447
|
-
```typescript
|
|
448
|
-
{
|
|
449
|
-
key: 'tall-hero-section',
|
|
450
|
-
trigger: 'viewEnter',
|
|
451
|
-
params: {
|
|
452
|
-
type: 'once',
|
|
453
|
-
threshold: 0.1, // Trigger when only 10% visible
|
|
454
|
-
inset: '-200px' // Extend detection area 200px beyond viewport
|
|
455
|
-
},
|
|
456
|
-
effects: [
|
|
457
|
-
{
|
|
458
|
-
key: 'tall-hero-section',
|
|
459
|
-
namedEffect: {
|
|
460
|
-
type: 'SlideIn'
|
|
461
|
-
},
|
|
462
|
-
duration: 1200,
|
|
463
|
-
easing: 'cubic-bezier(0.16, 1, 0.3, 1)'
|
|
464
|
-
}
|
|
465
|
-
]
|
|
466
|
-
}
|
|
467
|
-
```
|
|
468
|
-
|
|
469
|
-
**Example - Late Trigger for Precise Timing**:
|
|
470
|
-
|
|
471
|
-
```typescript
|
|
472
|
-
{
|
|
473
|
-
key: 'precision-content',
|
|
474
|
-
trigger: 'viewEnter',
|
|
475
|
-
params: {
|
|
476
|
-
type: 'once',
|
|
477
|
-
threshold: 0.8, // Wait until 80% visible
|
|
478
|
-
inset: '50px' // Shrink detection area by 50px
|
|
479
|
-
},
|
|
480
|
-
effects: [
|
|
481
|
-
{
|
|
482
|
-
key: 'precision-content',
|
|
483
|
-
keyframeEffect: {
|
|
484
|
-
name: 'blur',
|
|
485
|
-
keyframes: [
|
|
486
|
-
{ opacity: '0', filter: 'blur(5px)' },
|
|
487
|
-
{ opacity: '1', filter: 'blur(0)' }
|
|
488
|
-
]
|
|
489
|
-
},
|
|
490
|
-
duration: 600,
|
|
491
|
-
easing: 'ease-out',
|
|
492
|
-
fill: 'backwards'
|
|
178
|
+
easing: '[EASING_FUNCTION]',
|
|
179
|
+
effectId: '[UNIQUE_EFFECT_ID]'
|
|
493
180
|
}
|
|
494
181
|
]
|
|
495
182
|
}
|
|
496
183
|
```
|
|
497
184
|
|
|
498
|
-
|
|
185
|
+
### Variables
|
|
499
186
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
conditions: {
|
|
503
|
-
// Condition IDs are user-defined strings matched against these media predicates
|
|
504
|
-
'desktop-only': { type: 'media', predicate: '(min-width: 768px)' },
|
|
505
|
-
},
|
|
506
|
-
interactions: [
|
|
507
|
-
{
|
|
508
|
-
key: 'responsive-element',
|
|
509
|
-
trigger: 'viewEnter',
|
|
510
|
-
params: {
|
|
511
|
-
type: 'once',
|
|
512
|
-
threshold: 0.3,
|
|
513
|
-
inset: '-100px'
|
|
514
|
-
},
|
|
515
|
-
conditions: ['desktop-only'],
|
|
516
|
-
effects: [
|
|
517
|
-
{
|
|
518
|
-
key: 'responsive-element',
|
|
519
|
-
namedEffect: { type: 'FadeIn' },
|
|
520
|
-
duration: 800
|
|
521
|
-
}
|
|
522
|
-
]
|
|
523
|
-
}
|
|
524
|
-
]
|
|
525
|
-
}
|
|
526
|
-
```
|
|
187
|
+
- `[SOURCE_KEY]` / `[TARGET_KEY]` / `[TRIGGER_TYPE]` / `[VISIBILITY_THRESHOLD]` / `[VIEWPORT_INSETS]` / `[DURATION_MS]` / `[EASING_FUNCTION]` / `[UNIQUE_EFFECT_ID]` — same as Rule 1.
|
|
188
|
+
- `[CUSTOM_EFFECT_CALLBACK]` — function with signature `(element: HTMLElement, progress: number) => void`. Called on each animation frame with `element` being the target element, and `progress` from 0 to 1.
|
|
527
189
|
|
|
528
190
|
---
|
|
529
191
|
|
|
530
|
-
## Rule
|
|
192
|
+
## Rule 3: Sequences
|
|
531
193
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
**When to Apply**:
|
|
535
|
-
|
|
536
|
-
- When multiple elements should animate in sequence
|
|
537
|
-
- For creating wave or cascade effects
|
|
538
|
-
- When animating lists, grids, or collections
|
|
539
|
-
- For progressive content revelation
|
|
540
|
-
|
|
541
|
-
**Preferred approach: use `sequences`** on the interaction instead of manually setting `delay` on individual effects. Sequences automatically calculate stagger delays using `offset` and `offsetEasing`.
|
|
542
|
-
|
|
543
|
-
**Pattern (with `listContainer`)**:
|
|
194
|
+
Use sequences when a viewEnter should sync/stagger animations across multiple elements. Set `triggerType` on the sequence config to control playback behavior.
|
|
544
195
|
|
|
545
196
|
```typescript
|
|
546
197
|
{
|
|
547
|
-
key: '[
|
|
198
|
+
key: '[SOURCE_KEY]',
|
|
548
199
|
trigger: 'viewEnter',
|
|
549
200
|
params: {
|
|
550
|
-
|
|
551
|
-
|
|
201
|
+
threshold: [VISIBILITY_THRESHOLD],
|
|
202
|
+
inset: '[VIEWPORT_INSETS]'
|
|
552
203
|
},
|
|
553
204
|
sequences: [
|
|
554
205
|
{
|
|
206
|
+
triggerType: '[TRIGGER_TYPE]',
|
|
555
207
|
offset: [OFFSET_MS],
|
|
556
208
|
offsetEasing: '[OFFSET_EASING]',
|
|
557
209
|
effects: [
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
listContainer: '[LIST_CONTAINER_SELECTOR]'
|
|
561
|
-
}
|
|
210
|
+
[EFFECT_DEFINTION],
|
|
211
|
+
// .. more effects as necessary
|
|
562
212
|
]
|
|
563
213
|
}
|
|
564
214
|
]
|
|
565
215
|
}
|
|
566
216
|
```
|
|
567
217
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
- `[CONTAINER_KEY]`: Unique identifier for the container element
|
|
571
|
-
- `[OFFSET_MS]`: Stagger offset in ms between consecutive items (e.g., 80, 100, 120)
|
|
572
|
-
- `[OFFSET_EASING]`: How the offset is distributed — `'linear'` (equal spacing), `'quadIn'` (accelerating), `'sineOut'` (decelerating), etc.
|
|
573
|
-
- `[LIST_CONTAINER_SELECTOR]`: CSS selector for the list container whose children become sequence items
|
|
574
|
-
- Other variables same as Rule 1
|
|
575
|
-
|
|
576
|
-
**Example - Card Grid Stagger (listContainer)**:
|
|
577
|
-
|
|
578
|
-
```typescript
|
|
579
|
-
{
|
|
580
|
-
key: 'card-grid-container',
|
|
581
|
-
trigger: 'viewEnter',
|
|
582
|
-
params: {
|
|
583
|
-
type: 'once',
|
|
584
|
-
threshold: 0.3
|
|
585
|
-
},
|
|
586
|
-
sequences: [
|
|
587
|
-
{
|
|
588
|
-
offset: 100,
|
|
589
|
-
offsetEasing: 'quadIn',
|
|
590
|
-
effects: [
|
|
591
|
-
{
|
|
592
|
-
effectId: 'card-entrance',
|
|
593
|
-
listContainer: '.card-grid'
|
|
594
|
-
}
|
|
595
|
-
]
|
|
596
|
-
}
|
|
597
|
-
]
|
|
598
|
-
}
|
|
599
|
-
```
|
|
600
|
-
|
|
601
|
-
With effect in the registry:
|
|
602
|
-
|
|
603
|
-
```typescript
|
|
604
|
-
effects: {
|
|
605
|
-
'card-entrance': {
|
|
606
|
-
duration: 500,
|
|
607
|
-
easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
|
608
|
-
keyframeEffect: {
|
|
609
|
-
name: 'card-fade-up',
|
|
610
|
-
keyframes: [
|
|
611
|
-
{ transform: 'translateY(40px)', opacity: 0 },
|
|
612
|
-
{ transform: 'translateY(0)', opacity: 1 }
|
|
613
|
-
]
|
|
614
|
-
},
|
|
615
|
-
fill: 'both'
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
```
|
|
619
|
-
|
|
620
|
-
**Example - Feature List Cascade (per-key effects)**:
|
|
621
|
-
|
|
622
|
-
When items have individual keys rather than a shared container, list each as a separate effect in the sequence:
|
|
623
|
-
|
|
624
|
-
```typescript
|
|
625
|
-
{
|
|
626
|
-
key: 'features-section',
|
|
627
|
-
trigger: 'viewEnter',
|
|
628
|
-
params: {
|
|
629
|
-
type: 'once',
|
|
630
|
-
threshold: 0.4
|
|
631
|
-
},
|
|
632
|
-
sequences: [
|
|
633
|
-
{
|
|
634
|
-
offset: 100,
|
|
635
|
-
offsetEasing: 'linear',
|
|
636
|
-
effects: [
|
|
637
|
-
{ effectId: 'feature-slide', key: 'feature-1' },
|
|
638
|
-
{ effectId: 'feature-slide', key: 'feature-2' },
|
|
639
|
-
{ effectId: 'feature-slide', key: 'feature-3' }
|
|
640
|
-
]
|
|
641
|
-
}
|
|
642
|
-
]
|
|
643
|
-
}
|
|
644
|
-
```
|
|
645
|
-
|
|
646
|
-
```typescript
|
|
647
|
-
effects: {
|
|
648
|
-
'feature-slide': {
|
|
649
|
-
duration: 500,
|
|
650
|
-
easing: 'cubic-bezier(0.16, 1, 0.3, 1)',
|
|
651
|
-
keyframeEffect: {
|
|
652
|
-
name: 'feature-slide-in',
|
|
653
|
-
keyframes: [
|
|
654
|
-
{ opacity: '0', transform: 'translateX(-30px)' },
|
|
655
|
-
{ opacity: '1', transform: 'translateX(0)' }
|
|
656
|
-
]
|
|
657
|
-
},
|
|
658
|
-
fill: 'backwards'
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
```
|
|
662
|
-
|
|
663
|
-
---
|
|
664
|
-
|
|
665
|
-
## Advanced Patterns and Combinations
|
|
666
|
-
|
|
667
|
-
### ViewEnter with Animation Chaining
|
|
668
|
-
|
|
669
|
-
Using effectId to trigger subsequent animations:
|
|
670
|
-
|
|
671
|
-
```typescript
|
|
672
|
-
// Primary entrance
|
|
673
|
-
{
|
|
674
|
-
key: 'section-container',
|
|
675
|
-
trigger: 'viewEnter',
|
|
676
|
-
params: {
|
|
677
|
-
type: 'once',
|
|
678
|
-
threshold: 0.3
|
|
679
|
-
},
|
|
680
|
-
effects: [
|
|
681
|
-
{
|
|
682
|
-
key: 'section-title',
|
|
683
|
-
namedEffect: {
|
|
684
|
-
type: 'FadeIn'
|
|
685
|
-
},
|
|
686
|
-
duration: 600,
|
|
687
|
-
effectId: 'title-entrance'
|
|
688
|
-
}
|
|
689
|
-
]
|
|
690
|
-
},
|
|
691
|
-
// Chained content animation
|
|
692
|
-
{
|
|
693
|
-
key: 'section-title',
|
|
694
|
-
trigger: 'animationEnd',
|
|
695
|
-
params: {
|
|
696
|
-
effectId: 'title-entrance'
|
|
697
|
-
},
|
|
698
|
-
effects: [
|
|
699
|
-
{
|
|
700
|
-
key: 'section-content',
|
|
701
|
-
namedEffect: {
|
|
702
|
-
type: 'SlideIn'
|
|
703
|
-
},
|
|
704
|
-
duration: 500,
|
|
705
|
-
delay: 100
|
|
706
|
-
}
|
|
707
|
-
]
|
|
708
|
-
}
|
|
709
|
-
```
|
|
710
|
-
|
|
711
|
-
### Multi-Effect ViewEnter
|
|
712
|
-
|
|
713
|
-
Animating multiple targets from single viewport trigger:
|
|
714
|
-
|
|
715
|
-
```typescript
|
|
716
|
-
{
|
|
717
|
-
key: 'hero-trigger',
|
|
718
|
-
trigger: 'viewEnter',
|
|
719
|
-
params: {
|
|
720
|
-
type: 'once',
|
|
721
|
-
threshold: 0.2
|
|
722
|
-
},
|
|
723
|
-
effects: [
|
|
724
|
-
{
|
|
725
|
-
key: 'hero-background',
|
|
726
|
-
keyframeEffect: {
|
|
727
|
-
name: 'blur-bg',
|
|
728
|
-
keyframes: [
|
|
729
|
-
{ filter: 'blur(20px)', transform: 'scale(1.1)' },
|
|
730
|
-
{ filter: 'blur(0)', transform: 'scale(1)' }
|
|
731
|
-
]
|
|
732
|
-
},
|
|
733
|
-
duration: 1200,
|
|
734
|
-
easing: 'ease-out',
|
|
735
|
-
fill: 'backwards'
|
|
736
|
-
},
|
|
737
|
-
{
|
|
738
|
-
key: 'hero-title',
|
|
739
|
-
namedEffect: {
|
|
740
|
-
type: 'SlideIn'
|
|
741
|
-
},
|
|
742
|
-
duration: 800,
|
|
743
|
-
delay: 300
|
|
744
|
-
},
|
|
745
|
-
{
|
|
746
|
-
key: 'hero-subtitle',
|
|
747
|
-
keyframeEffect: {
|
|
748
|
-
name: 'subtitle-slide',
|
|
749
|
-
keyframes: [
|
|
750
|
-
{ opacity: '0', transform: 'translateY(30px)' },
|
|
751
|
-
{ opacity: '1', transform: 'translateY(0)' }
|
|
752
|
-
]
|
|
753
|
-
},
|
|
754
|
-
duration: 600,
|
|
755
|
-
fill: 'backwards',
|
|
756
|
-
delay: 600
|
|
757
|
-
},
|
|
758
|
-
{
|
|
759
|
-
key: 'hero-cta',
|
|
760
|
-
transition: {
|
|
761
|
-
duration: 400,
|
|
762
|
-
delay: 900,
|
|
763
|
-
styleProperties: [
|
|
764
|
-
{ name: 'opacity', value: '1' },
|
|
765
|
-
{ name: 'transform', value: 'translateY(0)' }
|
|
766
|
-
]
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
]
|
|
770
|
-
}
|
|
771
|
-
```
|
|
772
|
-
|
|
773
|
-
### Conditional ViewEnter Animations
|
|
774
|
-
|
|
775
|
-
Use the `conditions` config map to guard interactions by device or motion preference. Condition IDs are user-defined strings — they must be declared in the top-level `conditions` map before being referenced in an interaction.
|
|
776
|
-
|
|
777
|
-
```typescript
|
|
778
|
-
{
|
|
779
|
-
conditions: {
|
|
780
|
-
'desktop-only': { type: 'media', predicate: '(min-width: 768px)' },
|
|
781
|
-
'prefers-motion': { type: 'media', predicate: '(prefers-reduced-motion: no-preference)' },
|
|
782
|
-
'mobile-only': { type: 'media', predicate: '(max-width: 767px)' },
|
|
783
|
-
},
|
|
784
|
-
interactions: [
|
|
785
|
-
{
|
|
786
|
-
key: 'responsive-section',
|
|
787
|
-
trigger: 'viewEnter',
|
|
788
|
-
params: { type: 'once', threshold: 0.5 },
|
|
789
|
-
conditions: ['desktop-only', 'prefers-motion'],
|
|
790
|
-
effects: [
|
|
791
|
-
{
|
|
792
|
-
key: 'responsive-section',
|
|
793
|
-
namedEffect: { type: 'SlideIn' },
|
|
794
|
-
duration: 1000
|
|
795
|
-
}
|
|
796
|
-
]
|
|
797
|
-
},
|
|
798
|
-
// Simplified fallback for mobile or reduced-motion users
|
|
799
|
-
{
|
|
800
|
-
key: 'responsive-section',
|
|
801
|
-
trigger: 'viewEnter',
|
|
802
|
-
params: { type: 'once', threshold: 0.7 },
|
|
803
|
-
conditions: ['mobile-only'],
|
|
804
|
-
effects: [
|
|
805
|
-
{
|
|
806
|
-
key: 'responsive-section',
|
|
807
|
-
namedEffect: { type: 'FadeIn' },
|
|
808
|
-
duration: 400
|
|
809
|
-
}
|
|
810
|
-
]
|
|
811
|
-
}
|
|
812
|
-
]
|
|
813
|
-
}
|
|
814
|
-
```
|
|
815
|
-
|
|
816
|
-
---
|
|
817
|
-
|
|
818
|
-
## Preventing Flash of Unstyled Content (FOUC)
|
|
819
|
-
|
|
820
|
-
Use `generate(config)` from `@wix/interact/web` server-side or at build time to produce critical CSS that hides entrance elements until their animation plays.
|
|
821
|
-
|
|
822
|
-
**Constraints:**
|
|
823
|
-
|
|
824
|
-
- MUST be called server-side or at build time — not client-side
|
|
825
|
-
- MUST set `data-interact-initial="true"` on the `<interact-element>` whose first child should be hidden
|
|
826
|
-
- Only valid for `viewEnter` + `params.type: 'once'` where source and target are the same element
|
|
827
|
-
- Do NOT use for `hover`, `click`, or `viewEnter` with `repeat`/`alternate`/`state` types
|
|
828
|
-
|
|
829
|
-
```typescript
|
|
830
|
-
import { generate } from '@wix/interact/web';
|
|
831
|
-
|
|
832
|
-
const config: InteractConfig = {
|
|
833
|
-
interactions: [
|
|
834
|
-
{
|
|
835
|
-
key: 'hero',
|
|
836
|
-
trigger: 'viewEnter',
|
|
837
|
-
params: { type: 'once', threshold: 0.2 },
|
|
838
|
-
effects: [{ namedEffect: { type: 'FadeIn' }, duration: 800 }],
|
|
839
|
-
},
|
|
840
|
-
],
|
|
841
|
-
};
|
|
842
|
-
|
|
843
|
-
// Called at build time or on the server
|
|
844
|
-
const css = generate(config);
|
|
845
|
-
|
|
846
|
-
// Inject into <head> before the page renders
|
|
847
|
-
const html = `
|
|
848
|
-
<head>
|
|
849
|
-
<style>${css}</style>
|
|
850
|
-
</head>
|
|
851
|
-
<body>
|
|
852
|
-
<interact-element data-interact-key="hero" data-interact-initial="true">
|
|
853
|
-
<section class="hero">...</section>
|
|
854
|
-
</interact-element>
|
|
855
|
-
</body>
|
|
856
|
-
`;
|
|
857
|
-
```
|
|
858
|
-
|
|
859
|
-
---
|
|
860
|
-
|
|
861
|
-
## Best Practices for ViewEnter Interactions
|
|
862
|
-
|
|
863
|
-
### Behavior Guidelines
|
|
864
|
-
|
|
865
|
-
1. **Use `alternate` and `repeat` types only with a separate source `key` and target `key`** to avoid re-triggering when animation starts or not triggering at all if animated target is out of viewport or clipped
|
|
866
|
-
|
|
867
|
-
### Performance Guidelines
|
|
868
|
-
|
|
869
|
-
1. **Use `once` type for entrance animations** to avoid repeated triggers
|
|
870
|
-
2. **Be careful with separate source/target patterns** - ensure source doesn't get clipped
|
|
871
|
-
3. **Use appropriate thresholds** - avoid triggering too early or too late
|
|
872
|
-
|
|
873
|
-
### Threshold and Timing Guidelines
|
|
874
|
-
|
|
875
|
-
1. **Use realistic thresholds** (0.1-0.5) for natural timing
|
|
876
|
-
2. **Use tiny thresholds for huge elements** (0.01-0.05) for elements much larger than viewport
|
|
877
|
-
3. **Provide adequate inset margins** for mobile viewports
|
|
878
|
-
4. **Keep entrance animations moderate** (500-1200ms)
|
|
879
|
-
5. **Use staggered delays thoughtfully** (50-200ms intervals)
|
|
880
|
-
|
|
881
|
-
### Threshold and Timing Reference
|
|
882
|
-
|
|
883
|
-
**Recommended Thresholds by Content Type**:
|
|
884
|
-
|
|
885
|
-
- **Hero sections**: 0.1-0.3 (early trigger)
|
|
886
|
-
- **Content blocks**: 0.3-0.5 (balanced trigger)
|
|
887
|
-
- **Small elements**: 0.5-0.8 (late trigger)
|
|
888
|
-
- **Tall sections**: 0.1-0.2 (early trigger)
|
|
889
|
-
- **Huge sections**: 0.01-0.05 (ensure trigger)
|
|
890
|
-
|
|
891
|
-
**Recommended Insets by Device**:
|
|
892
|
-
|
|
893
|
-
- **Desktop**: '-50px' to '-200px'
|
|
894
|
-
- **Mobile**: '-20px' to '-100px'
|
|
895
|
-
- **Positive insets**: '50px' for precise timing
|
|
896
|
-
|
|
897
|
-
### Common Use Cases by Pattern
|
|
898
|
-
|
|
899
|
-
**Once Pattern**:
|
|
900
|
-
|
|
901
|
-
- Hero section entrances
|
|
902
|
-
- Content block reveals
|
|
903
|
-
- Image lazy loading
|
|
904
|
-
- Feature introductions
|
|
905
|
-
- Call-to-action reveals
|
|
906
|
-
|
|
907
|
-
**Repeat Pattern**:
|
|
908
|
-
|
|
909
|
-
- Interactive counters
|
|
910
|
-
- Scroll-triggered galleries
|
|
911
|
-
- Progressive content loading
|
|
912
|
-
- Repeated call-to-actions
|
|
913
|
-
- Dynamic content sections
|
|
914
|
-
|
|
915
|
-
**Alternate Pattern**:
|
|
916
|
-
|
|
917
|
-
- Scroll-responsive UI elements
|
|
918
|
-
- Reversible content reveals
|
|
919
|
-
- Navigation state changes
|
|
920
|
-
- Context-sensitive helpers
|
|
921
|
-
- Progressive disclosure
|
|
922
|
-
|
|
923
|
-
**State Pattern**:
|
|
924
|
-
|
|
925
|
-
- Ambient animations
|
|
926
|
-
- Background effects
|
|
927
|
-
- Decorative elements
|
|
928
|
-
- Loading states
|
|
929
|
-
- Atmospheric content
|
|
930
|
-
|
|
931
|
-
**Staggered Animations**:
|
|
932
|
-
|
|
933
|
-
- Card grids and lists
|
|
934
|
-
- Team member sections
|
|
935
|
-
- Feature comparisons
|
|
936
|
-
- Product catalogs
|
|
937
|
-
- Timeline elements
|
|
938
|
-
|
|
939
|
-
### Troubleshooting Common Issues
|
|
940
|
-
|
|
941
|
-
**ViewEnter not triggering**:
|
|
942
|
-
|
|
943
|
-
- Check if source element is clipped by parent overflow
|
|
944
|
-
- Verify element exists when `Interact.create()` is called
|
|
945
|
-
- Ensure threshold and inset values are appropriate
|
|
946
|
-
- Check for conflicting CSS that might hide elements
|
|
947
|
-
|
|
948
|
-
**ViewEnter triggering multiple times**:
|
|
949
|
-
|
|
950
|
-
- Use `once` type for entrance animations
|
|
951
|
-
- Avoid animating the source element if it's also the target
|
|
952
|
-
- Consider using separate source and target elements
|
|
953
|
-
|
|
954
|
-
**Animation performance issues**:
|
|
218
|
+
### Variables
|
|
955
219
|
|
|
956
|
-
-
|
|
957
|
-
-
|
|
958
|
-
-
|
|
959
|
-
-
|
|
220
|
+
- `[SOURCE_KEY]` / `[VISIBILITY_THRESHOLD]` / `[VIEWPORT_INSETS]` — same as Rule 1.
|
|
221
|
+
- `[TRIGGER_TYPE]` — same as Rule 1. `triggerType` is set on the sequence config, not on individual effects within the sequence.
|
|
222
|
+
- `[OFFSET_MS]` — time offset between each child's animation start, in milliseconds.
|
|
223
|
+
- `[OFFSET_EASING]` — CSS easing or named easing from `@wix/motion`, for the stagger distribution. Defaults to `'linear'`.
|
|
224
|
+
- `[EFFECT_DEFINTION]` — a definition of or a reference to a time-based animation effect.
|