@wix/interact 2.0.3 → 2.0.4

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.
Files changed (38) hide show
  1. package/dist/cjs/index.js +1 -1
  2. package/dist/cjs/react.js +1 -1
  3. package/dist/cjs/web.js +1 -1
  4. package/dist/es/index.js +1 -1
  5. package/dist/es/react.js +2 -2
  6. package/dist/es/web.js +2 -2
  7. package/dist/index-BZL18ynN.mjs +2750 -0
  8. package/dist/index-BZL18ynN.mjs.map +1 -0
  9. package/dist/index-IaOsZpFD.js +18 -0
  10. package/dist/index-IaOsZpFD.js.map +1 -0
  11. package/dist/tsconfig.build.tsbuildinfo +1 -1
  12. package/dist/types/core/Interact.d.ts +9 -1
  13. package/dist/types/core/Interact.d.ts.map +1 -1
  14. package/dist/types/core/add.d.ts.map +1 -1
  15. package/dist/types/handlers/animationEnd.d.ts +1 -1
  16. package/dist/types/handlers/animationEnd.d.ts.map +1 -1
  17. package/dist/types/handlers/effectHandlers.d.ts +2 -1
  18. package/dist/types/handlers/effectHandlers.d.ts.map +1 -1
  19. package/dist/types/handlers/eventTrigger.d.ts +1 -1
  20. package/dist/types/handlers/eventTrigger.d.ts.map +1 -1
  21. package/dist/types/handlers/viewEnter.d.ts +1 -1
  22. package/dist/types/handlers/viewEnter.d.ts.map +1 -1
  23. package/dist/types/types.d.ts +29 -2
  24. package/dist/types/types.d.ts.map +1 -1
  25. package/package.json +2 -2
  26. package/rules/MASTER-CLEANUP-PLAN.md +286 -0
  27. package/rules/click.md +12 -31
  28. package/rules/full-lean.md +26 -4
  29. package/rules/hover.md +68 -153
  30. package/rules/integration.md +17 -85
  31. package/rules/pointermove.md +32 -57
  32. package/rules/scroll-list.md +82 -280
  33. package/rules/viewenter.md +65 -90
  34. package/rules/viewprogress.md +139 -845
  35. package/dist/index-BfcN_rkn.mjs +0 -2338
  36. package/dist/index-BfcN_rkn.mjs.map +0 -1
  37. package/dist/index-HXLBEIjG.js +0 -18
  38. package/dist/index-HXLBEIjG.js.map +0 -1
@@ -1,387 +1,98 @@
1
1
  # ViewProgress Trigger Rules for @wix/interact
2
2
 
3
- These rules help generate scroll-driven interactions using the `@wix/interact` library. `viewProgress` triggers create scroll-based animations that update continuously as elements move through the viewport, leveraging native CSS ViewTimelines.
3
+ ## Core Concept
4
4
 
5
- ## Rule 1: Range-Based Parallax/Continuous Animation Control with Named Effects
5
+ `viewProgress` triggers create scroll-driven animations that update continuously as elements move through the viewport, leveraging native CSS ViewTimelines. Use when animation progress should be tied to the element's scroll position.
6
6
 
7
- **Use Case**: Continuous scroll-driven animations using pre-built named effects that respond to scroll position (e.g., parallax backgrounds, floating elements, scroll-responsive decorations)
8
-
9
- **When to Apply**:
10
-
11
- - For smooth parallax background movements
12
- - When creating scroll-responsive floating elements
13
- - For continuous scroll-driven decorative animations
14
- - When using pre-built motion effects for scroll interactions
15
-
16
- **KeyframeEffect Pattern**:
7
+ ## Config Template
17
8
 
18
9
  ```typescript
19
10
  {
20
11
  key: '[SOURCE_KEY]',
21
12
  trigger: 'viewProgress',
13
+ conditions: ['[CONDITION_NAME]'], // optional: e.g. 'prefers-motion', 'desktop-only'
22
14
  effects: [
23
15
  {
24
16
  key: '[TARGET_KEY]',
25
- keyframeEffect: {
26
- name: '[EFFECT_NAME]',
27
- keyframes: [EFFECT_KEYFRAMES]
28
- },
17
+ // Effect block — use exactly one of: namedEffect | keyframeEffect | customEffect
18
+ namedEffect: { type: '[NAMED_EFFECT]', /* preset-specific options only if documented */ }, // OR
19
+ keyframeEffect: { name: '[EFFECT_NAME]', keyframes: [EFFECT_KEYFRAMES] }, // OR
20
+ customEffect: (element, progress) => { [CUSTOM_LOGIC] },
29
21
  rangeStart: { name: '[RANGE_NAME]', offset: { unit: 'percentage', value: [START_PERCENTAGE] } },
30
22
  rangeEnd: { name: '[RANGE_NAME]', offset: { unit: 'percentage', value: [END_PERCENTAGE] } },
31
23
  easing: '[EASING_FUNCTION]',
32
- fill: 'both',
24
+ fill: '[FILL_MODE]',
33
25
  effectId: '[UNIQUE_EFFECT_ID]'
34
26
  }
35
27
  ]
36
28
  }
37
29
  ```
38
30
 
39
- **Variables**:
31
+ ## Variable Key
40
32
 
