0
# Animation System
1
2
ZRender provides a comprehensive animation framework that supports property interpolation, easing functions, timeline control, and advanced features like path morphing. The animation system is frame-based and optimized for smooth 60fps performance.
3
4
## Core Animation Classes
5
6
### Animator
7
8
The primary interface for controlling animations on elements:
9
10
```typescript { .api }
11
interface Animator {
12
// Timeline control
13
when(time: number, props: any): Animator;
14
during(callback: (percent: number) => void): Animator;
15
delay(time: number): Animator;
16
17
// Lifecycle callbacks
18
done(callback: () => void): Animator;
19
aborted(callback: () => void): Animator;
20
21
// Playback control
22
start(easing?: string | Function): Animator;
23
stop(): void;
24
pause(): void;
25
resume(): void;
26
27
// Configuration
28
getLoop(): boolean;
29
setLoop(loop: boolean): Animator;
30
}
31
```
32
33
### Element Animation Methods
34
35
All graphics elements provide animation methods:
36
37
```typescript { .api }
38
interface Element {
39
animate(props?: string): Animator;
40
animateStyle(): Animator;
41
animateShape(): Animator;
42
stopAnimation(forwardToLast?: boolean): this;
43
44
// Animation state queries
45
isAnimationFinished(): boolean;
46
getAnimationDelay(): number;
47
}
48
```
49
50
## Built-in Easing Functions
51
52
ZRender includes a comprehensive set of easing functions:
53
54
### Basic Easing
55
56
```typescript { .api }
57
function linear(t: number): number;
58
function easeInQuad(t: number): number;
59
function easeOutQuad(t: number): number;
60
function easeInOutQuad(t: number): number;
61
function easeInCubic(t: number): number;
62
function easeOutCubic(t: number): number;
63
function easeInOutCubic(t: number): number;
64
function easeInQuart(t: number): number;
65
function easeOutQuart(t: number): number;
66
function easeInOutQuart(t: number): number;
67
function easeInQuint(t: number): number;
68
function easeOutQuint(t: number): number;
69
function easeInOutQuint(t: number): number;
70
```
71
72
### Advanced Easing
73
74
```typescript { .api }
75
function easeInSine(t: number): number;
76
function easeOutSine(t: number): number;
77
function easeInOutSine(t: number): number;
78
function easeInExpo(t: number): number;
79
function easeOutExpo(t: number): number;
80
function easeInOutExpo(t: number): number;
81
function easeInCirc(t: number): number;
82
function easeOutCirc(t: number): number;
83
function easeInOutCirc(t: number): number;
84
```
85
86
### Elastic and Bounce
87
88
```typescript { .api }
89
function easeInElastic(t: number): number;
90
function easeOutElastic(t: number): number;
91
function easeInOutElastic(t: number): number;
92
function easeInBack(t: number): number;
93
function easeOutBack(t: number): number;
94
function easeInOutBack(t: number): number;
95
function easeInBounce(t: number): number;
96
function easeOutBounce(t: number): number;
97
function easeInOutBounce(t: number): number;
98
```
99
100
### Convenience Aliases
101
102
```typescript { .api }
103
const easeIn: typeof easeInQuad;
104
const easeOut: typeof easeOutQuad;
105
const easeInOut: typeof easeInOutQuad;
106
const ease: typeof easeInOut;
107
```
108
109
## Animation Configuration
110
111
### Clip Interface
112
113
```typescript { .api }
114
interface Clip {
115
life: number; // Duration in milliseconds
116
delay: number; // Delay before starting
117
loop: boolean; // Whether to loop
118
gap: number; // Gap between loops
119
easing: string | Function; // Easing function
120
onframe: (percent: number) => void; // Frame callback
121
ondestroy: () => void; // Cleanup callback
122
onrestart: () => void; // Restart callback
123
}
124
```
125
126
### Animation Options
127
128
```typescript { .api }
129
interface AnimationOptions {
130
duration?: number; // Animation duration
131
easing?: string | Function; // Easing function
132
delay?: number; // Start delay
133
loop?: boolean; // Loop animation
134
gap?: number; // Gap between loops
135
during?: (percent: number) => void; // Progress callback
136
done?: () => void; // Completion callback
137
aborted?: () => void; // Abortion callback
138
}
139
```
140
141
## Path Morphing
142
143
Advanced morphing capabilities for transforming between different paths:
144
145
```typescript { .api }
146
namespace morph {
147
function morphPath(from: string, to: string, animationOpts?: AnimationOptions): string;
148
function combineMorphing(morphList: any[]): any;
149
function isCombineMorphing(obj: any): boolean;
150
}
151
```
152
153
## Usage Examples
154
155
### Basic Property Animation
156
157
```typescript
158
import { Circle } from "zrender";
159
160
const circle = new Circle({
161
shape: { cx: 100, cy: 100, r: 30 },
162
style: { fill: '#74b9ff' },
163
position: [0, 0]
164
});
165
166
// Animate position
167
circle.animate('position')
168
.when(1000, [200, 150]) // Move to (200, 150) over 1 second
169
.when(2000, [100, 100]) // Return to (100, 100) over next second
170
.start('easeInOut');
171
172
// Animate shape properties
173
circle.animate('shape')
174
.when(1500, { r: 60 }) // Grow radius to 60
175
.start('easeOutElastic');
176
177
// Animate style properties
178
circle.animate('style')
179
.when(800, { fill: '#e17055', opacity: 0.7 })
180
.start('easeInOutQuad');
181
182
zr.add(circle);
183
```
184
185
### Complex Animation Sequences
186
187
```typescript
188
import { Rect } from "zrender";
189
190
const rect = new Rect({
191
shape: { x: 50, y: 50, width: 80, height: 60 },
192
style: { fill: '#00b894' }
193
});
194
195
// Chain multiple animations
196
rect.animate('position')
197
.when(1000, [200, 50])
198
.when(2000, [200, 200])
199
.when(3000, [50, 200])
200
.when(4000, [50, 50])
201
.start('easeInOutCubic');
202
203
// Parallel animation on different properties
204
rect.animate('shape')
205
.delay(500) // Start after 500ms delay
206
.when(1000, { width: 120, height: 40 })
207
.when(2000, { width: 40, height: 120 })
208
.when(3000, { width: 80, height: 60 })
209
.start('easeOutBounce');
210
211
// Color animation with callbacks
212
rect.animate('style')
213
.when(1000, { fill: '#fdcb6e' })
214
.when(2000, { fill: '#e84393' })
215
.when(3000, { fill: '#74b9ff' })
216
.when(4000, { fill: '#00b894' })
217
.during((percent) => {
218
// Custom logic during animation
219
if (percent > 0.5) {
220
rect.style.shadowBlur = (percent - 0.5) * 20;
221
}
222
})
223
.done(() => {
224
console.log('Animation completed!');
225
})
226
.start();
227
228
zr.add(rect);
229
```
230
231
### Easing Function Demonstrations
232
233
```typescript
234
import { Circle } from "zrender";
235
236
// Create circles to demonstrate different easing functions
237
const easingFunctions = [
238
'linear', 'easeInQuad', 'easeOutQuad', 'easeInOutQuad',
239
'easeInCubic', 'easeOutBounce', 'easeOutElastic', 'easeInBack'
240
];
241
242
easingFunctions.forEach((easing, index) => {
243
const circle = new Circle({
244
shape: { cx: 50, cy: 50 + index * 60, r: 20 },
245
style: { fill: '#74b9ff' }
246
});
247
248
// Animate position with different easing
249
circle.animate('position')
250
.when(2000, [400, 50 + index * 60])
251
.setLoop(true) // Loop the animation
252
.start(easing);
253
254
// Add label
255
const label = new Text({
256
style: {
257
text: easing,
258
fontSize: 12,
259
fill: '#2d3436'
260
},
261
position: [460, 55 + index * 60]
262
});
263
264
zr.add(circle);
265
zr.add(label);
266
});
267
```
268
269
### Interactive Animations
270
271
```typescript
272
import { Star } from "zrender";
273
274
const star = new Star({
275
shape: { cx: 200, cy: 200, n: 5, r0: 30, r: 60 },
276
style: { fill: '#fdcb6e', stroke: '#e17055', lineWidth: 2 }
277
});
278
279
// Hover animations
280
star.on('mouseover', () => {
281
star.stopAnimation(); // Stop any running animations
282
283
star.animate('shape')
284
.when(300, { r: 80, r0: 40 })
285
.start('easeOutElastic');
286
287
star.animate('style')
288
.when(200, { fill: '#fff5b4', shadowBlur: 15, shadowColor: '#fdcb6e' })
289
.start();
290
});
291
292
star.on('mouseout', () => {
293
star.stopAnimation();
294
295
star.animate('shape')
296
.when(300, { r: 60, r0: 30 })
297
.start('easeOutQuad');
298
299
star.animate('style')
300
.when(200, { fill: '#fdcb6e', shadowBlur: 0 })
301
.start();
302
});
303
304
// Click animation with rotation
305
star.on('click', () => {
306
star.animate('rotation')
307
.when(1000, Math.PI * 2) // Full rotation
308
.done(() => {
309
star.rotation = 0; // Reset rotation
310
})
311
.start('easeInOutCubic');
312
});
313
314
zr.add(star);
315
```
316
317
### Timeline Animations
318
319
```typescript
320
import { Group, Circle, Rect, Text } from "zrender";
321
322
// Create a group of elements for coordinated animation
323
const animationGroup = new Group();
324
325
const elements = [];
326
for (let i = 0; i < 5; i++) {
327
const circle = new Circle({
328
shape: { cx: 100 + i * 80, cy: 200, r: 25 },
329
style: { fill: `hsl(${i * 60}, 70%, 60%)` }
330
});
331
elements.push(circle);
332
animationGroup.add(circle);
333
}
334
335
// Staggered animation sequence
336
elements.forEach((element, index) => {
337
const delay = index * 200; // 200ms stagger
338
339
// Bounce animation
340
element.animate('position')
341
.delay(delay)
342
.when(600, [0, -100]) // Move up (relative to group)
343
.when(1200, [0, 0]) // Return to original position
344
.start('easeOutBounce');
345
346
// Scale animation
347
element.animate('scale')
348
.delay(delay + 300)
349
.when(400, [1.5, 1.5])
350
.when(800, [1, 1])
351
.start('easeInOutQuad');
352
});
353
354
// Group-level animation
355
animationGroup.animate('position')
356
.delay(1500)
357
.when(1000, [0, 100])
358
.start('easeInOutCubic');
359
360
zr.add(animationGroup);
361
```
362
363
### Custom Easing Functions
364
365
```typescript
366
import { Rect } from "zrender";
367
368
// Define custom easing function
369
const customEasing = (t: number): number => {
370
// Elastic overshoot effect
371
return t === 0 ? 0 : t === 1 ? 1 :
372
-Math.pow(2, 10 * (t - 1)) * Math.sin((t - 1.1) * 5 * Math.PI);
373
};
374
375
// Apply custom easing
376
const rect = new Rect({
377
shape: { x: 50, y: 150, width: 60, height: 40 },
378
style: { fill: '#e84393' }
379
});
380
381
rect.animate('position')
382
.when(2000, [400, 150])
383
.start(customEasing);
384
385
// Bezier curve easing (cubic-bezier equivalent)
386
const bezierEasing = (t: number): number => {
387
// Equivalent to cubic-bezier(0.25, 0.46, 0.45, 0.94)
388
const p0 = 0, p1 = 0.25, p2 = 0.45, p3 = 1;
389
const u = 1 - t;
390
return 3 * u * u * t * p1 + 3 * u * t * t * p2 + t * t * t * p3;
391
};
392
393
rect.animate('shape')
394
.when(2000, { width: 120, height: 80 })
395
.start(bezierEasing);
396
397
zr.add(rect);
398
```
399
400
### Animation Control and Management
401
402
```typescript
403
import { Circle } from "zrender";
404
405
const controlledCircle = new Circle({
406
shape: { cx: 200, cy: 200, r: 40 },
407
style: { fill: '#74b9ff' }
408
});
409
410
// Create controllable animation
411
let currentAnimation: Animator;
412
413
const startAnimation = () => {
414
currentAnimation = controlledCircle.animate('position')
415
.when(3000, [400, 200])
416
.when(6000, [200, 200])
417
.setLoop(true)
418
.start('easeInOutQuad');
419
};
420
421
const pauseAnimation = () => {
422
if (currentAnimation) {
423
currentAnimation.pause();
424
}
425
};
426
427
const resumeAnimation = () => {
428
if (currentAnimation) {
429
currentAnimation.resume();
430
}
431
};
432
433
const stopAnimation = () => {
434
if (currentAnimation) {
435
currentAnimation.stop();
436
}
437
};
438
439
// Control via keyboard events
440
document.addEventListener('keydown', (e) => {
441
switch(e.key) {
442
case 's': startAnimation(); break;
443
case 'p': pauseAnimation(); break;
444
case 'r': resumeAnimation(); break;
445
case 'x': stopAnimation(); break;
446
}
447
});
448
449
// Click to toggle animation
450
controlledCircle.on('click', () => {
451
if (controlledCircle.isAnimationFinished()) {
452
startAnimation();
453
} else {
454
stopAnimation();
455
}
456
});
457
458
zr.add(controlledCircle);
459
```
460
461
### Performance Optimization
462
463
```typescript
464
import { Group, Circle } from "zrender";
465
466
// Animate many elements efficiently
467
const createOptimizedAnimation = () => {
468
const group = new Group();
469
const elements: Circle[] = [];
470
471
// Create many elements
472
for (let i = 0; i < 100; i++) {
473
const circle = new Circle({
474
shape: {
475
cx: Math.random() * 800,
476
cy: Math.random() * 600,
477
r: 5 + Math.random() * 10
478
},
479
style: {
480
fill: `hsl(${Math.random() * 360}, 70%, 60%)`,
481
opacity: 0.8
482
}
483
});
484
elements.push(circle);
485
group.add(circle);
486
}
487
488
// Use single group animation instead of individual animations
489
// This is more performant for coordinated movements
490
group.animate('rotation')
491
.when(10000, Math.PI * 2)
492
.setLoop(true)
493
.start('linear');
494
495
// Stagger individual element animations efficiently
496
elements.forEach((element, index) => {
497
if (index % 10 === 0) { // Animate every 10th element
498
element.animate('scale')
499
.delay(Math.random() * 2000)
500
.when(1000, [1.5, 1.5])
501
.when(2000, [1, 1])
502
.setLoop(true)
503
.start('easeInOutSine');
504
}
505
});
506
507
return group;
508
};
509
510
const optimizedGroup = createOptimizedAnimation();
511
zr.add(optimizedGroup);
512
```