@wix/interact 2.0.3 → 2.1.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/es/index.js +1 -1
- package/dist/es/react.js +2 -2
- package/dist/es/web.js +2 -2
- package/dist/{index-BfcN_rkn.mjs → index-BP07b-Y1.mjs} +1202 -751
- package/dist/index-BP07b-Y1.mjs.map +1 -0
- package/dist/index-D4orAyUu.js +18 -0
- package/dist/index-D4orAyUu.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/types/core/Interact.d.ts +12 -1
- package/dist/types/core/Interact.d.ts.map +1 -1
- package/dist/types/core/add.d.ts.map +1 -1
- package/dist/types/core/remove.d.ts.map +1 -1
- package/dist/types/handlers/animationEnd.d.ts +1 -1
- package/dist/types/handlers/animationEnd.d.ts.map +1 -1
- package/dist/types/handlers/effectHandlers.d.ts +2 -1
- package/dist/types/handlers/effectHandlers.d.ts.map +1 -1
- package/dist/types/handlers/eventTrigger.d.ts +1 -1
- package/dist/types/handlers/eventTrigger.d.ts.map +1 -1
- package/dist/types/handlers/viewEnter.d.ts +1 -1
- package/dist/types/handlers/viewEnter.d.ts.map +1 -1
- package/dist/types/types.d.ts +29 -2
- package/dist/types/types.d.ts.map +1 -1
- package/docs/api/types.md +114 -0
- package/docs/examples/README.md +10 -1
- package/docs/examples/list-patterns.md +148 -0
- package/docs/guides/README.md +5 -0
- package/docs/guides/sequences.md +421 -0
- package/package.json +2 -2
- package/rules/MASTER-CLEANUP-PLAN.md +286 -0
- package/rules/click.md +124 -32
- package/rules/full-lean.md +93 -7
- package/rules/hover.md +142 -153
- package/rules/integration.md +83 -82
- package/rules/pointermove.md +32 -57
- package/rules/scroll-list.md +82 -280
- package/rules/viewenter.md +158 -253
- package/rules/viewprogress.md +139 -845
- package/dist/index-BfcN_rkn.mjs.map +0 -1
- package/dist/index-HXLBEIjG.js +0 -18
- package/dist/index-HXLBEIjG.js.map +0 -1
package/rules/viewprogress.md
CHANGED
|
@@ -1,387 +1,98 @@
|
|
|
1
1
|
# ViewProgress Trigger Rules for @wix/interact
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Core Concept
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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: '
|
|
24
|
+
fill: '[FILL_MODE]',
|
|
33
25
|
effectId: '[UNIQUE_EFFECT_ID]'
|
|
34
26
|
}
|
|
35
27
|
]
|
|
36
28
|
}
|
|
37
29
|
```
|
|
38
30
|
|
|
39
|
-
|
|
31
|
+
## Variable Key
|
|
40
32
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
**
|
|
49
|
+
**Offset semantics:** Positive offset values move the effective range forward along the scroll axis. 0 = start of range, 100 = end.
|
|
52
50
|
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
|
|
59
|
+
## Range Reference
|
|
75
60
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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
|
-
|
|
73
|
+
## Examples
|
|
99
74
|
|
|
100
|
-
|
|
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: '
|
|
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: '
|
|
83
|
+
key: 'hero-background',
|
|
165
84
|
namedEffect: {
|
|
166
|
-
type: '
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: '
|
|
153
|
+
key: 'feature-card',
|
|
765
154
|
trigger: 'viewProgress',
|
|
766
155
|
effects: [
|
|
156
|
+
// Animate IN as element enters viewport
|
|
767
157
|
{
|
|
768
|
-
key: '
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
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: '
|
|
786
|
-
rangeEnd: { name: '
|
|
787
|
-
|
|
788
|
-
|
|
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: '
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
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:
|
|
821
|
-
rangeEnd: { name: 'exit', offset: { unit: 'percentage', value:
|
|
822
|
-
|
|
823
|
-
|
|
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
|
-
|
|
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
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
},
|
|
917
|
-
|
|
918
|
-
{
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
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
|
-
|
|
366
|
+
### Interact-Specific
|
|
1076
367
|
|
|
1077
|
-
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
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
|
-
|
|
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.
|