41
- - `[SOURCE_KEY]`: Unique identifier for element that tracks scroll progress
42
- - `[TARGET_KEY]`: Unique identifier for element to animate (can be same as source or different)
43
- - `[EFFECT_NAME]`: Optional unique name for the effec
44
- - `[EFFECT_KEYFRAMES]`: Keyframes for the effect
45
- - `[RANGE_NAME]`: 'cover', 'contain', 'entry', 'exit', 'entry-crossing', or 'exit-crossing'
46
- - `[START_PERCENTAGE]`: Start point as percentage (0-100)
47
- - `[END_PERCENTAGE]`: End point as percentage (0-100)
48
- - `[EASING_FUNCTION]`: Timing function (typically 'linear' for smooth scroll effects)
49
- - `[UNIQUE_EFFECT_ID]`: Optional unique identifier
33
+ | Placeholder | Valid Values / Notes |
34
+ | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
35
+ | `[SOURCE_KEY]` | Unique identifier for element that tracks scroll progress |
36
+ | `[TARGET_KEY]` | Unique identifier for element to animate (can equal source) |
37
+ | `[NAMED_EFFECT]` | Preset from @wix/motion-presets (see Named Scroll Effects below). Some presets accept options (e.g. `direction`) — only use options you have documentation for; omit and rely on defaults otherwise |
38
+ | `[EFFECT_NAME]` | Unique name for keyframe effect |
39
+ | `[EFFECT_KEYFRAMES]` | Array of keyframe objects, e.g. `[{ opacity: '0' }, { opacity: '1' }]` |
40
+ | `[CUSTOM_LOGIC]` | JS: `progress` is 0–1 within range; mutate `element.style` or DOM |
41
+ | `[RANGE_NAME]` | 'cover', 'contain', 'entry', 'exit', 'entry-crossing', 'exit-crossing' |
42
+ | `[START_PERCENTAGE]` | 0–100 |
43
+ | `[END_PERCENTAGE]` | 0–100 |
44
+ | `[EASING_FUNCTION]` | 'linear', 'ease-in', 'ease-out', 'ease-in-out', or cubic-bezier string |
45
+ | `[FILL_MODE]` | 'both', 'backwards', 'forwards', 'none' |
46
+ | `[UNIQUE_EFFECT_ID]` | Optional unique identifier |
47
+ | `[CONDITION_NAME]` | User-defined condition ID declared in the top-level `conditions` map (e.g. `'prefers-motion'`, `'desktop-only'`) |
50
48
 
51
- **NamedEffect Pattern**:
49
+ **Offset semantics:** Positive offset values move the effective range forward along the scroll axis. 0 = start of range, 100 = end.
52
50
 
53
- ```typescript
54
- {
55
- key: '[SOURCE_KEY]',
56
- trigger: 'viewProgress',
57
- effects: [
58
- {
59
- key: '[TARGET_KEY]',
60
- namedEffect: {
61
- type: '[NAMED_EFFECT]'
62
- },
63
- rangeStart: { name: '[RANGE_NAME]', offset: { unit: 'percentage', value: [START_PERCENTAGE] } },
64
- rangeEnd: { name: '[RANGE_NAME]', offset: { unit: 'percentage', value: [END_PERCENTAGE] } },
65
- easing: '[EASING_FUNCTION]',
66
- effectId: '[UNIQUE_EFFECT_ID]'
67
- }
68
- ]
69
- }
70
- ```
51
+ ## Effect Type Selection
71
52
 
72
- - `[NAMED_EFFECT]`: Pre-built scroll effect name from @wix/motion-presets (e.g., 'ParallaxScroll', 'MoveScroll', 'FadeScroll', 'RevealScroll', 'GrowScroll', 'SlideScroll', 'SpinScroll', 'PanScroll', 'BlurScroll', 'ArcScroll', 'FlipScroll', 'Spin3dScroll', 'TiltScroll', 'TurnScroll', 'ShapeScroll', 'ShuttersScroll', 'ShrinkScroll', 'SkewPanScroll', 'StretchScroll')
53
+ | Scenario | Effect Type | Notes |
54
+ | ------------------------------------------------------------- | ---------------- | --------------------------------- |
55
+ | Parallax, scroll-responsive decorations, floating elements | `namedEffect` | Use presets; fastest to implement |
56
+ | Custom multi-property animations, brand-specific reveals | `keyframeEffect` | Full control over CSS keyframes |
57
+ | Dynamic content (counters, text reveal, canvas, calculations) | `customEffect` | JS callback; `progress` 0–1 |
73
58
 
74
- **Example - Background Parallax**:
59
+ ## Range Reference
75
60
 
76
- ```typescript
77
- {
78
- key: 'hero-section',
79
- trigger: 'viewProgress',
80
- effects: [
81
- {
82
- key: 'hero-background',
83
- namedEffect: {
84
- type: 'ParallaxScroll'
85
- },
86
- rangeStart: { name: 'cover', offset: { unit: 'percentage', value: 0 } },
87
- rangeEnd: { name: 'cover', offset: { unit: 'percentage', value: 100 } },
88
- easing: 'linear'
89
- }
90
- ]
91
- }
92
- ```
61
+ | Intent | rangeStart.name | rangeEnd.name | Typical Offsets |
62
+ | --------------------------------------- | --------------- | ------------- | ---------------------- |
63
+ | Parallax / continuous while visible | cover | cover | 0–100 |
64
+ | Entry animation (element entering view) | entry | entry | 0–30 start, 70–100 end |
65
+ | Exit animation (element leaving view) | exit | exit | 0–30 start, 70–100 end |
66
+ | Cross-range (entry to exit) | entry | exit | 0–100 |
67
+ | Contained phase | contain | contain | 0–100 |
93
68
 
94
- ---
69
+ ## Named Scroll Effects
95
70
 
96
- ## Rule 2: Range-Based Entry Animation Control with Named Effects
71
+ From `@wix/motion-presets` scroll animations: ParallaxScroll, MoveScroll, FadeScroll, RevealScroll, GrowScroll, SlideScroll, SpinScroll, PanScroll, BlurScroll, ArcScroll, FlipScroll, Spin3dScroll, TiltScroll, TurnScroll, ShapeScroll, ShuttersScroll, ShrinkScroll, SkewPanScroll, StretchScroll.
97
72
 
98
- **Use Case**: Scroll-driven entrance animations using named effects that start when elements enter the viewport (e.g., content reveals, scroll-driven introductions)
73
+ ## Examples
99
74
 
100
- **When to Apply**:
101
-
102
- - For scroll-controlled entrance animations
103
- - When elements should reveal gradually as they come into view
104
- - For progressive content disclosure based on scroll
105
- - When using pre-built entrance effects with scroll control
106
-
107
- **Pattern**:
75
+ ### Example 1: Named Effect (Parallax)
108
76
 
