@wix/interact 2.1.3 → 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-ByLXasWO.mjs +2832 -0
- 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-BtEG0cjF.mjs +0 -2791
- package/dist/index-BtEG0cjF.mjs.map +0 -1
- package/dist/index-ErMKtmX2.js +0 -18
- package/dist/index-ErMKtmX2.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/pointermove.md
CHANGED
|
@@ -1,1074 +1,148 @@
|
|
|
1
1
|
# PointerMove Trigger Rules for @wix/interact
|
|
2
2
|
|
|
3
|
-
These rules help generate pointer-driven interactions using
|
|
3
|
+
These rules help generate pointer-driven interactions using `@wix/interact`. PointerMove triggers create real-time animations that respond to mouse movement over elements or the entire viewport.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Table of Contents
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- [Trigger Source Elements with `hitArea: 'self'`](#trigger-source-elements-with-hitarea-self)
|
|
8
|
+
- [PointerMoveParams](#pointermoveparams)
|
|
9
|
+
- [Progress Object Structure](#progress-object-structure)
|
|
10
|
+
- [Centering with `centeredToTarget`](#centering-with-centeredtotarget)
|
|
11
|
+
- [Device Conditions](#device-conditions)
|
|
12
|
+
- [Rule 1: namedEffect](#rule-1-namedeffect)
|
|
13
|
+
- [Rule 2: keyframeEffect with Single Axis](#rule-2-keyframeeffect-with-single-axis)
|
|
14
|
+
- [Rule 3: Two keyframeEffects with Two Axes and `composite`](#rule-3-two-keyframeeffects-with-two-axes-and-composite)
|
|
15
|
+
- [Rule 4: customEffect](#rule-4-customeffect)
|
|
8
16
|
|
|
9
|
-
|
|
17
|
+
## Trigger Source Elements with `hitArea: 'self'`
|
|
10
18
|
|
|
11
|
-
|
|
12
|
-
2. **`customEffect`** (Advanced): Custom function receiving the 2D progress object for full control
|
|
13
|
-
3. **`keyframeEffect`** (Single-axis): The pointer position on a single axis is mapped to linear 0-1 progress for keyframe animations. Use `axis: 'x'` or `axis: 'y'` (defaults to `'y'`)
|
|
19
|
+
When using `hitArea: 'self'`, the source element is the hit area for pointer tracking:
|
|
14
20
|
|
|
15
|
-
|
|
21
|
+
- The source element **MUST NOT** have `pointer-events: none` — it needs to receive pointer events.
|
|
22
|
+
- **CRITICAL**: MUST AVOID using the same element as both source and target with effects that change size or position (e.g. `transform: translate(…)`, `scale(…)`). The transform shifts the hit area, causing jittery re-entry cycles. Instead, use `selector` to target a child element for the animation.
|
|
16
23
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
| Value | Behavior | Best For |
|
|
20
|
-
| -------- | ----------------------------------------------------- | ---------------------------------------- |
|
|
21
|
-
| `'self'` | Tracks mouse within the source element's bounds only | Local hover effects, card interactions |
|
|
22
|
-
| `'root'` | Tracks mouse anywhere in the viewport (document root) | Global cursor followers, ambient effects |
|
|
23
|
-
|
|
24
|
-
### Progress Object Structure (for `customEffect`)
|
|
25
|
-
|
|
26
|
-
When using `customEffect` with `pointerMove`, the progress parameter is an object:
|
|
27
|
-
|
|
28
|
-
```typescript
|
|
29
|
-
type Progress = {
|
|
30
|
-
x: number; // 0-1: horizontal position (0 = left edge, 1 = right edge)
|
|
31
|
-
y: number; // 0-1: vertical position (0 = top edge, 1 = bottom edge)
|
|
32
|
-
v?: {
|
|
33
|
-
// Velocity (optional)
|
|
34
|
-
x: number; // Horizontal velocity
|
|
35
|
-
y: number; // Vertical velocity
|
|
36
|
-
};
|
|
37
|
-
active?: boolean; // Whether mouse is currently in the hit area
|
|
38
|
-
};
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
### Centering with `centeredToTarget`
|
|
42
|
-
|
|
43
|
-
Controls how the progress range is calculated:
|
|
44
|
-
|
|
45
|
-
| Value | Behavior | Use When |
|
|
46
|
-
| ------- | -------------------------------------------------- | ---------------------------------------- |
|
|
47
|
-
| `true` | Centers the coordinate range at the target element | Source and target are different elements |
|
|
48
|
-
| `false` | Uses source element bounds for calculations | Cursor followers, global effects |
|
|
49
|
-
|
|
50
|
-
## Rule 1: Single Element Pointer Effects with 3D Named Effects
|
|
51
|
-
|
|
52
|
-
**Use Case**: Interactive 3D transformations on individual elements that respond to mouse position (e.g., card tilting, 3D product showcases, interactive buttons)
|
|
53
|
-
|
|
54
|
-
**When to Apply**:
|
|
55
|
-
|
|
56
|
-
- For interactive card hover effects
|
|
57
|
-
- When creating 3D product showcases
|
|
58
|
-
- For engaging button interactions
|
|
59
|
-
- When building interactive UI elements that respond to mouse movement
|
|
60
|
-
|
|
61
|
-
**Pattern**:
|
|
62
|
-
|
|
63
|
-
```typescript
|
|
64
|
-
{
|
|
65
|
-
key: '[SOURCE_KEY]',
|
|
66
|
-
trigger: 'pointerMove',
|
|
67
|
-
params: {
|
|
68
|
-
hitArea: '[HIT_AREA]'
|
|
69
|
-
},
|
|
70
|
-
effects: [
|
|
71
|
-
{
|
|
72
|
-
key: '[TARGET_KEY]',
|
|
73
|
-
namedEffect: {
|
|
74
|
-
type: '[3D_EFFECT_TYPE]',
|
|
75
|
-
[EFFECT_PROPERTIES]
|
|
76
|
-
},
|
|
77
|
-
centeredToTarget: [CENTERED_TO_TARGET],
|
|
78
|
-
effectId: '[UNIQUE_EFFECT_ID]'
|
|
79
|
-
}
|
|
80
|
-
]
|
|
81
|
-
}
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
**Variables**:
|
|
85
|
-
|
|
86
|
-
- `[SOURCE_KEY]`: Unique identifier for source element that tracks mouse movement
|
|
87
|
-
- `[TARGET_KEY]`: Unique identifier for target element to animate (can be same as source or different)
|
|
88
|
-
- `[HIT_AREA]`: 'self' (mouse within source element) or 'root' (mouse anywhere in viewport)
|
|
89
|
-
- `[3D_EFFECT_TYPE]`: 'Tilt3DMouse', 'Track3DMouse', 'SwivelMouse'
|
|
90
|
-
- `[EFFECT_PROPERTIES]`: Named effect specific properties (angle, perspective, direction, etc.)
|
|
91
|
-
- `[CENTERED_TO_TARGET]`: true (center range at target) or false (use source element bounds)
|
|
92
|
-
- `[UNIQUE_EFFECT_ID]`: Optional unique identifier
|
|
93
|
-
|
|
94
|
-
**Example - Interactive Product Card**:
|
|
95
|
-
|
|
96
|
-
```typescript
|
|
97
|
-
{
|
|
98
|
-
key: 'product-card',
|
|
99
|
-
trigger: 'pointerMove',
|
|
100
|
-
params: {
|
|
101
|
-
hitArea: 'self'
|
|
102
|
-
},
|
|
103
|
-
effects: [
|
|
104
|
-
{
|
|
105
|
-
key: 'product-card',
|
|
106
|
-
namedEffect: {
|
|
107
|
-
type: 'Tilt3DMouse',
|
|
108
|
-
angle: 15,
|
|
109
|
-
perspective: 1000
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
]
|
|
113
|
-
}
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
---
|
|
117
|
-
|
|
118
|
-
## Rule 2: Single Element Pointer Effects with Movement Named Effects
|
|
119
|
-
|
|
120
|
-
**Use Case**: Cursor-following and position-tracking effects on individual elements (e.g., floating elements, cursor followers, responsive decorations)
|
|
121
|
-
|
|
122
|
-
**When to Apply**:
|
|
123
|
-
|
|
124
|
-
- For cursor-following elements
|
|
125
|
-
- When creating floating responsive decorations
|
|
126
|
-
- For interactive element positioning
|
|
127
|
-
- When building mouse-aware UI components
|
|
128
|
-
|
|
129
|
-
**Pattern**:
|
|
130
|
-
|
|
131
|
-
```typescript
|
|
132
|
-
{
|
|
133
|
-
key: '[SOURCE_KEY]',
|
|
134
|
-
trigger: 'pointerMove',
|
|
135
|
-
params: {
|
|
136
|
-
hitArea: '[HIT_AREA]'
|
|
137
|
-
},
|
|
138
|
-
effects: [
|
|
139
|
-
{
|
|
140
|
-
key: '[TARGET_KEY]',
|
|
141
|
-
namedEffect: {
|
|
142
|
-
type: '[MOVEMENT_EFFECT_TYPE]',
|
|
143
|
-
distance: { value: [DISTANCE_VALUE], unit: '[DISTANCE_UNIT]' },
|
|
144
|
-
axis: '[AXIS_CONSTRAINT]'
|
|
145
|
-
},
|
|
146
|
-
centeredToTarget: [CENTERED_TO_TARGET],
|
|
147
|
-
effectId: '[UNIQUE_EFFECT_ID]'
|
|
148
|
-
}
|
|
149
|
-
]
|
|
150
|
-
}
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
**Variables**:
|
|
154
|
-
|
|
155
|
-
- `[MOVEMENT_EFFECT_TYPE]`: 'TrackMouse', 'AiryMouse', 'BounceMouse'
|
|
156
|
-
- `[DISTANCE_VALUE]`: Numeric value for movement distance
|
|
157
|
-
- `[DISTANCE_UNIT]`: 'px', 'percentage', 'vw', 'vh'
|
|
158
|
-
- `[AXIS_CONSTRAINT]`: 'both', 'horizontal', 'vertical'
|
|
159
|
-
- Other variables same as Rule 1
|
|
160
|
-
|
|
161
|
-
**Example - Cursor Follower Element**:
|
|
162
|
-
|
|
163
|
-
```typescript
|
|
164
|
-
{
|
|
165
|
-
key: 'cursor-follower',
|
|
166
|
-
trigger: 'pointerMove',
|
|
167
|
-
params: {
|
|
168
|
-
hitArea: 'root'
|
|
169
|
-
},
|
|
170
|
-
effects: [
|
|
171
|
-
{
|
|
172
|
-
namedEffect: {
|
|
173
|
-
type: 'TrackMouse',
|
|
174
|
-
distance: { value: 50, unit: 'percentage' },
|
|
175
|
-
axis: 'both'
|
|
176
|
-
},
|
|
177
|
-
centeredToTarget: false
|
|
178
|
-
}
|
|
179
|
-
]
|
|
180
|
-
}
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
**Example - Floating Decoration**:
|
|
184
|
-
|
|
185
|
-
```typescript
|
|
186
|
-
{
|
|
187
|
-
key: 'hero-section',
|
|
188
|
-
trigger: 'pointerMove',
|
|
189
|
-
params: {
|
|
190
|
-
hitArea: 'self'
|
|
191
|
-
},
|
|
192
|
-
effects: [
|
|
193
|
-
{
|
|
194
|
-
key: 'floating-element',
|
|
195
|
-
namedEffect: {
|
|
196
|
-
type: 'AiryMouse',
|
|
197
|
-
distance: { value: 30, unit: 'px' },
|
|
198
|
-
axis: 'both'
|
|
199
|
-
},
|
|
200
|
-
centeredToTarget: true,
|
|
201
|
-
effectId: 'hero-float'
|
|
202
|
-
}
|
|
203
|
-
]
|
|
204
|
-
}
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
---
|
|
208
|
-
|
|
209
|
-
## Rule 3: Single Element Pointer Effects with Scale Named Effects
|
|
210
|
-
|
|
211
|
-
**Use Case**: Dynamic scaling and deformation effects on individual elements based on mouse position (e.g., interactive scaling, organic transformations, blob effects)
|
|
212
|
-
|
|
213
|
-
**When to Apply**:
|
|
214
|
-
|
|
215
|
-
- For interactive scaling buttons
|
|
216
|
-
- When creating organic blob-like interactions
|
|
217
|
-
- For dynamic size responsive elements
|
|
218
|
-
- When building creative morphing interfaces
|
|
219
|
-
|
|
220
|
-
**Pattern**:
|
|
221
|
-
|
|
222
|
-
```typescript
|
|
223
|
-
{
|
|
224
|
-
key: '[SOURCE_KEY]',
|
|
225
|
-
trigger: 'pointerMove',
|
|
226
|
-
params: {
|
|
227
|
-
hitArea: '[HIT_AREA]'
|
|
228
|
-
},
|
|
229
|
-
effects: [
|
|
230
|
-
{
|
|
231
|
-
key: '[TARGET_KEY]',
|
|
232
|
-
namedEffect: {
|
|
233
|
-
type: '[SCALE_EFFECT_TYPE]',
|
|
234
|
-
[SCALE_PROPERTIES]
|
|
235
|
-
},
|
|
236
|
-
centeredToTarget: [CENTERED_TO_TARGET],
|
|
237
|
-
effectId: '[UNIQUE_EFFECT_ID]'
|
|
238
|
-
}
|
|
239
|
-
]
|
|
240
|
-
}
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
**Variables**:
|
|
244
|
-
|
|
245
|
-
- `[SCALE_EFFECT_TYPE]`: 'ScaleMouse', 'BlobMouse', 'SkewMouse'
|
|
246
|
-
- `[SCALE_PROPERTIES]`: Effect-specific properties (scale, distance, axis)
|
|
247
|
-
- Other variables same as Rule 1
|
|
248
|
-
|
|
249
|
-
**Example - Interactive Scale Button**:
|
|
250
|
-
|
|
251
|
-
```typescript
|
|
252
|
-
{
|
|
253
|
-
key: 'scale-button',
|
|
254
|
-
trigger: 'pointerMove',
|
|
255
|
-
params: {
|
|
256
|
-
hitArea: 'self'
|
|
257
|
-
},
|
|
258
|
-
effects: [
|
|
259
|
-
{
|
|
260
|
-
key: 'scale-button',
|
|
261
|
-
namedEffect: {
|
|
262
|
-
type: 'ScaleMouse',
|
|
263
|
-
scale: 1.1,
|
|
264
|
-
distance: { value: 100, unit: 'px' },
|
|
265
|
-
axis: 'both'
|
|
266
|
-
},
|
|
267
|
-
centeredToTarget: true
|
|
268
|
-
}
|
|
269
|
-
]
|
|
270
|
-
}
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
**Example - Organic Blob Effect**:
|
|
274
|
-
|
|
275
|
-
```typescript
|
|
276
|
-
{
|
|
277
|
-
key: 'blob-container',
|
|
278
|
-
trigger: 'pointerMove',
|
|
279
|
-
params: {
|
|
280
|
-
hitArea: 'self'
|
|
281
|
-
},
|
|
282
|
-
effects: [
|
|
283
|
-
{
|
|
284
|
-
key: 'blob-shape',
|
|
285
|
-
namedEffect: {
|
|
286
|
-
type: 'BlobMouse',
|
|
287
|
-
intensity: 0.8,
|
|
288
|
-
smoothness: 0.6
|
|
289
|
-
},
|
|
290
|
-
effectId: 'blob-morph'
|
|
291
|
-
}
|
|
292
|
-
]
|
|
293
|
-
}
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
---
|
|
297
|
-
|
|
298
|
-
## Rule 4: Single Element Pointer Effects with Visual Named Effects
|
|
299
|
-
|
|
300
|
-
**Use Case**: Visual effect transformations on individual elements based on mouse position (e.g., motion blur, rotation effects, visual filters)
|
|
301
|
-
|
|
302
|
-
**When to Apply**:
|
|
303
|
-
|
|
304
|
-
- For creative visual interfaces
|
|
305
|
-
- When adding motion blur to interactions
|
|
306
|
-
- For rotation-based mouse effects
|
|
307
|
-
- When creating dynamic visual feedback
|
|
308
|
-
|
|
309
|
-
**Pattern**:
|
|
310
|
-
|
|
311
|
-
```typescript
|
|
312
|
-
{
|
|
313
|
-
key: '[SOURCE_KEY]',
|
|
314
|
-
trigger: 'pointerMove',
|
|
315
|
-
params: {
|
|
316
|
-
hitArea: '[HIT_AREA]'
|
|
317
|
-
},
|
|
318
|
-
effects: [
|
|
319
|
-
{
|
|
320
|
-
key: '[TARGET_KEY]',
|
|
321
|
-
namedEffect: {
|
|
322
|
-
type: '[VISUAL_EFFECT_TYPE]',
|
|
323
|
-
[VISUAL_PROPERTIES]
|
|
324
|
-
},
|
|
325
|
-
centeredToTarget: [CENTERED_TO_TARGET],
|
|
326
|
-
effectId: '[UNIQUE_EFFECT_ID]'
|
|
327
|
-
}
|
|
328
|
-
]
|
|
329
|
-
}
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
**Variables**:
|
|
333
|
-
|
|
334
|
-
- `[VISUAL_EFFECT_TYPE]`: 'BlurMouse', 'SpinMouse'
|
|
335
|
-
- `[VISUAL_PROPERTIES]`: Effect-specific properties (blur amount, rotation speed)
|
|
336
|
-
- Other variables same as Rule 1
|
|
337
|
-
|
|
338
|
-
**Example - Motion Blur Card**:
|
|
339
|
-
|
|
340
|
-
```typescript
|
|
341
|
-
{
|
|
342
|
-
key: 'motion-card',
|
|
343
|
-
trigger: 'pointerMove',
|
|
344
|
-
params: {
|
|
345
|
-
hitArea: 'self'
|
|
346
|
-
},
|
|
347
|
-
effects: [
|
|
348
|
-
{
|
|
349
|
-
key: 'motion-card',
|
|
350
|
-
namedEffect: {
|
|
351
|
-
type: 'BlurMouse',
|
|
352
|
-
blurAmount: 5,
|
|
353
|
-
motionIntensity: 0.7
|
|
354
|
-
},
|
|
355
|
-
centeredToTarget: true
|
|
356
|
-
}
|
|
357
|
-
]
|
|
358
|
-
}
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
**Example - Spinning Element**:
|
|
362
|
-
|
|
363
|
-
```typescript
|
|
364
|
-
{
|
|
365
|
-
key: 'spin-trigger',
|
|
366
|
-
trigger: 'pointerMove',
|
|
367
|
-
params: {
|
|
368
|
-
hitArea: 'self'
|
|
369
|
-
},
|
|
370
|
-
effects: [
|
|
371
|
-
{
|
|
372
|
-
key: 'spinning-icon',
|
|
373
|
-
namedEffect: {
|
|
374
|
-
type: 'SpinMouse',
|
|
375
|
-
rotationSpeed: 0.5,
|
|
376
|
-
direction: 'clockwise'
|
|
377
|
-
},
|
|
378
|
-
centeredToTarget: false,
|
|
379
|
-
effectId: 'icon-spin'
|
|
380
|
-
}
|
|
381
|
-
]
|
|
382
|
-
}
|
|
383
|
-
```
|
|
384
|
-
|
|
385
|
-
---
|
|
386
|
-
|
|
387
|
-
## Rule 5: Multi-Element Pointer Parallax Effects with Named Effects
|
|
388
|
-
|
|
389
|
-
**Use Case**: Coordinated pointer-driven animations across multiple elements creating layered parallax effects (e.g., multi-layer backgrounds, depth effects, coordinated element responses)
|
|
390
|
-
|
|
391
|
-
**When to Apply**:
|
|
392
|
-
|
|
393
|
-
- For multi-layer background effects
|
|
394
|
-
- When creating depth and parallax interactions
|
|
395
|
-
- For coordinated UI element responses
|
|
396
|
-
- When building immersive pointer-driven experiences
|
|
397
|
-
|
|
398
|
-
**Pattern**:
|
|
399
|
-
|
|
400
|
-
```typescript
|
|
401
|
-
{
|
|
402
|
-
key: '[CONTAINER_KEY]',
|
|
403
|
-
trigger: 'pointerMove',
|
|
404
|
-
params: {
|
|
405
|
-
hitArea: '[HIT_AREA]'
|
|
406
|
-
},
|
|
407
|
-
effects: [
|
|
408
|
-
{
|
|
409
|
-
key: '[BACKGROUND_LAYER_KEY]',
|
|
410
|
-
namedEffect: {
|
|
411
|
-
type: '[BACKGROUND_EFFECT_TYPE]',
|
|
412
|
-
distance: { value: [BACKGROUND_DISTANCE], unit: '[DISTANCE_UNIT]' }
|
|
413
|
-
},
|
|
414
|
-
centeredToTarget: [CENTERED_TO_TARGET]
|
|
415
|
-
},
|
|
416
|
-
{
|
|
417
|
-
key: '[MIDGROUND_LAYER_KEY]',
|
|
418
|
-
namedEffect: {
|
|
419
|
-
type: '[MIDGROUND_EFFECT_TYPE]',
|
|
420
|
-
distance: { value: [MIDGROUND_DISTANCE], unit: '[DISTANCE_UNIT]' }
|
|
421
|
-
},
|
|
422
|
-
centeredToTarget: [CENTERED_TO_TARGET]
|
|
423
|
-
},
|
|
424
|
-
{
|
|
425
|
-
key: '[FOREGROUND_LAYER_KEY]',
|
|
426
|
-
namedEffect: {
|
|
427
|
-
type: '[FOREGROUND_EFFECT_TYPE]',
|
|
428
|
-
distance: { value: [FOREGROUND_DISTANCE], unit: '[DISTANCE_UNIT]' }
|
|
429
|
-
},
|
|
430
|
-
centeredToTarget: [CENTERED_TO_TARGET]
|
|
431
|
-
}
|
|
432
|
-
]
|
|
433
|
-
}
|
|
434
|
-
```
|
|
435
|
-
|
|
436
|
-
**Variables**:
|
|
437
|
-
|
|
438
|
-
- `[CONTAINER_KEY]`: Unique identifier for container element tracking mouse
|
|
439
|
-
- `[*_LAYER_KEY]`: Unique identifier for different layer elements
|
|
440
|
-
- `[*_EFFECT_TYPE]`: Named effects for each layer (typically movement effects)
|
|
441
|
-
- `[*_DISTANCE]`: Movement distance for each layer (creating depth)
|
|
442
|
-
- Other variables same as previous rules
|
|
443
|
-
|
|
444
|
-
**Example - Parallax Card Layers**:
|
|
445
|
-
|
|
446
|
-
```typescript
|
|
447
|
-
{
|
|
448
|
-
key: 'parallax-card',
|
|
449
|
-
trigger: 'pointerMove',
|
|
450
|
-
params: {
|
|
451
|
-
hitArea: 'self'
|
|
452
|
-
},
|
|
453
|
-
effects: [
|
|
454
|
-
{
|
|
455
|
-
key: 'bg-layer',
|
|
456
|
-
namedEffect: {
|
|
457
|
-
type: 'AiryMouse',
|
|
458
|
-
distance: { value: 15, unit: 'px' },
|
|
459
|
-
axis: 'both'
|
|
460
|
-
},
|
|
461
|
-
centeredToTarget: true
|
|
462
|
-
},
|
|
463
|
-
{
|
|
464
|
-
key: 'mid-layer',
|
|
465
|
-
namedEffect: {
|
|
466
|
-
type: 'TrackMouse',
|
|
467
|
-
distance: { value: 25, unit: 'px' },
|
|
468
|
-
axis: 'both'
|
|
469
|
-
},
|
|
470
|
-
centeredToTarget: true
|
|
471
|
-
},
|
|
472
|
-
{
|
|
473
|
-
key: 'fg-layer',
|
|
474
|
-
namedEffect: {
|
|
475
|
-
type: 'BounceMouse',
|
|
476
|
-
distance: { value: 35, unit: 'px' },
|
|
477
|
-
axis: 'both'
|
|
478
|
-
},
|
|
479
|
-
centeredToTarget: true
|
|
480
|
-
}
|
|
481
|
-
]
|
|
482
|
-
}
|
|
483
|
-
```
|
|
484
|
-
|
|
485
|
-
**Example - Multi-Layer Hero Section**:
|
|
486
|
-
|
|
487
|
-
```typescript
|
|
488
|
-
{
|
|
489
|
-
key: 'hero-container',
|
|
490
|
-
trigger: 'pointerMove',
|
|
491
|
-
params: {
|
|
492
|
-
hitArea: 'self'
|
|
493
|
-
},
|
|
494
|
-
effects: [
|
|
495
|
-
{
|
|
496
|
-
key: 'hero-bg',
|
|
497
|
-
namedEffect: {
|
|
498
|
-
type: 'AiryMouse',
|
|
499
|
-
distance: { value: 20, unit: 'px' },
|
|
500
|
-
axis: 'both'
|
|
501
|
-
},
|
|
502
|
-
centeredToTarget: true
|
|
503
|
-
},
|
|
504
|
-
{
|
|
505
|
-
key: 'hero-content',
|
|
506
|
-
namedEffect: {
|
|
507
|
-
type: 'TrackMouse',
|
|
508
|
-
distance: { value: 40, unit: 'px' },
|
|
509
|
-
axis: 'horizontal'
|
|
510
|
-
},
|
|
511
|
-
centeredToTarget: true
|
|
512
|
-
},
|
|
513
|
-
{
|
|
514
|
-
key: 'hero-decorations',
|
|
515
|
-
namedEffect: {
|
|
516
|
-
type: 'ScaleMouse',
|
|
517
|
-
scale: 1.05,
|
|
518
|
-
distance: { value: 60, unit: 'px' }
|
|
519
|
-
},
|
|
520
|
-
centeredToTarget: true
|
|
521
|
-
}
|
|
522
|
-
]
|
|
523
|
-
}
|
|
524
|
-
```
|
|
525
|
-
|
|
526
|
-
---
|
|
527
|
-
|
|
528
|
-
## Rule 6: Coordinated Group Pointer Effects with Named Effects
|
|
529
|
-
|
|
530
|
-
**Use Case**: Synchronized pointer-driven animations across related elements with different responses (e.g., card grids, navigation menus, interactive galleries)
|
|
531
|
-
|
|
532
|
-
**When to Apply**:
|
|
533
|
-
|
|
534
|
-
- For interactive card grids
|
|
535
|
-
- When building responsive navigation systems
|
|
536
|
-
- For gallery hover effects
|
|
537
|
-
- When creating coordinated interface responses
|
|
538
|
-
|
|
539
|
-
**Pattern**:
|
|
540
|
-
|
|
541
|
-
```typescript
|
|
542
|
-
{
|
|
543
|
-
key: '[CONTAINER_KEY]',
|
|
544
|
-
trigger: 'pointerMove',
|
|
545
|
-
params: {
|
|
546
|
-
hitArea: '[HIT_AREA]'
|
|
547
|
-
},
|
|
548
|
-
effects: [
|
|
549
|
-
{
|
|
550
|
-
key: '[PRIMARY_ELEMENTS_KEY]',
|
|
551
|
-
namedEffect: {
|
|
552
|
-
type: '[PRIMARY_EFFECT_TYPE]',
|
|
553
|
-
[PRIMARY_EFFECT_PROPERTIES]
|
|
554
|
-
},
|
|
555
|
-
centeredToTarget: [PRIMARY_CENTERED]
|
|
556
|
-
},
|
|
557
|
-
{
|
|
558
|
-
key: '[SECONDARY_ELEMENTS_KEY]',
|
|
559
|
-
namedEffect: {
|
|
560
|
-
type: '[SECONDARY_EFFECT_TYPE]',
|
|
561
|
-
[SECONDARY_EFFECT_PROPERTIES]
|
|
562
|
-
},
|
|
563
|
-
centeredToTarget: [SECONDARY_CENTERED]
|
|
564
|
-
}
|
|
565
|
-
]
|
|
566
|
-
}
|
|
567
|
-
```
|
|
568
|
-
|
|
569
|
-
**Variables**:
|
|
570
|
-
|
|
571
|
-
- `[PRIMARY_ELEMENTS_KEY]`: Unique identifier for primary responsive elements
|
|
572
|
-
- `[SECONDARY_ELEMENTS_KEY]`: Unique identifier for secondary responsive elements
|
|
573
|
-
- `[PRIMARY_EFFECT_TYPE]`: Named effect for primary elements
|
|
574
|
-
- `[SECONDARY_EFFECT_TYPE]`: Named effect for secondary elements
|
|
575
|
-
- `[*_EFFECT_PROPERTIES]`: Properties specific to each effect type
|
|
576
|
-
- `[*_CENTERED]`: Centering configuration for each element group
|
|
577
|
-
- Other variables same as previous rules
|
|
578
|
-
|
|
579
|
-
**Example - Interactive Card Grid**:
|
|
580
|
-
|
|
581
|
-
```typescript
|
|
582
|
-
{
|
|
583
|
-
key: 'card-grid',
|
|
584
|
-
trigger: 'pointerMove',
|
|
585
|
-
params: {
|
|
586
|
-
hitArea: 'self'
|
|
587
|
-
},
|
|
588
|
-
effects: [
|
|
589
|
-
{
|
|
590
|
-
key: 'grid-card',
|
|
591
|
-
namedEffect: {
|
|
592
|
-
type: 'Tilt3DMouse',
|
|
593
|
-
angle: 12,
|
|
594
|
-
perspective: 1000
|
|
595
|
-
},
|
|
596
|
-
centeredToTarget: true
|
|
597
|
-
},
|
|
598
|
-
{
|
|
599
|
-
key: 'card-shadow',
|
|
600
|
-
namedEffect: {
|
|
601
|
-
type: 'AiryMouse',
|
|
602
|
-
distance: { value: 20, unit: 'px' },
|
|
603
|
-
axis: 'both'
|
|
604
|
-
},
|
|
605
|
-
centeredToTarget: true
|
|
606
|
-
}
|
|
607
|
-
]
|
|
608
|
-
}
|
|
609
|
-
```
|
|
610
|
-
|
|
611
|
-
**Example - Navigation Menu Response**:
|
|
612
|
-
|
|
613
|
-
```typescript
|
|
614
|
-
{
|
|
615
|
-
key: 'nav-container',
|
|
616
|
-
trigger: 'pointerMove',
|
|
617
|
-
params: {
|
|
618
|
-
hitArea: 'self'
|
|
619
|
-
},
|
|
620
|
-
effects: [
|
|
621
|
-
{
|
|
622
|
-
key: 'nav-item',
|
|
623
|
-
namedEffect: {
|
|
624
|
-
type: 'ScaleMouse',
|
|
625
|
-
scale: 1.05,
|
|
626
|
-
distance: { value: 80, unit: 'px' }
|
|
627
|
-
},
|
|
628
|
-
centeredToTarget: true
|
|
629
|
-
},
|
|
630
|
-
{
|
|
631
|
-
key: 'nav-indicator',
|
|
632
|
-
namedEffect: {
|
|
633
|
-
type: 'TrackMouse',
|
|
634
|
-
distance: { value: 15, unit: 'px' },
|
|
635
|
-
axis: 'horizontal'
|
|
636
|
-
},
|
|
637
|
-
centeredToTarget: false
|
|
638
|
-
}
|
|
639
|
-
]
|
|
640
|
-
}
|
|
641
|
-
```
|
|
642
|
-
|
|
643
|
-
---
|
|
644
|
-
|
|
645
|
-
## Rule 7: Global Cursor Follower Effects with Named Effects
|
|
646
|
-
|
|
647
|
-
**Use Case**: Page-wide cursor following elements that respond to mouse movement anywhere (e.g., custom cursors, global decorative followers, interactive overlays)
|
|
648
|
-
|
|
649
|
-
**When to Apply**:
|
|
650
|
-
|
|
651
|
-
- For custom cursor implementations
|
|
652
|
-
- When creating global interactive overlays
|
|
653
|
-
- For page-wide decorative followers
|
|
654
|
-
- When building immersive cursor experiences
|
|
655
|
-
|
|
656
|
-
**Pattern**:
|
|
657
|
-
|
|
658
|
-
```typescript
|
|
659
|
-
{
|
|
660
|
-
key: '[FOLLOWER_KEY]',
|
|
661
|
-
trigger: 'pointerMove',
|
|
662
|
-
params: {
|
|
663
|
-
hitArea: 'root'
|
|
664
|
-
},
|
|
665
|
-
effects: [
|
|
666
|
-
{
|
|
667
|
-
namedEffect: {
|
|
668
|
-
type: '[FOLLOWER_EFFECT_TYPE]',
|
|
669
|
-
distance: { value: [FOLLOWER_DISTANCE], unit: '[DISTANCE_UNIT]' },
|
|
670
|
-
[FOLLOWER_PROPERTIES]
|
|
671
|
-
},
|
|
672
|
-
centeredToTarget: false,
|
|
673
|
-
effectId: '[FOLLOWER_EFFECT_ID]'
|
|
674
|
-
}
|
|
675
|
-
]
|
|
676
|
-
}
|
|
677
|
-
```
|
|
678
|
-
|
|
679
|
-
**Variables**:
|
|
680
|
-
|
|
681
|
-
- `[FOLLOWER_KEY]`: Unique identifier for cursor follower element
|
|
682
|
-
- `[FOLLOWER_EFFECT_TYPE]`: 'TrackMouse', 'AiryMouse', 'BounceMouse'
|
|
683
|
-
- `[FOLLOWER_DISTANCE]`: Distance/lag for follower (0 for perfect following)
|
|
684
|
-
- `[FOLLOWER_PROPERTIES]`: Additional effect properties
|
|
685
|
-
- `[FOLLOWER_EFFECT_ID]`: Unique identifier for the follower effect
|
|
686
|
-
- Other variables same as previous rules
|
|
687
|
-
|
|
688
|
-
**Example - Custom Cursor Follower**:
|
|
689
|
-
|
|
690
|
-
```typescript
|
|
691
|
-
{
|
|
692
|
-
key: 'custom-cursor',
|
|
693
|
-
trigger: 'pointerMove',
|
|
694
|
-
params: {
|
|
695
|
-
hitArea: 'root'
|
|
696
|
-
},
|
|
697
|
-
effects: [
|
|
698
|
-
{
|
|
699
|
-
namedEffect: {
|
|
700
|
-
type: 'TrackMouse',
|
|
701
|
-
distance: { value: 0, unit: 'px' },
|
|
702
|
-
axis: 'both'
|
|
703
|
-
},
|
|
704
|
-
centeredToTarget: false,
|
|
705
|
-
effectId: 'global-cursor'
|
|
706
|
-
}
|
|
707
|
-
]
|
|
708
|
-
}
|
|
709
|
-
```
|
|
710
|
-
|
|
711
|
-
**Example - Floating Decoration Follower**:
|
|
712
|
-
|
|
713
|
-
```typescript
|
|
714
|
-
{
|
|
715
|
-
key: 'floating-decoration',
|
|
716
|
-
trigger: 'pointerMove',
|
|
717
|
-
params: {
|
|
718
|
-
hitArea: 'root'
|
|
719
|
-
},
|
|
720
|
-
effects: [
|
|
721
|
-
{
|
|
722
|
-
namedEffect: {
|
|
723
|
-
type: 'AiryMouse',
|
|
724
|
-
distance: { value: 50, unit: 'px' },
|
|
725
|
-
axis: 'both'
|
|
726
|
-
},
|
|
727
|
-
centeredToTarget: false,
|
|
728
|
-
effectId: 'decoration-follower'
|
|
729
|
-
}
|
|
730
|
-
]
|
|
731
|
-
}
|
|
732
|
-
```
|
|
733
|
-
|
|
734
|
-
---
|
|
735
|
-
|
|
736
|
-
## Rule 8: Custom Pointer Effects with customEffect
|
|
737
|
-
|
|
738
|
-
**Use Case**: When you need full control over pointer-driven animations that cannot be achieved with named effects, such as custom physics, complex multi-property animations, or unique visual transformations.
|
|
739
|
-
|
|
740
|
-
**When to Apply**:
|
|
741
|
-
|
|
742
|
-
- For custom physics-based animations
|
|
743
|
-
- When creating unique visual effects not covered by named effects
|
|
744
|
-
- When controlling WebGL/WebGPU effects or other JavaScript controlled effects
|
|
745
|
-
- For complex DOM manipulations based on mouse position
|
|
746
|
-
- When implementing grid-based or particle effects
|
|
747
|
-
- For animations requiring access to velocity data
|
|
748
|
-
|
|
749
|
-
**IMPORTANT**: Only use `customEffect` when `namedEffect` cannot achieve the desired result. Named effects are optimized and GPU-friendly.
|
|
750
|
-
|
|
751
|
-
**Pattern - Basic customEffect**:
|
|
752
|
-
|
|
753
|
-
```typescript
|
|
754
|
-
{
|
|
755
|
-
key: '[SOURCE_KEY]',
|
|
756
|
-
trigger: 'pointerMove',
|
|
757
|
-
params: {
|
|
758
|
-
hitArea: '[HIT_AREA]'
|
|
759
|
-
},
|
|
760
|
-
effects: [
|
|
761
|
-
{
|
|
762
|
-
key: '[TARGET_KEY]',
|
|
763
|
-
customEffect: (element, progress) => {
|
|
764
|
-
// progress.x: 0-1 horizontal position
|
|
765
|
-
// progress.y: 0-1 vertical position
|
|
766
|
-
// progress.v: { x, y } velocity (optional)
|
|
767
|
-
// progress.active: boolean (optional)
|
|
768
|
-
|
|
769
|
-
[CUSTOM_ANIMATION_LOGIC]
|
|
770
|
-
},
|
|
771
|
-
centeredToTarget: [CENTERED_TO_TARGET]
|
|
772
|
-
}
|
|
773
|
-
]
|
|
774
|
-
}
|
|
775
|
-
```
|
|
776
|
-
|
|
777
|
-
**Variables**:
|
|
24
|
+
---
|
|
778
25
|
|
|
779
|
-
|
|
780
|
-
- `[TARGET_KEY]`: Unique identifier for target element to animate
|
|
781
|
-
- `[HIT_AREA]`: 'self' or 'root'
|
|
782
|
-
- `[CUSTOM_ANIMATION_LOGIC]`: Your custom animation code using the progress object
|
|
783
|
-
- `[CENTERED_TO_TARGET]`: true or false
|
|
26
|
+
## PointerMoveParams
|
|
784
27
|
|
|
785
|
-
|
|
28
|
+
`params` object for `pointerMove` interactions:
|
|
786
29
|
|
|
787
30
|
```typescript
|
|
788
|
-
{
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
hitArea: 'self'
|
|
793
|
-
},
|
|
794
|
-
effects: [
|
|
795
|
-
{
|
|
796
|
-
customEffect: (element, progress) => {
|
|
797
|
-
// Convert progress to angle (0-360 degrees)
|
|
798
|
-
const angle = Math.atan2(
|
|
799
|
-
progress.y - 0.5,
|
|
800
|
-
progress.x - 0.5
|
|
801
|
-
) * (180 / Math.PI);
|
|
802
|
-
|
|
803
|
-
element.style.transform = `rotate(${angle}deg)`;
|
|
804
|
-
},
|
|
805
|
-
centeredToTarget: true
|
|
806
|
-
}
|
|
807
|
-
]
|
|
808
|
-
}
|
|
31
|
+
type PointerMoveParams = {
|
|
32
|
+
hitArea?: 'root' | 'self';
|
|
33
|
+
axis?: 'x' | 'y';
|
|
34
|
+
};
|
|
809
35
|
```
|
|
810
36
|
|
|
811
|
-
|
|
37
|
+
### Properties
|
|
812
38
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
effects: [
|
|
821
|
-
{
|
|
822
|
-
customEffect: (element, progress) => {
|
|
823
|
-
// Calculate distance from center (0.5, 0.5)
|
|
824
|
-
const dx = (progress.x - 0.5) * 2; // -1 to 1
|
|
825
|
-
const dy = (progress.y - 0.5) * 2; // -1 to 1
|
|
39
|
+
- `hitArea` — determines where mouse movement is tracked:
|
|
40
|
+
- `'self'` — tracks pointer within the source element's bounds only. Use for local pointer-tracking effects on a specific element.
|
|
41
|
+
- `'root'` — tracks pointer anywhere in the viewport. Use for global cursor followers, ambient effects.
|
|
42
|
+
- `axis` — restricts pointer tracking to a single axis. Used with `keyframeEffect` to map one axis to 0–1 progress; ignored by `namedEffect` and `customEffect` which receive the full 2D progress:
|
|
43
|
+
- `'x'` — maps horizontal pointer position to 0–1 progress for keyframe interpolation.
|
|
44
|
+
- `'y'` — maps vertical pointer position to 0–1 progress for keyframe interpolation. **Default** when `keyframeEffect` is used.
|
|
45
|
+
- For `namedEffect` or `customEffect` both axes are available via the 2D progress object, and will be ignored.
|
|
826
46
|
|
|
827
|
-
|
|
828
|
-
const maxMove = 20; // pixels
|
|
829
|
-
const moveX = dx * maxMove;
|
|
830
|
-
const moveY = dy * maxMove;
|
|
47
|
+
---
|
|
831
48
|
|
|
832
|
-
|
|
833
|
-
},
|
|
834
|
-
centeredToTarget: true
|
|
835
|
-
}
|
|
836
|
-
]
|
|
837
|
-
}
|
|
838
|
-
```
|
|
49
|
+
## Progress Object Structure
|
|
839
50
|
|
|
840
|
-
|
|
51
|
+
When using `customEffect` with `pointerMove`, the progress parameter is an object:
|
|
841
52
|
|
|
842
53
|
```typescript
|
|
843
|
-
{
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
// Use velocity for motion blur intensity
|
|
853
|
-
const velocity = progress.v || { x: 0, y: 0 };
|
|
854
|
-
const speed = Math.sqrt(velocity.x ** 2 + velocity.y ** 2);
|
|
855
|
-
|
|
856
|
-
// Apply blur based on speed
|
|
857
|
-
const blurAmount = Math.min(speed * 0.5, 10);
|
|
858
|
-
element.style.filter = `blur(${blurAmount}px)`;
|
|
859
|
-
|
|
860
|
-
// Move element towards mouse
|
|
861
|
-
const offsetX = (progress.x - 0.5) * 100;
|
|
862
|
-
const offsetY = (progress.y - 0.5) * 100;
|
|
863
|
-
element.style.transform = `translate(${offsetX}px, ${offsetY}px)`;
|
|
864
|
-
},
|
|
865
|
-
centeredToTarget: false
|
|
866
|
-
}
|
|
867
|
-
]
|
|
868
|
-
}
|
|
54
|
+
type Progress = {
|
|
55
|
+
x: number; // 0-1: horizontal position (0 = left edge, 1 = right edge)
|
|
56
|
+
y: number; // 0-1: vertical position (0 = top edge, 1 = bottom edge)
|
|
57
|
+
v?: {
|
|
58
|
+
x: number; // Horizontal velocity: negative = moving left, positive = moving right. Magnitude reflects speed.
|
|
59
|
+
y: number; // Vertical velocity: negative = moving up, positive = moving down. Magnitude reflects speed.
|
|
60
|
+
};
|
|
61
|
+
active?: boolean; // Whether mouse is currently in the hit area
|
|
62
|
+
};
|
|
869
63
|
```
|
|
870
64
|
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
```typescript
|
|
874
|
-
// First, cache grid cell positions for performance
|
|
875
|
-
const cellCache = new Map();
|
|
876
|
-
// Cache viewport size
|
|
877
|
-
const windowWidth = window.innerWidth;
|
|
878
|
-
const windowHeight = window.innerHeight;
|
|
879
|
-
// ... populate cache with cell center positions
|
|
880
|
-
// ... update `windowWidth/height` on window `resize` event
|
|
65
|
+
---
|
|
881
66
|
|
|
882
|
-
|
|
883
|
-
key: 'interactive-grid',
|
|
884
|
-
trigger: 'pointerMove',
|
|
885
|
-
params: {
|
|
886
|
-
hitArea: 'root'
|
|
887
|
-
},
|
|
888
|
-
effects: [
|
|
889
|
-
{
|
|
890
|
-
customEffect: (element, progress) => {
|
|
891
|
-
// Convert progress to viewport coordinates
|
|
892
|
-
const mouseX = progress.x * windowWidth;
|
|
893
|
-
const mouseY = progress.y * windowHeight;
|
|
67
|
+
## Centering with `centeredToTarget`
|
|
894
68
|
|
|
895
|
-
|
|
896
|
-
for (const [cell, cache] of cellCache) {
|
|
897
|
-
const deltaX = mouseX - cache.x;
|
|
898
|
-
const deltaY = mouseY - cache.y;
|
|
69
|
+
Controls which element's bounds define the 0–1 progress range.
|
|
899
70
|
|
|
900
|
-
|
|
901
|
-
|
|
71
|
+
- **`false` (default)**: Progress is calculated against the **source element's** (or viewport's) bounds. The `50%` progress of the timeline is at the center of the source element.
|
|
72
|
+
- **`true`**: `50%` progress of the timeline is calculated against the **target element's center**. The edges of the timeline are still calculated against the edges of the source element/viewport depending on `hitAea`.
|
|
902
73
|
|
|
903
|
-
|
|
904
|
-
const dist = Math.sqrt(deltaX ** 2 + deltaY ** 2);
|
|
905
|
-
const intensity = Math.max(0, 1 - dist / 500);
|
|
74
|
+
---
|
|
906
75
|
|
|
907
|
-
|
|
908
|
-
}
|
|
909
|
-
},
|
|
910
|
-
centeredToTarget: false
|
|
911
|
-
}
|
|
912
|
-
]
|
|
913
|
-
}
|
|
914
|
-
```
|
|
76
|
+
## Device Conditions
|
|
915
77
|
|
|
916
|
-
|
|
78
|
+
`pointerMove` works best on hover-capable devices. Use a `conditions` entry with a `(hover: hover)` media query to prevent the interaction from registering on touch-only devices. On touch-only devices, consider a fallback to `viewEnter` or `viewProgress` based interactions:
|
|
917
79
|
|
|
918
80
|
```typescript
|
|
919
81
|
{
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
params: {
|
|
923
|
-
hitArea: 'self'
|
|
82
|
+
conditions: {
|
|
83
|
+
'[CONDITION_NAME]': { type: 'media', predicate: '(hover: hover)' }
|
|
924
84
|
},
|
|
925
|
-
|
|
85
|
+
interactions: [
|
|
926
86
|
{
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
return;
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
// Mouse is active in hit area
|
|
936
|
-
const scale = 1 + (1 - Math.abs(progress.x - 0.5) * 2) * 0.1;
|
|
937
|
-
element.style.transform = `scale(${scale})`;
|
|
938
|
-
element.style.opacity = '1';
|
|
939
|
-
},
|
|
940
|
-
centeredToTarget: true
|
|
87
|
+
key: '[SOURCE_KEY]',
|
|
88
|
+
trigger: 'pointerMove',
|
|
89
|
+
conditions: ['[CONDITION_NAME]'],
|
|
90
|
+
params: { hitArea: '[HIT_AREA]' },
|
|
91
|
+
effects: [ /* ... */ ]
|
|
941
92
|
}
|
|
942
93
|
]
|
|
943
94
|
}
|
|
944
95
|
```
|
|
945
96
|
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
For smoother animations, you can use `transitionDuration` and `transitionEasing`:
|
|
949
|
-
|
|
950
|
-
```typescript
|
|
951
|
-
{
|
|
952
|
-
key: 'smooth-custom',
|
|
953
|
-
trigger: 'pointerMove',
|
|
954
|
-
params: {
|
|
955
|
-
hitArea: 'self'
|
|
956
|
-
},
|
|
957
|
-
effects: [
|
|
958
|
-
{
|
|
959
|
-
customEffect: (element, progress) => {
|
|
960
|
-
const x = (progress.x - 0.5) * 50;
|
|
961
|
-
const y = (progress.y - 0.5) * 50;
|
|
962
|
-
element.style.transform = `translate(${x}px, ${y}px)`;
|
|
963
|
-
},
|
|
964
|
-
transitionDuration: 100,
|
|
965
|
-
transitionEasing: 'easeOut',
|
|
966
|
-
centeredToTarget: true
|
|
967
|
-
}
|
|
968
|
-
]
|
|
969
|
-
}
|
|
970
|
-
```
|
|
97
|
+
For devices with dynamic viewport sizes (e.g. mobile browsers where the address bar collapses), consider using viewport-relative units carefully and prefer `lvh`/`svh` over `dvh` unless dynamic viewport behavior is specifically desired.
|
|
971
98
|
|
|
972
99
|
---
|
|
973
100
|
|
|
974
|
-
## Rule
|
|
975
|
-
|
|
976
|
-
**Use Case**: Complex parallax effects with custom physics or non-standard transformations across multiple layers.
|
|
101
|
+
## Rule 1: namedEffect
|
|
977
102
|
|
|
978
|
-
|
|
103
|
+
Use pre-built mouse presets from `@wix/motion-presets` that handle 2D mouse tracking internally. Mouse presets are preferred over `keyframeEffect` for 2D effects.
|
|
979
104
|
|
|
980
|
-
|
|
981
|
-
- When layers need different calculation methods
|
|
982
|
-
- For effects combining multiple CSS properties
|
|
983
|
-
|
|
984
|
-
**Pattern**:
|
|
105
|
+
**Multiple effects:** The `effects` array can contain multiple effects — all share the same pointer tracking and fire together. Use this to animate different targets from the same pointer movement.
|
|
985
106
|
|
|
986
107
|
```typescript
|
|
987
108
|
{
|
|
988
|
-
key: '[
|
|
109
|
+
key: '[SOURCE_KEY]',
|
|
989
110
|
trigger: 'pointerMove',
|
|
990
111
|
params: {
|
|
991
112
|
hitArea: '[HIT_AREA]'
|
|
992
113
|
},
|
|
993
114
|
effects: [
|
|
994
115
|
{
|
|
995
|
-
key: '[
|
|
996
|
-
|
|
997
|
-
[
|
|
116
|
+
key: '[TARGET_KEY]',
|
|
117
|
+
namedEffect: {
|
|
118
|
+
type: '[NAMED_EFFECT_TYPE]',
|
|
119
|
+
[EFFECT_PROPERTIES]
|
|
998
120
|
},
|
|
999
|
-
centeredToTarget:
|
|
121
|
+
centeredToTarget: [CENTERED_TO_TARGET],
|
|
122
|
+
transitionDuration: [TRANSITION_DURATION_MS],
|
|
123
|
+
transitionEasing: '[TRANSITION_EASING]'
|
|
1000
124
|
},
|
|
1001
|
-
|
|
1002
|
-
key: '[LAYER_2_KEY]',
|
|
1003
|
-
customEffect: (element, progress) => {
|
|
1004
|
-
[LAYER_2_CUSTOM_LOGIC]
|
|
1005
|
-
},
|
|
1006
|
-
centeredToTarget: true
|
|
1007
|
-
}
|
|
125
|
+
// additional effects targeting other elements can be added here
|
|
1008
126
|
]
|
|
1009
127
|
}
|
|
1010
128
|
```
|
|
1011
129
|
|
|
1012
|
-
|
|
130
|
+
### Variables
|
|
1013
131
|
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
{
|
|
1023
|
-
key: 'bg-stars',
|
|
1024
|
-
customEffect: (element, progress) => {
|
|
1025
|
-
// Background: subtle movement, inverted direction
|
|
1026
|
-
const x = (0.5 - progress.x) * 10;
|
|
1027
|
-
const y = (0.5 - progress.y) * 10;
|
|
1028
|
-
element.style.transform = `translate(${x}px, ${y}px)`;
|
|
1029
|
-
},
|
|
1030
|
-
centeredToTarget: true
|
|
1031
|
-
},
|
|
1032
|
-
{
|
|
1033
|
-
key: 'mid-clouds',
|
|
1034
|
-
customEffect: (element, progress) => {
|
|
1035
|
-
// Midground: moderate movement with rotation
|
|
1036
|
-
const x = (progress.x - 0.5) * 30;
|
|
1037
|
-
const y = (progress.y - 0.5) * 20;
|
|
1038
|
-
const rotation = (progress.x - 0.5) * 5;
|
|
1039
|
-
element.style.transform = `translate(${x}px, ${y}px) rotate(${rotation}deg)`;
|
|
1040
|
-
},
|
|
1041
|
-
centeredToTarget: true
|
|
1042
|
-
},
|
|
1043
|
-
{
|
|
1044
|
-
key: 'fg-elements',
|
|
1045
|
-
customEffect: (element, progress) => {
|
|
1046
|
-
// Foreground: strong movement with scale
|
|
1047
|
-
const x = (progress.x - 0.5) * 60;
|
|
1048
|
-
const y = (progress.y - 0.5) * 40;
|
|
1049
|
-
const scale = 1 + Math.abs(progress.x - 0.5) * 0.1;
|
|
1050
|
-
element.style.transform = `translate(${x}px, ${y}px) scale(${scale})`;
|
|
1051
|
-
},
|
|
1052
|
-
centeredToTarget: true
|
|
1053
|
-
}
|
|
1054
|
-
]
|
|
1055
|
-
}
|
|
1056
|
-
```
|
|
132
|
+
- `[SOURCE_KEY]` — identifier matching the element's key (`data-interact-key` for web, `interactKey` for React). The element that tracks pointer movement.
|
|
133
|
+
- `[TARGET_KEY]` — identifier matching the element's key on the element to animate (can be same as source or different).
|
|
134
|
+
- `[HIT_AREA]` — `'self'` (track pointer within source element) or `'root'` (track pointer anywhere in viewport).
|
|
135
|
+
- `[NAMED_EFFECT_TYPE]` — a registered effect name, or a preset from `@wix/motion-presets` `mouse` library.
|
|
136
|
+
- `[EFFECT_PROPERTIES]` — preset-specific options. Refer to motion-presets rules for each preset's available options and their value types. Do NOT guess preset option names or types; omit unknown options and rely on defaults.
|
|
137
|
+
- `[CENTERED_TO_TARGET]` — `true` or `false`. See **Centering with `centeredToTarget`** above.
|
|
138
|
+
- `[TRANSITION_DURATION_MS]` — optional number. Milliseconds for smoothing (interpolating) between progress updates. The animation does not jump to the new progress value instantly; instead it transitions over this duration. Use to add inertia/lag to the effect, making it feel more physical (e.g. `200`–`600`).
|
|
139
|
+
- `[TRANSITION_EASING]` — optional string. CSS easing or named easing from `@wix/motion`. Adds a natural deceleration feel when used with `transitionDuration`.
|
|
1057
140
|
|
|
1058
141
|
---
|
|
1059
142
|
|
|
1060
|
-
## Rule
|
|
1061
|
-
|
|
1062
|
-
**Use Case**: When you want to use standard keyframe animations driven by pointer movement along a single axis (e.g., horizontal sliders, vertical progress indicators, single-axis parallax effects)
|
|
1063
|
-
|
|
1064
|
-
**When to Apply**:
|
|
1065
|
-
|
|
1066
|
-
- For slider-like interactions driven by horizontal mouse position
|
|
1067
|
-
- For vertical scroll-like effects driven by vertical mouse position
|
|
1068
|
-
- When you have existing keyframe animations you want to control with pointer movement
|
|
1069
|
-
- For simple linear interpolation effects along one axis
|
|
143
|
+
## Rule 2: keyframeEffect with Single Axis
|
|
1070
144
|
|
|
1071
|
-
|
|
145
|
+
Use `keyframeEffect` when the pointer position along a single axis should drive a keyframe animation. The pointer's position on the chosen axis is mapped to linear 0–1 progress.
|
|
1072
146
|
|
|
1073
147
|
```typescript
|
|
1074
148
|
{
|
|
@@ -1076,457 +150,130 @@ For smoother animations, you can use `transitionDuration` and `transitionEasing`
|
|
|
1076
150
|
trigger: 'pointerMove',
|
|
1077
151
|
params: {
|
|
1078
152
|
hitArea: '[HIT_AREA]',
|
|
1079
|
-
axis: '[AXIS]'
|
|
153
|
+
axis: '[AXIS]'
|
|
1080
154
|
},
|
|
1081
155
|
effects: [
|
|
1082
156
|
{
|
|
1083
157
|
key: '[TARGET_KEY]',
|
|
1084
158
|
keyframeEffect: {
|
|
1085
|
-
name: '[
|
|
1086
|
-
keyframes: [
|
|
1087
|
-
{ [PROPERTY]: '[START_VALUE]' },
|
|
1088
|
-
{ [PROPERTY]: '[END_VALUE]' }
|
|
1089
|
-
]
|
|
1090
|
-
},
|
|
1091
|
-
fill: '[FILL_MODE]',
|
|
1092
|
-
centeredToTarget: [CENTERED_TO_TARGET]
|
|
1093
|
-
}
|
|
1094
|
-
]
|
|
1095
|
-
}
|
|
1096
|
-
```
|
|
1097
|
-
|
|
1098
|
-
**Variables**:
|
|
1099
|
-
|
|
1100
|
-
- `[SOURCE_KEY]`: Unique identifier for source element tracking mouse movement
|
|
1101
|
-
- `[TARGET_KEY]`: Unique identifier for target element to animate
|
|
1102
|
-
- `[HIT_AREA]`: 'self' or 'root'
|
|
1103
|
-
- `[AXIS]`: 'x' (maps x position) or 'y' (maps y position) - **defaults to 'y'** (in `params`)
|
|
1104
|
-
- `[ANIMATION_NAME]`: Name for the keyframe animation
|
|
1105
|
-
- `[PROPERTY]`: CSS property to animate (transform, opacity, etc.)
|
|
1106
|
-
- `[START_VALUE]`: Value at progress 0 (left/top edge)
|
|
1107
|
-
- `[END_VALUE]`: Value at progress 1 (right/bottom edge)
|
|
1108
|
-
- `[FILL_MODE]`: 'none', 'forwards', 'backwards', 'both'
|
|
1109
|
-
- `[CENTERED_TO_TARGET]`: true or false
|
|
1110
|
-
|
|
1111
|
-
**Example - Horizontal Slider with Multiple Targets**:
|
|
1112
|
-
|
|
1113
|
-
This example shows a pointer-driven slider where the X position controls both a sliding element and an indicator's opacity/scale.
|
|
1114
|
-
|
|
1115
|
-
```typescript
|
|
1116
|
-
{
|
|
1117
|
-
interactions: [
|
|
1118
|
-
{
|
|
1119
|
-
key: 'pointer-container',
|
|
1120
|
-
trigger: 'pointerMove',
|
|
1121
|
-
params: { hitArea: 'self', axis: 'x' },
|
|
1122
|
-
effects: [
|
|
1123
|
-
{
|
|
1124
|
-
key: 'pointer-slider',
|
|
1125
|
-
effectId: 'slide-effect',
|
|
1126
|
-
},
|
|
1127
|
-
{
|
|
1128
|
-
key: 'pointer-indicator',
|
|
1129
|
-
effectId: 'indicator-effect',
|
|
1130
|
-
},
|
|
1131
|
-
],
|
|
1132
|
-
},
|
|
1133
|
-
],
|
|
1134
|
-
effects: {
|
|
1135
|
-
'slide-effect': {
|
|
1136
|
-
keyframeEffect: {
|
|
1137
|
-
name: 'slide-x',
|
|
1138
|
-
keyframes: [
|
|
1139
|
-
{ transform: 'translateX(0px)' },
|
|
1140
|
-
{ transform: 'translateX(220px)' },
|
|
1141
|
-
],
|
|
1142
|
-
},
|
|
1143
|
-
fill: 'both',
|
|
1144
|
-
},
|
|
1145
|
-
'indicator-effect': {
|
|
1146
|
-
keyframeEffect: {
|
|
1147
|
-
name: 'indicator-fade-scale',
|
|
1148
|
-
keyframes: [
|
|
1149
|
-
{ opacity: '0.3', transform: 'scale(0.8)' },
|
|
1150
|
-
{ opacity: '1', transform: 'scale(1)' },
|
|
1151
|
-
],
|
|
159
|
+
name: '[EFFECT_NAME]',
|
|
160
|
+
keyframes: [KEYFRAMES]
|
|
1152
161
|
},
|
|
1153
162
|
fill: 'both',
|
|
163
|
+
centeredToTarget: [CENTERED_TO_TARGET],
|
|
164
|
+
transitionDuration: [TRANSITION_DURATION_MS],
|
|
165
|
+
transitionEasing: '[TRANSITION_EASING]',
|
|
166
|
+
effectId: '[UNIQUE_EFFECT_ID]'
|
|
1154
167
|
},
|
|
1155
|
-
|
|
168
|
+
// additional effects targeting other elements can be added here
|
|
169
|
+
]
|
|
1156
170
|
}
|
|
1157
171
|
```
|
|
1158
172
|
|
|
1159
|
-
|
|
173
|
+
### Variables
|
|
1160
174
|
|
|
1161
|
-
- `
|
|
1162
|
-
-
|
|
175
|
+
- `[SOURCE_KEY]` / `[TARGET_KEY]` — same as Rule 1.
|
|
176
|
+
- `[HIT_AREA]` — `'self'` or `'root'`.
|
|
177
|
+
- `[AXIS]` — `'x'` (horizontal) or `'y'` (vertical). Defaults to `'y'` when omitted.
|
|
178
|
+
- `[EFFECT_NAME]` — unique string name for the keyframe effect.
|
|
179
|
+
- `[KEYFRAMES]` — array of CSS keyframe objects (e.g. `[{ transform: 'rotate(-10deg)' }, { transform: 'rotate(0)' }, { transform: 'rotate(10deg)' }]`). Distributed evenly across 0–1 progress: first keyframe = progress 0 (left/top edge), last = progress 1 (right/bottom edge). Any number of keyframes is allowed.
|
|
180
|
+
- `[CENTERED_TO_TARGET]` — optional. `true` or `false`. See **Centering with `centeredToTarget`** above. Defaults to `false`.
|
|
181
|
+
- `[TRANSITION_DURATION_MS]` — optional. Milliseconds for smoothing between progress updates. See Rule 1 for details.
|
|
182
|
+
- `[TRANSITION_EASING]` — optional. CSS easing string or named easing from `@wix/motion`. See Rule 1 for supported values.
|
|
183
|
+
- `[UNIQUE_EFFECT_ID]` — optional string identifier.
|
|
1163
184
|
|
|
1164
185
|
---
|
|
1165
186
|
|
|
1166
|
-
## Rule
|
|
1167
|
-
|
|
1168
|
-
**Use Case**: Independent X/Y axis control using two `keyframeEffect` animations on the same target.
|
|
187
|
+
## Rule 3: Two keyframeEffects with Two Axes and `composite`
|
|
1169
188
|
|
|
1170
|
-
|
|
1171
|
-
Define two interactions on the same source/target pair—one for `axis: 'x'`, one for `axis: 'y'`. When animating the same CSS property (e.g. `transform`), use the `composite` option to combine the effects.
|
|
1172
|
-
|
|
1173
|
-
**Example - 2D Scale Control**:
|
|
1174
|
-
|
|
1175
|
-
X axis controls `scaleX`, Y axis controls `scaleY`.
|
|
189
|
+
Use two separate interactions on the same source/target pair — one for `axis: 'x'`, one for `axis: 'y'` — for independent 2D control with keyframes. When both effects animate the same CSS property (e.g. `transform` or `filter`), use `composite` to combine them.
|
|
1176
190
|
|
|
1177
191
|
```typescript
|
|
1178
192
|
{
|
|
1179
193
|
interactions: [
|
|
1180
194
|
{
|
|
1181
|
-
key: '
|
|
195
|
+
key: '[SOURCE_KEY]',
|
|
1182
196
|
trigger: 'pointerMove',
|
|
1183
|
-
params: { hitArea: '
|
|
1184
|
-
effects: [
|
|
1185
|
-
{
|
|
1186
|
-
key: 'composite-add-ball',
|
|
1187
|
-
effectId: 'scale-x-effect',
|
|
1188
|
-
},
|
|
1189
|
-
],
|
|
197
|
+
params: { hitArea: '[HIT_AREA]', axis: 'x' },
|
|
198
|
+
effects: [{ key: '[TARGET_KEY]', effectId: '[X_EFFECT_ID]' }]
|
|
1190
199
|
},
|
|
1191
200
|
{
|
|
1192
|
-
key: '
|
|
201
|
+
key: '[SOURCE_KEY]',
|
|
1193
202
|
trigger: 'pointerMove',
|
|
1194
|
-
params: { hitArea: '
|
|
1195
|
-
effects: [
|
|
1196
|
-
|
|
1197
|
-
key: 'composite-add-ball',
|
|
1198
|
-
effectId: 'scale-y-effect',
|
|
1199
|
-
},
|
|
1200
|
-
],
|
|
1201
|
-
},
|
|
203
|
+
params: { hitArea: '[HIT_AREA]', axis: 'y' },
|
|
204
|
+
effects: [{ key: '[TARGET_KEY]', effectId: '[Y_EFFECT_ID]' }]
|
|
205
|
+
}
|
|
1202
206
|
],
|
|
1203
207
|
effects: {
|
|
1204
|
-
'
|
|
208
|
+
'[X_EFFECT_ID]': {
|
|
1205
209
|
keyframeEffect: {
|
|
1206
|
-
name: '
|
|
1207
|
-
keyframes: [
|
|
1208
|
-
{ transform: 'scaleX(0.5)' },
|
|
1209
|
-
{ transform: 'scaleX(1.5)' },
|
|
1210
|
-
],
|
|
210
|
+
name: '[X_EFFECT_NAME]',
|
|
211
|
+
keyframes: [X_KEYFRAMES]
|
|
1211
212
|
},
|
|
1212
|
-
fill: 'both'
|
|
1213
|
-
composite: '
|
|
213
|
+
fill: '[FILL_MODE]', // usually 'both'
|
|
214
|
+
composite: '[COMPOSITE_OPERATION]',
|
|
215
|
+
transitionDuration: [TRANSITION_DURATION_MS],
|
|
216
|
+
transitionEasing: '[TRANSITION_EASING]'
|
|
1214
217
|
},
|
|
1215
|
-
'
|
|
218
|
+
'[Y_EFFECT_ID]': {
|
|
1216
219
|
keyframeEffect: {
|
|
1217
|
-
name: '
|
|
1218
|
-
keyframes: [
|
|
1219
|
-
{ transform: 'scaleY(0.5)' },
|
|
1220
|
-
{ transform: 'scaleY(1.5)' },
|
|
1221
|
-
],
|
|
220
|
+
name: '[Y_EFFECT_NAME]',
|
|
221
|
+
keyframes: [Y_KEYFRAMES]
|
|
1222
222
|
},
|
|
1223
|
-
fill: 'both'
|
|
1224
|
-
composite: '
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
}
|
|
1228
|
-
```
|
|
1229
|
-
|
|
1230
|
-
---
|
|
1231
|
-
|
|
1232
|
-
## Advanced Patterns and Combinations
|
|
1233
|
-
|
|
1234
|
-
### Responsive Pointer Effects
|
|
1235
|
-
|
|
1236
|
-
`pointerMove` only fires on pointer-capable devices, but touch users still visit the page. Use the `conditions` config map to define device/motion guards — condition IDs are arbitrary strings you define, matched against the media query predicates you provide.
|
|
1237
|
-
|
|
1238
|
-
```typescript
|
|
1239
|
-
{
|
|
1240
|
-
conditions: {
|
|
1241
|
-
// Only run pointer effects on devices that support hover (non-touch)
|
|
1242
|
-
'supports-hover': { type: 'media', predicate: '(hover: hover)' },
|
|
1243
|
-
// Suppress animations for users who prefer reduced motion
|
|
1244
|
-
'prefers-motion': { type: 'media', predicate: '(prefers-reduced-motion: no-preference)' },
|
|
1245
|
-
},
|
|
1246
|
-
interactions: [
|
|
1247
|
-
{
|
|
1248
|
-
key: 'responsive-element',
|
|
1249
|
-
trigger: 'pointerMove',
|
|
1250
|
-
conditions: ['supports-hover', 'prefers-motion'],
|
|
1251
|
-
params: { hitArea: 'self' },
|
|
1252
|
-
effects: [
|
|
1253
|
-
{
|
|
1254
|
-
key: 'responsive-element',
|
|
1255
|
-
namedEffect: { type: 'Tilt3DMouse', angle: 20, perspective: 800 },
|
|
1256
|
-
centeredToTarget: true
|
|
1257
|
-
}
|
|
1258
|
-
]
|
|
223
|
+
fill: '[FILL_MODE]', // usually 'both'
|
|
224
|
+
composite: '[COMPOSITE_OPERATION]',
|
|
225
|
+
transitionDuration: [TRANSITION_DURATION_MS],
|
|
226
|
+
transitionEasing: '[TRANSITION_EASING]'
|
|
1259
227
|
}
|
|
1260
|
-
|
|
228
|
+
}
|
|
1261
229
|
}
|
|
1262
230
|
```
|
|
1263
231
|
|
|
1264
|
-
###
|
|
232
|
+
### Variables
|
|
1265
233
|
|
|
1266
|
-
|
|
234
|
+
- `[SOURCE_KEY]` / `[TARGET_KEY]` — same as Rule 1.
|
|
235
|
+
- `[HIT_AREA]` — `'self'` or `'root'`.
|
|
236
|
+
- `[X_EFFECT_ID]` / `[Y_EFFECT_ID]` — unique string identifiers for the X-axis and Y-axis effects. Required — they map to keys in the top-level `effects` map.
|
|
237
|
+
- `[X_EFFECT_NAME]` / `[Y_EFFECT_NAME]` — unique string names for each keyframe effect.
|
|
238
|
+
- `[X_KEYFRAMES]` / `[Y_KEYFRAMES]` — arrays of WAAPI keyframe objects for the X-axis and Y-axis effects respectively. Each effect can vary in propertise and keyframes.
|
|
239
|
+
- `[COMPOSITE_OPERATION]` — `'add'` or `'accumulate'`. Required when both effects animate `transform` and/or both animate `filter`, so their values combine rather than override. `'add'`: composited transform functions are appended. `'accumulate'`: matching function arguments are summed.
|
|
240
|
+
- `[FILL_MODE]` — typically `'both'` to ensure the effect keeps applying after exiting the effect's active range.
|
|
241
|
+
- `[TRANSITION_DURATION_MS]` — optional. Milliseconds for smoothing between progress updates. See Rule 1 for details.
|
|
242
|
+
- `[TRANSITION_EASING]` — optional. CSS easing function for the smoothing transition. See Rule 1 for supported values.
|
|
1267
243
|
|
|
1268
|
-
|
|
1269
|
-
// Local interaction - mouse must be over element
|
|
1270
|
-
{
|
|
1271
|
-
key: 'local-card',
|
|
1272
|
-
trigger: 'pointerMove',
|
|
1273
|
-
params: {
|
|
1274
|
-
hitArea: 'self'
|
|
1275
|
-
},
|
|
1276
|
-
effects: [
|
|
1277
|
-
{
|
|
1278
|
-
key: 'local-card',
|
|
1279
|
-
namedEffect: {
|
|
1280
|
-
type: 'Tilt3DMouse',
|
|
1281
|
-
angle: 15
|
|
1282
|
-
},
|
|
1283
|
-
centeredToTarget: true
|
|
1284
|
-
}
|
|
1285
|
-
]
|
|
1286
|
-
},
|
|
1287
|
-
// Global interaction - responds to mouse anywhere
|
|
1288
|
-
{
|
|
1289
|
-
key: 'global-background',
|
|
1290
|
-
trigger: 'pointerMove',
|
|
1291
|
-
params: {
|
|
1292
|
-
hitArea: 'root'
|
|
1293
|
-
},
|
|
1294
|
-
effects: [
|
|
1295
|
-
{
|
|
1296
|
-
key: 'ambient-element',
|
|
1297
|
-
namedEffect: {
|
|
1298
|
-
type: 'AiryMouse',
|
|
1299
|
-
distance: { value: 30, unit: 'px' }
|
|
1300
|
-
},
|
|
1301
|
-
centeredToTarget: false
|
|
1302
|
-
}
|
|
1303
|
-
]
|
|
1304
|
-
}
|
|
1305
|
-
```
|
|
244
|
+
---
|
|
1306
245
|
|
|
1307
|
-
|
|
246
|
+
## Rule 4: customEffect
|
|
1308
247
|
|
|
1309
|
-
|
|
248
|
+
Use `customEffect` when you need full imperative control over pointer-driven animations — custom physics, complex multi-property animations, velocity-reactive effects, or controlling WebGL/WebGPU and other JavaScript-driven effects. The callback receives the 2D progress object (see **Progress Object Structure**).
|
|
1310
249
|
|
|
1311
250
|
```typescript
|
|
1312
251
|
{
|
|
1313
|
-
key: '
|
|
252
|
+
key: '[SOURCE_KEY]',
|
|
1314
253
|
trigger: 'pointerMove',
|
|
1315
254
|
params: {
|
|
1316
|
-
hitArea: '
|
|
255
|
+
hitArea: '[HIT_AREA]'
|
|
1317
256
|
},
|
|
1318
257
|
effects: [
|
|
1319
258
|
{
|
|
1320
|
-
key: '
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
distance: { value: 100, unit: 'px' },
|
|
1324
|
-
axis: 'horizontal'
|
|
259
|
+
key: '[TARGET_KEY]',
|
|
260
|
+
customEffect: (element: Element, progress: Progress) => {
|
|
261
|
+
[CUSTOM_ANIMATION_LOGIC]
|
|
1325
262
|
},
|
|
1326
|
-
centeredToTarget:
|
|
263
|
+
centeredToTarget: [CENTERED_TO_TARGET],
|
|
264
|
+
transitionDuration: [TRANSITION_DURATION_MS],
|
|
265
|
+
transitionEasing: '[TRANSITION_EASING]'
|
|
1327
266
|
},
|
|
1328
|
-
|
|
1329
|
-
key: 'vertical-indicator',
|
|
1330
|
-
namedEffect: {
|
|
1331
|
-
type: 'ScaleMouse',
|
|
1332
|
-
scale: 1.2,
|
|
1333
|
-
distance: { value: 150, unit: 'px' },
|
|
1334
|
-
axis: 'vertical'
|
|
1335
|
-
},
|
|
1336
|
-
centeredToTarget: true
|
|
1337
|
-
}
|
|
267
|
+
// additional effects targeting other elements can be added here
|
|
1338
268
|
]
|
|
1339
269
|
}
|
|
1340
270
|
```
|
|
1341
271
|
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
## Best Practices for PointerMove Interactions
|
|
1345
|
-
|
|
1346
|
-
### Effect Type Selection Guidelines
|
|
1347
|
-
|
|
1348
|
-
**When to use `namedEffect` (Preferred)**:
|
|
1349
|
-
|
|
1350
|
-
1. For standard mouse-tracking effects (tilt, track, scale, blur)
|
|
1351
|
-
2. When GPU-optimized performance is critical
|
|
1352
|
-
3. For effects that match preset behavior (3D tilt, elastic following)
|
|
1353
|
-
4. When you don't need custom physics or calculations
|
|
1354
|
-
|
|
1355
|
-
**When to use `customEffect`**:
|
|
1356
|
-
|
|
1357
|
-
1. For custom physics-based animations (springs, gravity)
|
|
1358
|
-
2. When you need access to velocity data (`progress.v`)
|
|
1359
|
-
3. For complex DOM manipulations (updating multiple elements)
|
|
1360
|
-
4. When creating effects not covered by named presets
|
|
1361
|
-
5. For grid/particle systems with many elements
|
|
1362
|
-
6. For controlling WebGL/WebGPU effects
|
|
1363
|
-
|
|
1364
|
-
**When to use `keyframeEffect`**:
|
|
1365
|
-
|
|
1366
|
-
1. When you want single-axis control using the `axis` parameter ('x' or 'y')
|
|
1367
|
-
2. For slider-like interactions driven by pointer position along one axis
|
|
1368
|
-
3. For 2D control, use two `keyframeEffect` interactions with `composite` (see Rule 11)
|
|
1369
|
-
|
|
1370
|
-
### Performance Guidelines
|
|
1371
|
-
|
|
1372
|
-
1. **Limit simultaneous pointer effects** - too many can cause performance issues
|
|
1373
|
-
2. **Test on various devices** - pointer sensitivity varies across hardware
|
|
1374
|
-
3. **Cache DOM queries outside `customEffect` callbacks** - avoid repeated `querySelector` calls inside the callback
|
|
1375
|
-
4. **Use `requestAnimationFrame` sparingly** - the library already handles frame timing
|
|
1376
|
-
5. **Prefer `namedEffect` over `customEffect`** - named effects are optimized for GPU acceleration
|
|
1377
|
-
|
|
1378
|
-
### Hit Area Guidelines
|
|
1379
|
-
|
|
1380
|
-
1. **Use `hitArea: 'self'`** for local element interactions (cards, buttons, hover effects)
|
|
1381
|
-
2. **Use `hitArea: 'root'`** for global cursor followers and ambient effects
|
|
1382
|
-
3. **Consider container boundaries** when choosing hit areas
|
|
1383
|
-
4. **Test hit area responsiveness** across different screen sizes
|
|
1384
|
-
5. **`'self'`** is more performant than `'root'` - use when possible
|
|
1385
|
-
|
|
1386
|
-
### Centering Guidelines
|
|
1387
|
-
|
|
1388
|
-
1. **Set `centeredToTarget: true`** when target differs from source (e.g., animating child element from parent)
|
|
1389
|
-
2. **Use `centeredToTarget: false`** for cursor followers and global effects
|
|
1390
|
-
3. **Test centering behavior** with different element sizes
|
|
1391
|
-
4. **Consider responsive design** when setting centering
|
|
1392
|
-
5. **Centering affects how progress.x/y map to element position**
|
|
1393
|
-
|
|
1394
|
-
### Common Use Cases by Pattern
|
|
1395
|
-
|
|
1396
|
-
**Single Element 3D Effects (Rule 1)** - `namedEffect`:
|
|
1397
|
-
|
|
1398
|
-
- Interactive product cards
|
|
1399
|
-
- 3D showcase elements
|
|
1400
|
-
- Immersive button interactions
|
|
1401
|
-
- Portfolio item presentations
|
|
1402
|
-
|
|
1403
|
-
**Movement Followers (Rule 2)** - `namedEffect`:
|
|
1404
|
-
|
|
1405
|
-
- Cursor follower elements
|
|
1406
|
-
- Floating decorative elements
|
|
1407
|
-
- Responsive UI indicators
|
|
1408
|
-
- Interactive overlays
|
|
1409
|
-
|
|
1410
|
-
**Scale & Deformation (Rule 3)** - `namedEffect`:
|
|
1411
|
-
|
|
1412
|
-
- Organic interface elements
|
|
1413
|
-
- Interactive morphing shapes
|
|
1414
|
-
- Creative scaling buttons
|
|
1415
|
-
- Blob-like interactions
|
|
1416
|
-
|
|
1417
|
-
**Visual Effects (Rule 4)** - `namedEffect`:
|
|
1418
|
-
|
|
1419
|
-
- Creative interface elements
|
|
1420
|
-
- Motion blur interactions
|
|
1421
|
-
- Spinning decorative elements
|
|
1422
|
-
- Dynamic visual feedback
|
|
1423
|
-
|
|
1424
|
-
**Multi-Element Parallax (Rule 5)** - `namedEffect`:
|
|
1425
|
-
|
|
1426
|
-
- Layered background effects
|
|
1427
|
-
- Depth-based interactions
|
|
1428
|
-
- Immersive hero sections
|
|
1429
|
-
- Complex scene responses
|
|
1430
|
-
|
|
1431
|
-
**Group Coordination (Rule 6)** - `namedEffect`:
|
|
1432
|
-
|
|
1433
|
-
- Interactive card grids
|
|
1434
|
-
- Navigation menu systems
|
|
1435
|
-
- Gallery hover effects
|
|
1436
|
-
- Coordinated UI responses
|
|
1437
|
-
|
|
1438
|
-
**Global Followers (Rule 7)** - `namedEffect`:
|
|
1439
|
-
|
|
1440
|
-
- Custom cursor implementations
|
|
1441
|
-
- Page-wide decorative elements
|
|
1442
|
-
- Global interactive overlays
|
|
1443
|
-
- Immersive cursor experiences
|
|
1444
|
-
|
|
1445
|
-
**Custom Pointer Effects (Rule 8)** - `customEffect`:
|
|
1446
|
-
|
|
1447
|
-
- Grid-based rotation systems
|
|
1448
|
-
- Magnetic pull/push effects
|
|
1449
|
-
- Physics-based animations
|
|
1450
|
-
- Velocity-reactive effects
|
|
1451
|
-
- Complex DOM manipulations
|
|
1452
|
-
- Particle systems
|
|
1453
|
-
|
|
1454
|
-
**Multi-Element Custom Parallax (Rule 9)** - `customEffect`:
|
|
1455
|
-
|
|
1456
|
-
- Non-linear parallax physics
|
|
1457
|
-
- Layers with different calculation methods
|
|
1458
|
-
- Combined transform effects per layer
|
|
1459
|
-
- Custom easing per element
|
|
1460
|
-
|
|
1461
|
-
**Single-Axis Keyframe Control (Rule 10)** - `keyframeEffect`:
|
|
1462
|
-
|
|
1463
|
-
- Horizontal slider interactions
|
|
1464
|
-
- Vertical progress indicators
|
|
1465
|
-
- Single-axis reveal effects
|
|
1466
|
-
- Linear interpolation along one axis
|
|
1467
|
-
|
|
1468
|
-
**Composite Keyframe (Rule 11)** - Two `keyframeEffect` + `composite`:
|
|
1469
|
-
|
|
1470
|
-
- 2D element positioning with pointer
|
|
1471
|
-
- Combined X/Y transform animations
|
|
1472
|
-
- Independent axis control with keyframes
|
|
1473
|
-
- Declarative 2D animations without customEffect
|
|
1474
|
-
|
|
1475
|
-
### Troubleshooting Common Issues
|
|
1476
|
-
|
|
1477
|
-
**Poor pointer responsiveness**:
|
|
1478
|
-
|
|
1479
|
-
- Verify `hitArea` configuration
|
|
1480
|
-
- Test `centeredToTarget` settings
|
|
1481
|
-
- Ensure target elements are properly positioned
|
|
1482
|
-
|
|
1483
|
-
**Performance issues**:
|
|
1484
|
-
|
|
1485
|
-
- Reduce number of simultaneous effects
|
|
1486
|
-
- Use simpler named effects
|
|
1487
|
-
- Check for CSS conflicts
|
|
1488
|
-
- Test on lower-end devices
|
|
1489
|
-
- In customEffect: cache DOM queries outside the callback
|
|
1490
|
-
- Avoid creating objects inside customEffect callbacks
|
|
1491
|
-
|
|
1492
|
-
**customEffect not updating smoothly**:
|
|
1493
|
-
|
|
1494
|
-
- Add `transitionDuration` and `transitionEasing` for smoother transitions
|
|
1495
|
-
- Avoid expensive calculations inside the callback
|
|
1496
|
-
- Consider debouncing complex logic
|
|
1497
|
-
|
|
1498
|
-
**customEffect progress values unexpected**:
|
|
1499
|
-
|
|
1500
|
-
- Remember x/y are 0-1 normalized (not pixel values)
|
|
1501
|
-
- Check `centeredToTarget` setting affects coordinate mapping
|
|
1502
|
-
- Verify `hitArea` matches expected tracking area
|
|
1503
|
-
- Use `progress.active` to handle edge cases
|
|
1504
|
-
|
|
1505
|
-
**Unexpected behavior on touch devices**:
|
|
1506
|
-
|
|
1507
|
-
- Implement appropriate conditions for touch vs. mouse
|
|
1508
|
-
- Provide touch-friendly alternatives
|
|
1509
|
-
- Test pointer events on mobile devices
|
|
1510
|
-
- Consider disabling complex effects on touch
|
|
1511
|
-
|
|
1512
|
-
**Effects not triggering**:
|
|
1513
|
-
|
|
1514
|
-
- Verify source element exists and is visible
|
|
1515
|
-
- Check `data-interact-key` matches CSS selector
|
|
1516
|
-
- Ensure proper hit area configuration
|
|
1517
|
-
- Test mouse event propagation
|
|
1518
|
-
|
|
1519
|
-
---
|
|
1520
|
-
|
|
1521
|
-
## Quick Reference: Effect Type Selection
|
|
272
|
+
### Variables
|
|
1522
273
|
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
| Multi-axis keyframe (X + Y) | Two interactions with `keyframeEffect` | Use `composite: 'add'` or `'accumulate'` for same prop |
|
|
1530
|
-
| Custom physics | `customEffect` | Full control over calculations |
|
|
1531
|
-
| Velocity-based effects | `customEffect` | Access to `progress.v` |
|
|
1532
|
-
| Grid/particle systems | `customEffect` | Can manipulate many elements |
|
|
274
|
+
- `[SOURCE_KEY]` / `[TARGET_KEY]` — same as Rule 1.
|
|
275
|
+
- `[HIT_AREA]` — `'self'` or `'root'`.
|
|
276
|
+
- `[CUSTOM_ANIMATION_LOGIC]` — JavaScript using `progress.x`, `progress.y`, `progress.v`, and `progress.active` to apply the effect. See **Progress Object Structure** above.
|
|
277
|
+
- `[CENTERED_TO_TARGET]` — optional. `true` or `false`. See **Centering with `centeredToTarget`** above. Defaults to `false`.
|
|
278
|
+
- `[TRANSITION_DURATION_MS]` — optional. Milliseconds for smoothing between progress updates. See Rule 1 for details.
|
|
279
|
+
- `[TRANSITION_EASING]` — optional. CSS easing function for the smoothing transition. See Rule 1 for supported values.
|