109
77
  ```typescript
110
78
  {
111
- key: '[SOURCE_KEY]',
112
- trigger: 'viewProgress',
113
- effects: [
114
- {
115
- key: '[TARGET_KEY]',
116
- namedEffect: {
117
- type: '[ENTRANCE_EFFECT]'
118
- },
119
- rangeStart: { name: 'entry', offset: { unit: 'percentage', value: [ENTRY_START] } },
120
- rangeEnd: { name: 'entry', offset: { unit: 'percentage', value: [ENTRY_END] } },
121
- easing: '[EASING_FUNCTION]',
122
- effectId: '[UNIQUE_EFFECT_ID]'
123
- }
124
- ]
125
- }
126
- ```
127
-
128
- **Variables**:
129
-
130
- - `[ENTRANCE_EFFECT]`: Named entrance effect from @wix/motion-presets scroll animations (e.g., 'FadeScroll', 'SlideScroll', 'RevealScroll', 'ShapeScroll', 'GrowScroll', 'MoveScroll', 'BlurScroll')
131
- - `[ENTRY_START]`: Entry animation start percentage (typically 0-30)
132
- - `[ENTRY_END]`: Entry animation end percentage (typically 70-100)
133
- - Other variables same as Rule 1
134
-
135
- **Example - Content Reveal on Entry**:
136
-
137
- ```typescript
138
- {
139
- key: 'content-block',
140
- trigger: 'viewProgress',
141
- effects: [
142
- {
143
- key: 'content-block',
144
- namedEffect: {
145
- type: 'RevealScroll',
146
- direction: 'left'
147
- },
148
- rangeStart: { name: 'entry', offset: { unit: 'percentage', value: 0 } },
149
- rangeEnd: { name: 'entry', offset: { unit: 'percentage', value: 60 } },
150
- easing: 'ease-out'
151
- }
152
- ]
153
- }
154
- ```
155
-
156
- **Example - Progressive Image Reveal**:
157
-
158
- ```typescript
159
- {
160
- key: 'image-container',
79
+ key: 'hero-section',
161
80
  trigger: 'viewProgress',
162
81
  effects: [
163
82
  {
164
- key: 'feature-image',
83
+ key: 'hero-background',
165
84
  namedEffect: {
166
- type: 'FadeScroll'
167
- },
168
- rangeStart: { name: 'entry', offset: { unit: 'percentage', value: 20 } },
169
- rangeEnd: { name: 'entry', offset: { unit: 'percentage', value: 80 } },
170
- easing: 'cubic-bezier(0.16, 1, 0.3, 1)',
171
- effectId: 'image-reveal'
172
- }
173
- ]
174
- }
175
- ```
176
-
177
- ---
178
-
179
- ## Rule 3: Range-Based Exit Animation Control with Named Effects
180
-
181
- **Use Case**: Scroll-driven exit animations using named effects that trigger when elements leave the viewport (e.g., content hiding, scroll-out effects, element dismissals)
182
-
183
- **When to Apply**:
184
-
185
- - For scroll-controlled exit animations
186
- - When elements should hide gradually as they leave view
187
- - For creating scroll-responsive content dismissals
188
- - When using pre-built exit effects with scroll control
189
-
190
- **Pattern**:
191
-
192
- ```typescript
193
- {
194
- key: '[SOURCE_KEY]',
195
- trigger: 'viewProgress',
196
- effects: [
197
- {
198
- key: '[TARGET_KEY]',
199
- keyframeEffect: {
200
- name: '[EFFECT_NAME]',
201
- keyframes: [EFFECT_KEYFRAMES]
202
- },
203
- rangeStart: { name: 'exit', offset: { unit: 'percentage', value: [EXIT_START] } },
204
- rangeEnd: { name: 'exit', offset: { unit: 'percentage', value: [EXIT_END] } },
205
- easing: '[EASING_FUNCTION]',
206
- fill: 'both',
207
- effectId: '[UNIQUE_EFFECT_ID]'
208
- }
209
- ]
210
- }
211
- ```
212
-
213
- **Variables**:
214
-
215
- - `[EXIT_START]`: Exit animation start percentage (typically 0-30)
216
- - `[EXIT_END]`: Exit animation end percentage (typically 70-100)
217
- - `[EFFECT_KEYFRAMES]`:
218
-
219
- **Example - Content Fade Out on Exit**:
220
-
221
- ```typescript
222
- {
223
- key: 'hero-content',
224
- trigger: 'viewProgress',
225
- effects: [
226
- {
227
- key: 'hero-text',
228
- keyframeEffect: {
229
- name: 'fade-out',
230
- keyframes: [{
231
- opacity: 0
232
- }]
233
- },
234
- rangeStart: { name: 'exit', offset: { unit: 'percentage', value: 0 } },
235
- rangeEnd: { name: 'exit', offset: { unit: 'percentage', value: 100 } },
236
- easing: 'ease-in',
237
- fill: 'both'
238
- }
239
- ]
240
- }
241
- ```
242
-
243
- ---
244
-
245
- ## Rule 4: Range-Based Parallax/Continuous Animation Control with Keyframe Effects
246
-
247
- **Use Case**: Custom scroll-driven animations using keyframe effects for precise control over continuous scroll-responsive animations (e.g., custom parallax movements, complex scroll transformations, multi-property scroll effects)
248
-
249
- **When to Apply**:
250
-
251
- - For custom parallax effects not available in named effects
252
- - When combining multiple CSS properties in scroll animations
253
- - For precise control over scroll-driven transformations
254
- - When creating unique scroll-responsive visual effects
255
-
256
- **Pattern**:
257
-
258
- ```typescript
259
- {
260
- key: '[SOURCE_KEY]',
261
- trigger: 'viewProgress',
262
- effects: [
263
- {
264
- key: '[TARGET_KEY]',
265
- keyframeEffect: {
266
- name: '[UNIQUE_KEYFRAME_EFFECT_NAME]',
267
- keyframes: [
268
- { [CSS_PROPERTY_1]: '[START_VALUE_1]', [CSS_PROPERTY_2]: '[START_VALUE_2]', [CSS_PROPERTY_3]: '[START_VALUE_3]' },
269
- { [CSS_PROPERTY_1]: '[END_VALUE_1]', [CSS_PROPERTY_2]: '[END_VALUE_2]', [CSS_PROPERTY_3]: '[END_VALUE_3]' }
270
- ]
271
- },
272
- rangeStart: { name: '[RANGE_NAME]', offset: { unit: 'percentage', value: [START_PERCENTAGE] } },
273
- rangeEnd: { name: '[RANGE_NAME]', offset: { unit: 'percentage', value: [END_PERCENTAGE] } },
274
- easing: '[EASING_FUNCTION]',
275
- fill: 'both',
276
- effectId: '[UNIQUE_EFFECT_ID]'
277
- }
278
- ]
279
- }
280
- ```
281
-
282
- **Variables**:
283
-
284
- - `[UNIQUE_KEYFRAME_EFFECT_NAME]`: unique name for the CSS keyframe effect (can equal `[UNIQUE_EFFECT_ID]` if provided)
285
- - `[CSS_PROPERTY_N]`: CSS property names (e.g., 'transform', 'opacity', 'filter')
286
- - `[START_VALUE_N]`: Starting value for the property
287
- - `[END_VALUE_N]`: Ending value for the property
288
- - Other variables same as Rule 1
289
-
290
- **Example - Custom Background Parallax**:
291
-
292
- ```typescript
293
- {
294
- key: 'parallax-section',
295
- trigger: 'viewProgress',
296
- effects: [
297
- {
298
- key: 'parallax-bg',
299
- keyframeEffect: {
300
- name: 'parallax-bg',
301
- keyframes: [
302
- { transform: 'translateY(0)', filter: 'brightness(1)', opacity: '0.9' },
303
- { opacity: '1' },
304
- { transform: 'translateY(-200px)', filter: 'brightness(0.8)', opacity: '0.9' }
305
- ]
85
+ type: 'ParallaxScroll'
306
86
  },
307
87
  rangeStart: { name: 'cover', offset: { unit: 'percentage', value: 0 } },
308
88
  rangeEnd: { name: 'cover', offset: { unit: 'percentage', value: 100 } },
309
- easing: 'linear',
310
- fill: 'both'
311
- }
312
- ]
313
- }
314
- ```
315
-
316
- **Example - Multi-Layer Scroll Effect**:
317
-
318
- ```typescript
319
- {
320
- key: 'complex-section',
321
- trigger: 'viewProgress',
322
- effects: [
323
- {
324
- key: 'background-layer',
325
- keyframeEffect: {
326
- name: 'bg-scroll',
327
- keyframes: [
328
- { transform: 'scale(1.1) translateY(0)', filter: 'blur(0)' },
329
- { transform: 'scale(1) translateY(-100px)', filter: 'blur(2px)' }
330
- ]
331
- },
332
- rangeStart: { name: 'entry', offset: { unit: 'percentage', value: 0 } },
333
- rangeEnd: { name: 'exit', offset: { unit: 'percentage', value: 100 } },
334
- easing: 'linear',
335
- fill: 'both',
336
- effectId: 'bg-scroll'
337
- }
338
- ]
339
- }
340
- ```
341
-
342
- ---
343
-
344
- ## Rule 5: Range-Based Entry Animation Control with Keyframe Effects
345
-
346
- **Use Case**: Custom scroll-driven entrance animations using keyframe effects for precise control over how elements appear as they enter the viewport (e.g., custom reveal effects, multi-property entrances, unique scroll-in animations)
347
-
348
- **When to Apply**:
349
-
350
- - For custom entrance effects not available in named effects
351
- - When combining multiple properties in entrance animations
352
- - For brand-specific or unique entry animations
353
- - When creating complex reveal sequences
354
-
355
- **Pattern**:
356
-
357
- ```typescript
358
- {
359
- key: '[SOURCE_KEY]',
360
- trigger: 'viewProgress',
361
- effects: [
362
- {
363
- key: '[TARGET_KEY]',
364
- keyframeEffect: {
365
- name: '[UNIQUE_KEYFRAME_EFFECT_NAME]',
366
- keyframes: [
367
- { [CSS_PROPERTY_1]: '[START_VALUE_1]', [CSS_PROPERTY_2]: '[START_VALUE_2]' },
368
- { [CSS_PROPERTY_1]: '[END_VALUE_1]', [CSS_PROPERTY_2]: '[END_VALUE_2]' }
369
- ]
370
- },
371
- rangeStart: { name: 'entry', offset: { unit: 'percentage', value: [ENTRY_START] } },
372
- rangeEnd: { name: 'entry', offset: { unit: 'percentage', value: [ENTRY_END] } },
373
- easing: '[EASING_FUNCTION]',
374
- fill: 'both',
375
- effectId: '[UNIQUE_EFFECT_ID]'
89
+ easing: 'linear'
376
90
  }
377
91
  ]
378
92
  }
379
93
  ```
380
94
 
381
- **Variables**:
382
- Same as Rule 4, with focus on entry range
383
-
384
- **Example - Custom Card Entrance**:
95
+ ### Example 2: Keyframe Effect (Custom Animation)
385
96
 
386
97
  ```typescript
387
98
  {
@@ -406,264 +117,7 @@ Same as Rule 4, with focus on entry range
406
117
  }
407
118
  ```
408
119
 
409
- **Example - Text Progressive Reveal**:
410
-
411
- ```typescript
412
- {
413
- key: 'text-container',
414
- trigger: 'viewProgress',
415
- effects: [
416
- {
417
- key: 'main-heading',
418
- keyframeEffect: {
419
- name: 'heading-reveal',
420
- keyframes: [
421
- { opacity: '0', transform: 'translateX(-50px)', color: 'rgba(0,0,0,0.3)' },
422
- { opacity: '1', transform: 'translateX(0)', color: 'rgba(0,0,0,1)' }
423
- ]
424
- },
425
- rangeStart: { name: 'entry', offset: { unit: 'percentage', value: 10 } },
426
- rangeEnd: { name: 'entry', offset: { unit: 'percentage', value: 60 } },
427
- easing: 'ease-out',
428
- fill: 'both',
429
- effectId: 'heading-reveal'
430
- }
431
- ]
432
- }
433
- ```
434
-
435
- ---
436
-
437
- ## Rule 6: Range-Based Exit Animation Control with Keyframe Effects
438
-
439
- **Use Case**: Custom scroll-driven exit animations using keyframe effects for precise control over how elements disappear as they leave the viewport (e.g., custom hide effects, multi-property exits, unique scroll-out animations)
440
-
441
- **When to Apply**:
442
-
443
- - For custom exit effects not available in named effects
444
- - When combining multiple properties in exit animations
445
- - For creating smooth content transitions on scroll out
446
- - When elements need complex hiding sequences
447
-
448
- **Pattern**:
449
-
450
- ```typescript
451
- {
452
- key: '[SOURCE_KEY]',
453
- trigger: 'viewProgress',
454
- effects: [
455
- {
456
- key: '[TARGET_KEY]',
457
- keyframeEffect: {
458
- name: '[UNIQUE_KEYFRAME_EFFECT_NAME]',
459
- keyframes: [
460
- { [CSS_PROPERTY_1]: '[START_VALUE_1]', [CSS_PROPERTY_2]: '[START_VALUE_2]' },
461
- { [CSS_PROPERTY_1]: '[END_VALUE_1]', [CSS_PROPERTY_2]: '[END_VALUE_2]' }
462
- ]
463
- },
464
- rangeStart: { name: 'exit', offset: { unit: 'percentage', value: [EXIT_START] } },
465
- rangeEnd: { name: 'exit', offset: { unit: 'percentage', value: [EXIT_END] } },
466
- easing: '[EASING_FUNCTION]',
467
- fill: 'both',
468
- effectId: '[UNIQUE_EFFECT_ID]'
469
- }
470
- ]
471
- }
472
- ```
473
-
474
- **Variables**:
475
- Same as Rule 4, with focus on exit range
476
-
477
- **Example - Hero Content Exit**:
478
-
479
- ```typescript
480
- {
481
- key: 'hero-section',
482
- trigger: 'viewProgress',
483
- effects: [
484
- {
485
- key: 'hero-content',
486
- keyframeEffect: {
487
- name: 'hero-content-animation',
488
- keyframes: [
489
- { opacity: '1', transform: 'translateY(0) scale(1)', filter: 'blur(0)' },
490
- { opacity: '0', transform: 'translateY(-50px) scale(0.95)', filter: 'blur(3px)' }
491
- ]
492
- },
493
- rangeStart: { name: 'exit', offset: { unit: 'percentage', value: 0 } },
494
- rangeEnd: { name: 'exit', offset: { unit: 'percentage', value: 60 } },
495
- easing: 'ease-in',
496
- fill: 'both'
497
- }
498
- ]
499
- }
500
- ```
501
-
502
- **Example - Navigation Scroll Hide**:
503
-
504
- ```typescript
505
- {
506
- key: 'main-header',
507
- trigger: 'viewProgress',
508
- effects: [
509
- {
510
- key: 'sticky-nav',
511
- keyframeEffect: {
512
- name: 'nav-hide',
513
- keyframes: [
514
- { transform: 'translateY(0)', opacity: '1', backdropFilter: 'blur(10px)' },
515
- { transform: 'translateY(-100%)', opacity: '0.7', backdropFilter: 'blur(0)' }
516
- ]
517
- },
518
- rangeStart: { name: 'exit', offset: { unit: 'percentage', value: 20 } },
519
- rangeEnd: { name: 'exit', offset: { unit: 'percentage', value: 80 } },
520
- easing: 'ease-in-out',
521
- fill: 'both',
522
- effectId: 'nav-hide'
523
- }
524
- ]
525
- }
526
- ```
527
-
528
- ---
529
-
530
- ## Rule 7: Range-Based Parallax/Continuous Animation Control with Custom Effects
531
-
532
- **Use Case**: JavaScript-powered scroll-driven animations with full programmatic control for complex interactions (e.g., canvas animations, complex calculations, dynamic content updates, interactive scroll effects)
533
-
534
- **When to Apply**:
535
-
536
- - For animations requiring complex calculations
537
- - When integrating with canvas or WebGL
538
- - For dynamic content updates based on scroll
539
- - When CSS keyframes are insufficient
540
-
541
- **Pattern**:
542
-
543
- ```typescript
544
- {
545
- key: '[SOURCE_KEY]',
546
- trigger: 'viewProgress',
547
- effects: [
548
- {
549
- key: '[TARGET_KEY]',
550
- customEffect: (element, progress) => {
551
- // progress is 0-1 representing scroll position within range
552
- [CUSTOM_ANIMATION_LOGIC]
553
- },
554
- rangeStart: { name: '[RANGE_NAME]', offset: { unit: 'percentage', value: [START_PERCENTAGE] } },
555
- rangeEnd: { name: '[RANGE_NAME]', offset: { unit: 'percentage', value: [END_PERCENTAGE] } },
556
- fill: 'both',
557
- effectId: '[UNIQUE_EFFECT_ID]'
558
- }
559
- ]
560
- }
561
- ```
562
-
563
- **Variables**:
564
-
565
- - `[CUSTOM_ANIMATION_LOGIC]`: JavaScript code for custom animation
566
- - Other variables same as Rule 1
567
-
568
- **Example - Scroll Counter Update**:
569
-
570
- ```typescript
571
- {
572
- key: 'stats-section',
573
- trigger: 'viewProgress',
574
- effects: [
575
- {
576
- key: 'progress-counter',
577
- customEffect: (element, progress) => {
578
- const currentValue = Math.floor(progress * 100);
579
- element.textContent = `${currentValue}%`;
580
- element.style.color = `hsl(${progress * 120}, 70%, 50%)`;
581
- },
582
- rangeStart: { name: 'cover', offset: { unit: 'percentage', value: 0 } },
583
- rangeEnd: { name: 'cover', offset: { unit: 'percentage', value: 100 } },
584
- fill: 'both',
585
- effectId: 'progress-counter'
586
- }
587
- ]
588
- }
589
- ```
590
-
591
- **Example - Complex Particle Animation**:
592
-
593
- ```typescript
594
- {
595
- key: 'particle-container',
596
- trigger: 'viewProgress',
597
- effects: [
598
- {
599
- key: 'particle-canvas',
600
- customEffect: (element, progress) => {
601
- const particles = element.querySelectorAll('.particle');
602
- particles.forEach((particle, index) => {
603
- const delay = index * 0.1;
604
- const adjustedProgress = Math.max(0, Math.min(1, (progress - delay) / (1 - delay)));
605
- const rotation = adjustedProgress * 360;
606
- const scale = 0.5 + (adjustedProgress * 0.5);
607
- const translateY = (1 - adjustedProgress) * 200;
608
-
609
- particle.style.transform = `
610
- translateY(${translateY}px)
611
- rotate(${rotation}deg)
612
- scale(${scale})
613
- `;
614
- particle.style.opacity = adjustedProgress;
615
- });
616
- },
617
- rangeStart: { name: 'entry', offset: { unit: 'percentage', value: 0 } },
618
- rangeEnd: { name: 'exit', offset: { unit: 'percentage', value: 100 } },
619
- fill: 'both',
620
- effectId: 'particle-scroll'
621
- }
622
- ]
623
- }
624
- ```
625
-
626
- ---
627
-
628
- ## Rule 8: Range-Based Entry Animation Control with Custom Effects
629
-
630
- **Use Case**: JavaScript-powered entrance animations with programmatic control for complex entry sequences (e.g., dynamic counters, interactive reveals, calculated animations, progressive loading effects)
631
-
632
- **When to Apply**:
633
-
634
- - For entrance animations requiring calculations
635
- - When creating dynamic content reveals
636
- - For interactive entrance sequences
637
- - When standard keyframes cannot achieve the desired effect
638
-
639
- **Pattern**:
640
-
641
- ```typescript
642
- {
643
- key: '[SOURCE_KEY]',
644
- trigger: 'viewProgress',
645
- effects: [
646
- {
647
- key: '[TARGET_KEY]',
648
- customEffect: (element, progress) => {
649
- // progress is 0-1 representing entry progress
650
- [ENTRY_ANIMATION_LOGIC]
651
- },
652
- rangeStart: { name: 'entry', offset: { unit: 'percentage', value: [ENTRY_START] } },
653
- rangeEnd: { name: 'entry', offset: { unit: 'percentage', value: [ENTRY_END] } },
654
- fill: 'both',
655
- effectId: '[UNIQUE_EFFECT_ID]'
656
- }
657
- ]
658
- }
659
- ```
660
-
661
- **Variables**:
662
-
663
- - `[ENTRY_ANIMATION_LOGIC]`: JavaScript code for custom entry animation
664
- - Other variables same as previous rules
665
-
666
- **Example - Dynamic Text Reveal**:
120
+ ### Example 3: Custom Effect (Dynamic Content)
667
121
 
668
122
  ```typescript
669
123
  {
@@ -690,145 +144,50 @@ Same as Rule 4, with focus on exit range
690
144
  }
691
145
  ```
692
146
 
693
- **Example - Progressive Chart Fill**:
694
-
695
- ```typescript
696
- {
697
- key: 'chart-container',
698
- trigger: 'viewProgress',
699
- effects: [
700
- {
701
- key: 'chart-bar',
702
- customEffect: (element, progress) => {
703
- const targetHeight = element.dataset.targetHeight || 100;
704
- const currentHeight = targetHeight * progress;
705
- const colorIntensity = Math.floor(255 * progress);
706
-
707
- element.style.height = `${currentHeight}px`;
708
- element.style.backgroundColor = `rgb(${255 - colorIntensity}, ${colorIntensity}, 100)`;
709
- element.style.boxShadow = `0 0 ${progress * 20}px rgba(0, ${colorIntensity}, 255, 0.5)`;
710
- },
711
- rangeStart: { name: 'entry', offset: { unit: 'percentage', value: 20 } },
712
- rangeEnd: { name: 'entry', offset: { unit: 'percentage', value: 90 } },
713
- fill: 'both',
714
- effectId: 'chart-fill'
715
- }
716
- ]
717
- }
718
- ```
719
-
720
- ---
721
-
722
- ## Rule 9: Range-Based Exit Animation Control with Custom Effects
723
-
724
- **Use Case**: JavaScript-powered exit animations with programmatic control for complex exit sequences (e.g., dynamic hiding effects, calculated dismissals, interactive fade-outs, progressive unloading effects)
725
-
726
- **When to Apply**:
727
-
728
- - For exit animations requiring calculations
729
- - When creating dynamic content hiding
730
- - For interactive exit sequences
731
- - When standard keyframes cannot achieve the desired exit effect
732
-
733
- **Pattern**:
734
-
735
- ```typescript
736
- {
737
- key: '[SOURCE_KEY]',
738
- trigger: 'viewProgress',
739
- effects: [
740
- {
741
- key: '[TARGET_KEY]',
742
- customEffect: (element, progress) => {
743
- // progress is 0-1 representing exit progress
744
- [EXIT_ANIMATION_LOGIC]
745
- },
746
- rangeStart: { name: 'exit', offset: { unit: 'percentage', value: [EXIT_START] } },
747
- rangeEnd: { name: 'exit', offset: { unit: 'percentage', value: [EXIT_END] } },
748
- fill: 'both',
749
- effectId: '[UNIQUE_EFFECT_ID]'
750
- }
751
- ]
752
- }
753
- ```
754
-
755
- **Variables**:
756
-
757
- - `[EXIT_ANIMATION_LOGIC]`: JavaScript code for custom exit animation
758
- - Other variables same as previous rules
147
+ ### Example 4: Multi-Range (Entry + Exit on the same element)
759
148
 
760
- **Example - Dissolve Effect Exit**:
149
+ Animating the same element in on scroll entry and out on scroll exit requires two separate effects within the same interaction — one scoped to the `entry` range, one to `exit`. This pattern is non-obvious because both effects share the same `key` but have different ranges.
761
150
 
762
151
  ```typescript
763
152
  {
764
- key: 'content-section',
153
+ key: 'feature-card',
765
154
  trigger: 'viewProgress',
766
155
  effects: [
156
+ // Animate IN as element enters viewport
767
157
  {
768
- key: 'dissolving-content',
769
- customEffect: (element, progress) => {
770
- const particles = element.querySelectorAll('.content-particle');
771
- const dissolveProgress = progress;
772
-
773
- particles.forEach((particle, index) => {
774
- const delay = (index / particles.length) * 0.5;
775
- const particleProgress = Math.max(0, (dissolveProgress - delay) / (1 - delay));
776
-
777
- particle.style.opacity = 1 - particleProgress;
778
- particle.style.transform = `
779
- translateY(${particleProgress * -100}px)
780
- rotate(${particleProgress * 180}deg)
781
- scale(${1 - particleProgress * 0.5})
782
- `;
783
- });
158
+ key: 'feature-card',
159
+ keyframeEffect: {
160
+ name: 'card-in',
161
+ keyframes: [
162
+ { opacity: '0', transform: 'translateY(40px)' },
163
+ { opacity: '1', transform: 'translateY(0)' }
164
+ ]
784
165
  },
785
- rangeStart: { name: 'exit', offset: { unit: 'percentage', value: 10 } },
786
- rangeEnd: { name: 'exit', offset: { unit: 'percentage', value: 90 } },
787
- fill: 'both',
788
- effectId: 'dissolve-exit'
789
- }
790
- ]
791
- }
792
- ```
793
-
794
- **Example - Data Visualization Exit**:
795
-
796
- ```typescript
797
- {
798
- key: 'data-visualization',
799
- trigger: 'viewProgress',
800
- effects: [
166
+ rangeStart: { name: 'entry', offset: { unit: 'percentage', value: 0 } },
167
+ rangeEnd: { name: 'entry', offset: { unit: 'percentage', value: 60 } },
168
+ easing: 'ease-out',
169
+ fill: 'both'
170
+ },
171
+ // Animate OUT as element exits viewport
801
172
  {
802
- key: 'data-point',
803
- customEffect: (element, progress) => {
804
- const dataPoints = element.closest('interact-element')?.querySelectorAll('.data-point') || [];
805
- const totalPoints = dataPoints.length;
806
- const elementIndex = Array.from(dataPoints).indexOf(element);
807
-
808
- // Staggered exit based on data point position
809
- const staggerDelay = (elementIndex / totalPoints) * 0.3;
810
- const adjustedProgress = Math.max(0, (progress - staggerDelay) / (1 - staggerDelay));
811
-
812
- const scale = 1 - (adjustedProgress * 0.8);
813
- const rotation = adjustedProgress * 720; // Two full rotations
814
- const opacity = 1 - adjustedProgress;
815
-
816
- element.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
817
- element.style.opacity = opacity;
818
- element.style.filter = `blur(${adjustedProgress * 10}px)`;
173
+ key: 'feature-card',
174
+ keyframeEffect: {
175
+ name: 'card-out',
176
+ keyframes: [
177
+ { opacity: '1', transform: 'translateY(0)' },
178
+ { opacity: '0', transform: 'translateY(-40px)' }
179
+ ]
819
180
  },
820
- rangeStart: { name: 'exit', offset: { unit: 'percentage', value: 0 } },
821
- rangeEnd: { name: 'exit', offset: { unit: 'percentage', value: 80 } },
822
- fill: 'both',
823
- effectId: 'data-exit'
181
+ rangeStart: { name: 'exit', offset: { unit: 'percentage', value: 40 } },
182
+ rangeEnd: { name: 'exit', offset: { unit: 'percentage', value: 100 } },
183
+ easing: 'ease-in',
184
+ fill: 'both'
824
185
  }
825
186
  ]
826
187
  }
827
188
  ```
828
189
 
829
- ---
830
-
831
- ## Advanced Patterns and Combinations
190
+ ## Advanced Patterns
832
191
 
833
192
  ### Multi-Range ViewProgress Effects
834
193
 
@@ -890,49 +249,58 @@ Combining different ranges for complex scroll animations:
890
249
 
891
250
  ### ViewProgress with Conditional Behavior
892
251
 
893
- Responsive scroll animations:
252
+ Use interact `conditions` for responsive scroll animations and `prefers-reduced-motion`. Condition IDs are user-defined strings — they must be declared in the top-level `conditions` map before being referenced in an interaction.
894
253
 
895
254
  ```typescript
896
255
  {
897
- key: 'responsive-parallax',
898
- trigger: 'viewProgress',
899
- conditions: ['desktop-only', 'prefers-motion'],
900
- effects: [
901
- {
902
- key: 'parallax-bg',
903
- keyframeEffect: {
904
- name: 'parallax-bg',
905
- keyframes: [
906
- { transform: 'translateY(0)' },
907
- { transform: 'translateY(-300px)' }
908
- ]
909
- },
910
- rangeStart: { name: 'cover', offset: { unit: 'percentage', value: 0 } },
911
- rangeEnd: { name: 'cover', offset: { unit: 'percentage', value: 100 } },
912
- easing: 'linear',
913
- fill: 'both'
914
- }
915
- ]
916
- },
917
- // Simplified version for mobile
918
- {
919
- key: 'responsive-parallax',
920
- trigger: 'viewProgress',
921
- conditions: ['mobile-only'],
922
- effects: [
923
- {
924
- key: 'parallax-bg',
925
- keyframeEffect: {
926
- name: 'fade-out-bg',
927
- keyframes: [
928
- { opacity: '1' },
929
- { opacity: '0.7' }
930
- ]
931
- },
932
- rangeStart: { name: 'exit', offset: { unit: 'percentage', value: 0 } },
933
- rangeEnd: { name: 'exit', offset: { unit: 'percentage', value: 100 } },
934
- easing: 'linear',
935
- fill: 'both'
256
+ conditions: {
257
+ 'desktop-only': { type: 'media', predicate: '(min-width: 768px)' },
258
+ 'prefers-motion': { type: 'media', predicate: '(prefers-reduced-motion: no-preference)' },
259
+ 'mobile-only': { type: 'media', predicate: '(max-width: 767px)' },
260
+ },
261
+ interactions: [
262
+ {
263
+ key: 'responsive-parallax',
264
+ trigger: 'viewProgress',
265
+ conditions: ['desktop-only', 'prefers-motion'],
266
+ effects: [
267
+ {
268
+ key: 'parallax-bg',
269
+ keyframeEffect: {
270
+ name: 'parallax-bg',
271
+ keyframes: [
272
+ { transform: 'translateY(0)' },
273
+ { transform: 'translateY(-300px)' }
274
+ ]
275
+ },
276
+ rangeStart: { name: 'cover', offset: { unit: 'percentage', value: 0 } },
277
+ rangeEnd: { name: 'cover', offset: { unit: 'percentage', value: 100 } },
278
+ easing: 'linear',
279
+ fill: 'both'
280
+ }
281
+ ]
282
+ },
283
+ // Simplified fallback for mobile
284
+ {
285
+ key: 'responsive-parallax',
286
+ trigger: 'viewProgress',
287
+ conditions: ['mobile-only'],
288
+ effects: [
289
+ {
290
+ key: 'parallax-bg',
291
+ keyframeEffect: {
292
+ name: 'fade-out-bg',
293
+ keyframes: [
294
+ { opacity: '1' },
295
+ { opacity: '0.7' }
296
+ ]
297
+ },
298
+ rangeStart: { name: 'exit', offset: { unit: 'percentage', value: 0 } },
299
+ rangeEnd: { name: 'exit', offset: { unit: 'percentage', value: 100 } },
300
+ easing: 'linear',
301
+ fill: 'both'
302
+ }
303
+ ]
936
304
  }
937
305
  ]
938
306
  }
@@ -993,92 +361,18 @@ Orchestrating multiple elements with viewProgress:
993
361
  }
994
362
  ```
995
363
 
996
- ---
997
-
998
- ## Best Practices for ViewProgress Interactions
999
-
1000
- ### Performance Guidelines
1001
-
1002
- 1. **Use `linear` easing** for most scroll effects to avoid jarring transitions
1003
- 2. **Prefer `transform`, `filter`, and `opacity`** properties for hardware acceleration
1004
-
1005
- ### Range Configuration Guidelines
1006
-
1007
- 1. **Use appropriate range names**:
1008
- - `entry`: For animations that happen as element enters viewport
1009
- - `cover`: For animations while element is intersecting viewport
1010
- - `exit`: For animations as element leaves viewport
1011
- - `contain`: For animations while element is contained within viewport
1012
-
1013
- 2. **Offset Guidelines**:
1014
- - **0-100 values**: Represent percentage of the range
1015
- - **Start with broad ranges** (0-100) then refine
1016
- - **Use smaller ranges** (20-80) for more controlled animations
1017
- - **Avoid overlapping ranges** to prevent conflicting animations
1018
- - **Use 0-50% cover range or 0-100% entry range** for entry animations
1019
- - **Use 50-100% cover range or 0-100% exit range** for exit animations
1020
-
1021
- ### User Experience Guidelines
1022
-
1023
- 1. **Keep scroll animations subtle** to avoid motion sickness
1024
- 2. **Ensure content remains readable** during animations
1025
- 3. **Use progressive enhancement** - ensure content works without animations
1026
- 4. **Test on various devices** for performance and smoothness
1027
-
1028
- ### Accessibility Considerations
1029
-
1030
- 1. **Respect `prefers-reduced-motion`** for all scroll animations
1031
- 2. **Provide alternatives** for motion-sensitive users
1032
- 3. **Don't rely solely on scroll animations** for important content
1033
- 4. **Ensure keyboard navigation** still works with scroll effects
1034
-
1035
- ### Common Use Cases by Pattern
1036
-
1037
- **Parallax/Continuous (Rules 1, 4, 7)**:
1038
-
1039
- - Background image parallax
1040
- - Floating decorative elements
1041
- - Continuous progress indicators
1042
- - Multi-layer depth effects
1043
- - Scroll-responsive backgrounds
1044
-
1045
- **Entry Animation (Rules 2, 5, 8)**:
1046
-
1047
- - Content reveals on scroll
1048
- - Progressive image loading
1049
- - Element introductions
1050
- - Staggered content appearance
1051
-
1052
- **Exit Animation (Rules 3, 6, 9)**:
1053
-
1054
- - Hero content fade-out
1055
- - Navigation hiding
1056
- - Content dismissals
1057
- - Scroll-out transitions
1058
- - Element cleanup effects
1059
-
1060
- ### Troubleshooting Common Issues
1061
-
1062
- **Janky scroll performance**:
1063
-
1064
- - Use hardware-accelerated properties only
1065
- - Simplify custom effect calculations
1066
- - Test on lower-end devices
1067
-
1068
- **Unexpected animation behavior**:
1069
-
1070
- - Check range configurations match intended behavior
1071
- - Verify source element visibility throughout scroll
1072
- - Ensure target elements exist and are selectable
1073
- - Test range offset values
364
+ ## Best Practices
1074
365
 
1075
- **Poor visual results**:
366
+ ### Interact-Specific
1076
367
 
1077
- - Adjust easing functions for scroll context
1078
- - Fine-tune range start/end percentages
1079
- - Consider element positioning and layering
1080
- - Test across different content heights
368
+ 1. **Respect `prefers-reduced-motion`** via interact `conditions`: use `'prefers-motion'` so scroll animations run only when the user has not requested reduced motion.
369
+ 2. **Use `linear` easing** for most scroll effects; non-linear easing can feel jarring as scroll position changes.
370
+ 3. **Range configuration:** Verify source element remains visible throughout the scroll range. If the source is hidden or in a frozen stacking context, the ViewTimeline constraint may not update correctly.
371
+ 4. **Avoid overlapping ranges** on the same target to prevent conflicting animations.
372
+ 5. **Entry/exit timing:** Use 0–50% cover or 0–100% entry for entrances; 50–100% cover or 0–100% exit for exits. Start with broad ranges (0–100) then refine.
373
+ 6. **customEffect:** Use `element.closest('interact-element')` when querying related DOM within the callback; target elements must exist when the effect runs.
1081
374
 
1082
- ---
375
+ ### Troubleshooting
1083
376
 
1084
- These rules provide comprehensive coverage for ViewProgress trigger interactions in `@wix/interact`, supporting all range types (entry, cover, contain, exit) and effect types (named, keyframe, custom) as outlined in the development plan Stage 1.4.
377
+ - **Unexpected behavior:** Check range names match intent; verify source visibility; ensure target elements exist.
378
+ - **Janky custom effects:** Simplify calculations; avoid layout-triggering reads in the callback.