0
# Animation & Interaction
1
2
Animation and interaction systems in PIXI.js that enable dynamic content and user input handling through frame-based animations, global update loops, and comprehensive event management.
3
4
## Core Animation Systems
5
6
### AnimatedSprite Class { .api }
7
8
Frame-based sprite animation system that automatically cycles through texture sequences.
9
10
```typescript
11
class AnimatedSprite extends Sprite {
12
// Animation Control
13
animationSpeed: number; // Speed multiplier (default: 1)
14
loop: boolean; // Whether animation repeats (default: true)
15
playing: boolean; // Current playback state (readonly)
16
autoUpdate: boolean; // Use Ticker.shared for updates
17
updateAnchor: boolean; // Update anchor from texture defaultAnchor
18
19
// Frame Management
20
textures: Texture[] | FrameObject[]; // Animation frame sequence
21
currentFrame: number; // Current frame index
22
totalFrames: number; // Total frame count (readonly)
23
24
// Event Callbacks
25
onComplete?: () => void; // Called when animation finishes
26
onFrameChange?: (frame: number) => void; // Called on frame change
27
onLoop?: () => void; // Called when animation loops
28
29
// Constructor
30
constructor(textures: Texture[] | FrameObject[], autoUpdate?: boolean);
31
32
// Playback Control
33
play(): void; // Start animation
34
stop(): void; // Stop animation
35
gotoAndPlay(frameNumber: number): void; // Jump to frame and play
36
gotoAndStop(frameNumber: number): void; // Jump to frame and stop
37
38
// Manual Updates
39
update(deltaTime: number): void; // Update animation state
40
41
// Static Factory Methods
42
static fromFrames(frames: string[]): AnimatedSprite;
43
static fromImages(images: string[]): AnimatedSprite;
44
}
45
46
interface FrameObject {
47
texture: Texture; // Frame texture
48
time: number; // Duration in milliseconds
49
}
50
```
51
52
### Ticker System { .api }
53
54
Global update loop system that manages frame-based updates and timing.
55
56
```typescript
57
class Ticker {
58
// Static Properties
59
static targetFPMS: number; // Target frames per millisecond (0.06)
60
static shared: Ticker; // Shared ticker instance
61
static system: Ticker; // System ticker instance
62
63
// Timing Properties
64
deltaTime: number; // Scaled time between frames
65
deltaMS: number; // Milliseconds between frames (scaled)
66
elapsedMS: number; // Raw milliseconds between frames
67
lastTime: number; // Last update timestamp
68
speed: number; // Time scale multiplier
69
started: boolean; // Whether ticker is running
70
autoStart: boolean; // Auto-start when listeners added
71
FPS: number; // Current frames per second (readonly)
72
count: number; // Number of listeners (readonly)
73
74
// FPS Limits
75
minFPS: number; // Minimum FPS (caps deltaTime)
76
maxFPS: number; // Maximum FPS (throttles updates)
77
78
// Listener Management
79
add<T>(fn: TickerCallback<T>, context?: T, priority?: number): this;
80
addOnce<T>(fn: TickerCallback<T>, context?: T, priority?: number): this;
81
remove<T>(fn: TickerCallback<T>, context?: T): this;
82
83
// Lifecycle
84
start(): void; // Start the ticker
85
stop(): void; // Stop the ticker
86
destroy(): void; // Clean up resources
87
88
// Manual Updates
89
update(currentTime?: number): void; // Manual frame update
90
}
91
92
type TickerCallback<T> = (this: T, deltaTime: number) => any;
93
94
class TickerListener<T = any> {
95
priority: number; // Execution priority
96
next: TickerListener; // Next listener in chain
97
previous: TickerListener; // Previous listener in chain
98
}
99
100
enum UPDATE_PRIORITY {
101
INTERACTION = 50, // Event system updates
102
HIGH = 25, // AnimatedSprite updates
103
NORMAL = 0, // Default priority
104
LOW = -25, // Application rendering
105
UTILITY = -50 // Background utilities
106
}
107
```
108
109
## Event System
110
111
### FederatedEvent Base { .api }
112
113
Core event class that provides DOM-compatible event handling with bubbling and propagation.
114
115
```typescript
116
class FederatedEvent<N extends UIEvent | PixiTouch = UIEvent | PixiTouch> implements UIEvent {
117
// Event Properties
118
type: string; // Event type (e.g., 'mousedown')
119
target: FederatedEventTarget; // Event target
120
currentTarget: FederatedEventTarget; // Current event target in propagation
121
timeStamp: number; // Event timestamp
122
bubbles: boolean; // Whether event bubbles
123
cancelable: boolean; // Whether event can be cancelled
124
defaultPrevented: boolean; // Whether preventDefault was called
125
126
// Event Phase
127
eventPhase: number; // Current propagation phase
128
readonly NONE: 0;
129
readonly CAPTURING_PHASE: 1;
130
readonly AT_TARGET: 2;
131
readonly BUBBLING_PHASE: 3;
132
133
// Propagation Control
134
propagationStopped: boolean;
135
propagationImmediatelyStopped: boolean;
136
137
// Native Event Reference
138
nativeEvent: N; // Original native event
139
originalEvent: FederatedEvent<N>; // Original federated event
140
141
// DOM Coordinates
142
layer: Point; // Layer-relative coordinates
143
page: Point; // Page-relative coordinates
144
readonly layerX: number;
145
readonly layerY: number;
146
readonly pageX: number;
147
readonly pageY: number;
148
149
// Event Management
150
manager: EventBoundary; // Event boundary manager
151
path: FederatedEventTarget[]; // Propagation path
152
153
// Methods
154
preventDefault(): void; // Prevent default behavior
155
stopPropagation(): void; // Stop propagation to next target
156
stopImmediatePropagation(): void; // Stop all further propagation
157
composedPath(): FederatedEventTarget[]; // Get propagation path
158
}
159
```
160
161
### Mouse and Pointer Events { .api }
162
163
Extended event classes for mouse and pointer interactions.
164
165
```typescript
166
class FederatedMouseEvent extends FederatedEvent<MouseEvent | PointerEvent | PixiTouch>
167
implements MouseEvent {
168
169
// Mouse State
170
button: number; // Pressed button (0=left, 1=middle, 2=right)
171
buttons: number; // Currently pressed buttons bitmask
172
173
// Modifier Keys
174
altKey: boolean; // Alt key pressed
175
ctrlKey: boolean; // Ctrl key pressed
176
metaKey: boolean; // Meta key pressed
177
shiftKey: boolean; // Shift key pressed
178
179
// Coordinates
180
client: Point; // Client coordinates
181
readonly clientX: number;
182
readonly clientY: number;
183
readonly x: number; // Alias for clientX
184
readonly y: number; // Alias for clientY
185
186
movement: Point; // Movement delta
187
readonly movementX: number;
188
readonly movementY: number;
189
190
offset: Point; // Target-relative coordinates
191
readonly offsetX: number;
192
readonly offsetY: number;
193
194
global: Point; // World coordinates
195
readonly globalX: number;
196
readonly globalY: number;
197
198
screen: Point; // Screen coordinates
199
readonly screenX: number;
200
readonly screenY: number;
201
202
detail: number; // Click count
203
204
// Utility Methods
205
getLocalPosition<P extends IPointData>(
206
displayObject: DisplayObject,
207
point?: P,
208
globalPos?: IPointData
209
): P;
210
211
getModifierState(key: string): boolean;
212
}
213
214
class FederatedPointerEvent extends FederatedMouseEvent implements PointerEvent {
215
// Pointer Properties
216
pointerId: number; // Unique pointer identifier
217
pointerType: string; // 'mouse', 'pen', 'touch'
218
isPrimary: boolean; // Whether primary pointer
219
220
// Touch/Pen Properties
221
width: number; // Contact width
222
height: number; // Contact height
223
pressure: number; // Applied pressure
224
tangentialPressure: number; // Barrel pressure (stylus)
225
tiltX: number; // X-axis tilt angle
226
tiltY: number; // Y-axis tilt angle
227
twist: number; // Rotation angle
228
altitudeAngle: number; // Altitude angle
229
azimuthAngle: number; // Azimuth angle
230
231
// Coalesced Events
232
getCoalescedEvents(): PointerEvent[];
233
getPredictedEvents(): PointerEvent[]; // Not supported
234
}
235
236
class FederatedWheelEvent extends FederatedMouseEvent implements WheelEvent {
237
deltaMode: number; // Delta measurement units
238
deltaX: number; // Horizontal scroll delta
239
deltaY: number; // Vertical scroll delta
240
deltaZ: number; // Z-axis scroll delta
241
}
242
```
243
244
### Event Target Interface { .api }
245
246
Interface defining interactive display object capabilities.
247
248
```typescript
249
interface FederatedEventTarget extends utils.EventEmitter, EventTarget {
250
// Interactive Properties
251
cursor: Cursor | string; // Preferred cursor style
252
interactive: boolean; // Enable interaction events
253
eventMode: EventMode; // Interaction mode
254
interactiveChildren: boolean; // Children can be interactive
255
hitArea: IHitArea | null; // Custom hit testing area
256
257
// Hierarchy
258
readonly parent?: FederatedEventTarget;
259
readonly children?: ReadonlyArray<FederatedEventTarget>;
260
261
// Methods
262
isInteractive(): boolean; // Check if interactive
263
264
// Event Handlers (Property-based)
265
onclick: FederatedEventHandler | null;
266
onmousedown: FederatedEventHandler | null;
267
onmouseenter: FederatedEventHandler | null;
268
onmouseleave: FederatedEventHandler | null;
269
onmousemove: FederatedEventHandler | null;
270
onglobalmousemove: FederatedEventHandler | null;
271
onmouseout: FederatedEventHandler | null;
272
onmouseover: FederatedEventHandler | null;
273
onmouseup: FederatedEventHandler | null;
274
onmouseupoutside: FederatedEventHandler | null;
275
276
onpointercancel: FederatedEventHandler | null;
277
onpointerdown: FederatedEventHandler | null;
278
onpointerenter: FederatedEventHandler | null;
279
onpointerleave: FederatedEventHandler | null;
280
onpointermove: FederatedEventHandler | null;
281
onglobalpointermove: FederatedEventHandler | null;
282
onpointerout: FederatedEventHandler | null;
283
onpointerover: FederatedEventHandler | null;
284
onpointertap: FederatedEventHandler | null;
285
onpointerup: FederatedEventHandler | null;
286
onpointerupoutside: FederatedEventHandler | null;
287
288
onrightclick: FederatedEventHandler | null;
289
onrightdown: FederatedEventHandler | null;
290
onrightup: FederatedEventHandler | null;
291
onrightupoutside: FederatedEventHandler | null;
292
293
ontap: FederatedEventHandler | null;
294
ontouchcancel: FederatedEventHandler | null;
295
ontouchend: FederatedEventHandler | null;
296
ontouchendoutside: FederatedEventHandler | null;
297
ontouchmove: FederatedEventHandler | null;
298
onglobaltouchmove: FederatedEventHandler | null;
299
ontouchstart: FederatedEventHandler | null;
300
301
onwheel: FederatedEventHandler<FederatedWheelEvent> | null;
302
303
// DOM-compatible Methods
304
addEventListener(type: string, listener: EventListenerOrEventListenerObject,
305
options?: AddListenerOptions): void;
306
removeEventListener(type: string, listener: EventListenerOrEventListenerObject,
307
options?: RemoveListenerOptions): void;
308
dispatchEvent(event: Event): boolean;
309
}
310
311
type EventMode = 'none' | 'passive' | 'auto' | 'static' | 'dynamic';
312
type FederatedEventHandler<T = FederatedPointerEvent> = (event: T) => void;
313
type Cursor = 'auto' | 'default' | 'none' | 'pointer' | 'grab' | 'grabbing' | /* ... */;
314
315
interface IHitArea {
316
contains(x: number, y: number): boolean;
317
}
318
```
319
320
### EventBoundary { .api }
321
322
Event management system that handles hit testing, event mapping, and propagation.
323
324
```typescript
325
class EventBoundary {
326
// Core Properties
327
rootTarget: DisplayObject; // Root event target
328
dispatch: utils.EventEmitter; // Global event emitter
329
cursor: Cursor | string; // Current cursor style
330
331
// Configuration
332
moveOnAll: boolean; // Emit move events on all objects
333
enableGlobalMoveEvents: boolean; // Enable global move events
334
335
constructor(rootTarget?: DisplayObject);
336
337
// Event Mapping
338
addEventMapping(type: string, fn: (e: FederatedEvent) => void): void;
339
mapEvent(e: FederatedEvent): void;
340
dispatchEvent(e: FederatedEvent, type?: string): void;
341
342
// Hit Testing
343
hitTest(x: number, y: number): DisplayObject;
344
345
// Event Propagation
346
propagate(e: FederatedEvent, type?: string): void;
347
propagationPath(target: FederatedEventTarget): FederatedEventTarget[];
348
all(e: FederatedEvent, type?: string | string[],
349
targets?: FederatedEventTarget[]): void;
350
351
// Event Creation
352
protected createPointerEvent(from: FederatedPointerEvent,
353
type?: string,
354
target?: FederatedEventTarget): FederatedPointerEvent;
355
protected createWheelEvent(from: FederatedWheelEvent): FederatedWheelEvent;
356
protected clonePointerEvent(from: FederatedPointerEvent,
357
type?: string): FederatedPointerEvent;
358
359
// Event Pool Management
360
protected allocateEvent<T extends FederatedEvent>(
361
constructor: { new(boundary: EventBoundary): T }
362
): T;
363
protected freeEvent<T extends FederatedEvent>(event: T): void;
364
}
365
```
366
367
## Type Definitions
368
369
### Animation Types
370
371
```typescript
372
// Animation frame with duration
373
interface FrameObject {
374
texture: Texture; // Frame texture
375
time: number; // Duration in milliseconds
376
}
377
378
// Ticker callback signature
379
type TickerCallback<T> = (this: T, deltaTime: number) => any;
380
381
// Update priority levels
382
enum UPDATE_PRIORITY {
383
INTERACTION = 50, // Event system priority
384
HIGH = 25, // Animation priority
385
NORMAL = 0, // Default priority
386
LOW = -25, // Rendering priority
387
UTILITY = -50 // Background tasks
388
}
389
```
390
391
### Event Types
392
393
```typescript
394
// Event modes for display objects
395
type EventMode = 'none' // No events
396
| 'passive' // Hit tested but no events
397
| 'auto' // Hit tested if parent interactive
398
| 'static' // Full interactivity
399
| 'dynamic'; // Interactive + mock events
400
401
// Event handler function
402
type FederatedEventHandler<T = FederatedPointerEvent> = (event: T) => void;
403
404
// Cursor styles
405
type Cursor = 'auto' | 'default' | 'none' | 'context-menu' | 'help'
406
| 'pointer' | 'progress' | 'wait' | 'cell' | 'crosshair'
407
| 'text' | 'vertical-text' | 'alias' | 'copy' | 'move'
408
| 'no-drop' | 'not-allowed' | 'e-resize' | 'n-resize'
409
| 'ne-resize' | 'nw-resize' | 's-resize' | 'se-resize'
410
| 'sw-resize' | 'w-resize' | 'ns-resize' | 'ew-resize'
411
| 'nesw-resize' | 'col-resize' | 'nwse-resize' | 'row-resize'
412
| 'all-scroll' | 'zoom-in' | 'zoom-out' | 'grab' | 'grabbing';
413
414
// Hit area interface
415
interface IHitArea {
416
contains(x: number, y: number): boolean;
417
}
418
419
// Touch event extension
420
interface PixiTouch extends Touch {
421
button: number;
422
buttons: number;
423
isPrimary: boolean;
424
width: number;
425
height: number;
426
tiltX: number;
427
tiltY: number;
428
pointerType: string;
429
pointerId: number;
430
pressure: number;
431
twist: number;
432
tangentialPressure: number;
433
layerX: number;
434
layerY: number;
435
offsetX: number;
436
offsetY: number;
437
isNormalized: boolean;
438
}
439
```
440
441
## Usage Examples
442
443
### Frame-Based Animation
444
445
```typescript
446
import { AnimatedSprite, Assets, Application } from 'pixi.js';
447
448
// Create application
449
const app = new Application();
450
document.body.appendChild(app.view);
451
452
// Load spritesheet with animation data
453
const sheet = await Assets.load('assets/character.json');
454
const walkAnimation = new AnimatedSprite(sheet.animations['walk']);
455
456
// Configure animation
457
walkAnimation.animationSpeed = 0.167; // 10 FPS at 60 FPS
458
walkAnimation.loop = true;
459
walkAnimation.anchor.set(0.5);
460
walkAnimation.position.set(400, 300);
461
462
// Add event handlers
463
walkAnimation.onComplete = () => {
464
console.log('Animation completed!');
465
};
466
467
walkAnimation.onFrameChange = (currentFrame) => {
468
console.log(`Frame changed to: ${currentFrame}`);
469
};
470
471
walkAnimation.onLoop = () => {
472
console.log('Animation looped!');
473
};
474
475
// Add to stage and play
476
app.stage.addChild(walkAnimation);
477
walkAnimation.play();
478
479
// Manual animation from textures
480
const textures = [
481
Assets.get('frame1.png'),
482
Assets.get('frame2.png'),
483
Assets.get('frame3.png')
484
];
485
486
const manualAnimation = new AnimatedSprite(textures);
487
manualAnimation.animationSpeed = 0.5;
488
489
// Animation with custom frame durations
490
const timedFrames = [
491
{ texture: Assets.get('idle.png'), time: 1000 }, // 1 second
492
{ texture: Assets.get('blink.png'), time: 100 }, // 0.1 seconds
493
{ texture: Assets.get('idle.png'), time: 2000 } // 2 seconds
494
];
495
496
const timedAnimation = new AnimatedSprite(timedFrames);
497
```
498
499
### Custom Animation with Ticker
500
501
```typescript
502
import { Ticker, Sprite, Assets } from 'pixi.js';
503
504
class CustomAnimator {
505
private sprites: Sprite[] = [];
506
private time: number = 0;
507
508
constructor() {
509
// Add to shared ticker with high priority
510
Ticker.shared.add(this.update, this, UPDATE_PRIORITY.HIGH);
511
}
512
513
private update = (deltaTime: number): void => {
514
this.time += deltaTime;
515
516
// Update all sprites
517
this.sprites.forEach((sprite, index) => {
518
// Floating animation
519
sprite.y += Math.sin(this.time * 0.1 + index) * 0.5;
520
521
// Rotation animation
522
sprite.rotation += deltaTime * 0.02;
523
524
// Scale pulsing
525
const scale = 1 + Math.sin(this.time * 0.15 + index) * 0.1;
526
sprite.scale.set(scale);
527
});
528
};
529
530
addSprite(sprite: Sprite): void {
531
this.sprites.push(sprite);
532
}
533
534
destroy(): void {
535
Ticker.shared.remove(this.update, this);
536
}
537
}
538
539
// Usage
540
const animator = new CustomAnimator();
541
const sprite = new Sprite(Assets.get('star.png'));
542
animator.addSprite(sprite);
543
544
// Create custom ticker for specific timing
545
const customTicker = new Ticker();
546
customTicker.maxFPS = 30; // Limit to 30 FPS
547
customTicker.add((deltaTime) => {
548
// Custom update logic at 30 FPS
549
console.log(`Update at ${customTicker.FPS} FPS`);
550
});
551
customTicker.start();
552
```
553
554
### Mouse and Touch Interaction
555
556
```typescript
557
import { Sprite, Assets, FederatedPointerEvent } from 'pixi.js';
558
559
const interactiveSprite = new Sprite(Assets.get('button.png'));
560
interactiveSprite.anchor.set(0.5);
561
interactiveSprite.position.set(400, 300);
562
563
// Enable interactivity
564
interactiveSprite.eventMode = 'static';
565
interactiveSprite.cursor = 'pointer';
566
567
// Property-based event handlers
568
interactiveSprite.onpointerdown = (event: FederatedPointerEvent) => {
569
console.log('Pointer down:', event.pointerId, event.pointerType);
570
interactiveSprite.tint = 0x888888; // Darken on press
571
};
572
573
interactiveSprite.onpointerup = (event: FederatedPointerEvent) => {
574
console.log('Pointer up:', event.pointerId);
575
interactiveSprite.tint = 0xFFFFFF; // Restore color
576
};
577
578
interactiveSprite.onpointermove = (event: FederatedPointerEvent) => {
579
// Get local coordinates
580
const localPos = event.getLocalPosition(interactiveSprite);
581
console.log('Local position:', localPos.x, localPos.y);
582
583
// Check modifier keys
584
if (event.shiftKey) {
585
console.log('Shift key held while moving');
586
}
587
};
588
589
// EventListener-style handlers
590
interactiveSprite.addEventListener('click', (event: FederatedPointerEvent) => {
591
console.log(`Clicked ${event.detail} times`); // Click count
592
593
// Handle multi-touch
594
if (event.pointerType === 'touch') {
595
console.log('Touch interaction');
596
}
597
});
598
599
// Drag functionality
600
let isDragging = false;
601
let dragOffset = { x: 0, y: 0 };
602
603
interactiveSprite.addEventListener('pointerdown', (event: FederatedPointerEvent) => {
604
isDragging = true;
605
const localPos = event.getLocalPosition(interactiveSprite.parent);
606
dragOffset.x = localPos.x - interactiveSprite.x;
607
dragOffset.y = localPos.y - interactiveSprite.y;
608
609
// Capture pointer for drag-outside behavior
610
interactiveSprite.setPointerCapture?.(event.pointerId);
611
});
612
613
interactiveSprite.addEventListener('globalpointermove', (event: FederatedPointerEvent) => {
614
if (isDragging) {
615
const globalPos = event.global;
616
interactiveSprite.x = globalPos.x - dragOffset.x;
617
interactiveSprite.y = globalPos.y - dragOffset.y;
618
}
619
});
620
621
interactiveSprite.addEventListener('pointerup', () => {
622
isDragging = false;
623
});
624
625
interactiveSprite.addEventListener('pointerupoutside', () => {
626
isDragging = false;
627
});
628
629
app.stage.addChild(interactiveSprite);
630
```
631
632
### Event Propagation and Bubbling
633
634
```typescript
635
import { Container, Graphics, FederatedPointerEvent } from 'pixi.js';
636
637
// Create nested container structure
638
const outerContainer = new Container();
639
const middleContainer = new Container();
640
const innerSprite = new Graphics()
641
.beginFill(0xFF0000)
642
.drawCircle(0, 0, 50)
643
.endFill();
644
645
// Set up hierarchy
646
outerContainer.addChild(middleContainer);
647
middleContainer.addChild(innerSprite);
648
app.stage.addChild(outerContainer);
649
650
// Position containers
651
outerContainer.position.set(200, 200);
652
middleContainer.position.set(100, 100);
653
654
// Enable interactivity
655
outerContainer.eventMode = 'static';
656
middleContainer.eventMode = 'static';
657
innerSprite.eventMode = 'static';
658
659
// Event capturing (fires before target)
660
outerContainer.addEventListener('click', (event: FederatedPointerEvent) => {
661
console.log('Outer container capturing phase');
662
}, { capture: true });
663
664
// Event bubbling (fires after target)
665
outerContainer.addEventListener('click', (event: FederatedPointerEvent) => {
666
console.log('Outer container bubbling phase');
667
console.log('Event phase:', event.eventPhase);
668
});
669
670
middleContainer.addEventListener('click', (event: FederatedPointerEvent) => {
671
console.log('Middle container clicked');
672
673
// Stop propagation to parent
674
if (event.shiftKey) {
675
event.stopPropagation();
676
console.log('Propagation stopped');
677
}
678
});
679
680
innerSprite.addEventListener('click', (event: FederatedPointerEvent) => {
681
console.log('Inner sprite clicked (target)');
682
console.log('Composed path:', event.composedPath().map(t => t.constructor.name));
683
684
// Prevent default and stop immediate propagation
685
if (event.ctrlKey) {
686
event.stopImmediatePropagation();
687
console.log('Immediate propagation stopped');
688
}
689
});
690
```
691
692
### Advanced Event Handling
693
694
```typescript
695
import { EventBoundary, FederatedPointerEvent, Graphics } from 'pixi.js';
696
697
// Custom hit area
698
const customButton = new Graphics()
699
.beginFill(0x00FF00)
700
.drawRect(0, 0, 100, 50)
701
.endFill();
702
703
// Define circular hit area larger than visual
704
customButton.hitArea = new Circle(50, 25, 40);
705
customButton.eventMode = 'static';
706
707
// Multi-pointer tracking
708
const activePointers = new Map<number, FederatedPointerEvent>();
709
710
customButton.addEventListener('pointerdown', (event: FederatedPointerEvent) => {
711
activePointers.set(event.pointerId, event);
712
console.log(`Active pointers: ${activePointers.size}`);
713
});
714
715
customButton.addEventListener('pointerup', (event: FederatedPointerEvent) => {
716
activePointers.delete(event.pointerId);
717
});
718
719
customButton.addEventListener('pointercancel', (event: FederatedPointerEvent) => {
720
activePointers.delete(event.pointerId);
721
});
722
723
// Gesture recognition example
724
let lastTapTime = 0;
725
let tapCount = 0;
726
727
customButton.addEventListener('tap', (event: FederatedPointerEvent) => {
728
const now = Date.now();
729
730
if (now - lastTapTime < 300) { // Double tap threshold
731
tapCount++;
732
} else {
733
tapCount = 1;
734
}
735
736
lastTapTime = now;
737
738
if (tapCount === 2) {
739
console.log('Double tap detected!');
740
tapCount = 0;
741
}
742
});
743
744
// Wheel event handling
745
customButton.addEventListener('wheel', (event: FederatedWheelEvent) => {
746
console.log('Wheel delta:', event.deltaY);
747
748
// Zoom based on wheel
749
const zoomFactor = event.deltaY > 0 ? 0.9 : 1.1;
750
customButton.scale.x *= zoomFactor;
751
customButton.scale.y *= zoomFactor;
752
753
// Prevent browser scrolling
754
event.preventDefault();
755
});
756
757
app.stage.addChild(customButton);
758
```
759
760
### Animation Timing and Control
761
762
```typescript
763
import { AnimatedSprite, Ticker, Assets } from 'pixi.js';
764
765
class AnimationController {
766
private animations: Map<string, AnimatedSprite> = new Map();
767
private globalSpeed: number = 1;
768
769
async loadAnimations() {
770
const sheet = await Assets.load('characters.json');
771
772
// Create different character animations
773
const walkAnim = new AnimatedSprite(sheet.animations['walk']);
774
const runAnim = new AnimatedSprite(sheet.animations['run']);
775
const idleAnim = new AnimatedSprite(sheet.animations['idle']);
776
777
// Configure each animation
778
walkAnim.animationSpeed = 0.167; // 10 FPS
779
runAnim.animationSpeed = 0.333; // 20 FPS
780
idleAnim.animationSpeed = 0.083; // 5 FPS
781
782
this.animations.set('walk', walkAnim);
783
this.animations.set('run', runAnim);
784
this.animations.set('idle', idleAnim);
785
}
786
787
playAnimation(name: string): void {
788
// Stop all animations
789
this.animations.forEach(anim => anim.stop());
790
791
// Play requested animation
792
const anim = this.animations.get(name);
793
if (anim) {
794
anim.gotoAndPlay(0);
795
}
796
}
797
798
setGlobalSpeed(speed: number): void {
799
this.globalSpeed = speed;
800
this.animations.forEach(anim => {
801
const baseSpeed = anim.animationSpeed / this.globalSpeed;
802
anim.animationSpeed = baseSpeed * speed;
803
});
804
}
805
806
// Custom frame interpolation
807
private frameInterpolation(anim: AnimatedSprite, targetFrame: number, duration: number): void {
808
const startFrame = anim.currentFrame;
809
const frameDistance = targetFrame - startFrame;
810
let elapsed = 0;
811
812
const updateLerp = (delta: number) => {
813
elapsed += delta;
814
const progress = Math.min(elapsed / duration, 1);
815
816
// Smooth interpolation
817
const smoothProgress = progress * progress * (3 - 2 * progress);
818
const currentFrame = startFrame + frameDistance * smoothProgress;
819
820
anim.currentFrame = Math.floor(currentFrame);
821
822
if (progress >= 1) {
823
Ticker.shared.remove(updateLerp);
824
}
825
};
826
827
Ticker.shared.add(updateLerp);
828
}
829
830
// Seamless animation transitions
831
transitionTo(fromName: string, toName: string, blendDuration: number = 0.5): void {
832
const fromAnim = this.animations.get(fromName);
833
const toAnim = this.animations.get(toName);
834
835
if (!fromAnim || !toAnim) return;
836
837
// Start blend
838
let blendProgress = 0;
839
const blendSpeed = 1 / (blendDuration * 60); // Assuming 60 FPS
840
841
const blendUpdate = (delta: number) => {
842
blendProgress += blendSpeed * delta;
843
844
// Cross-fade alpha values
845
fromAnim.alpha = 1 - blendProgress;
846
toAnim.alpha = blendProgress;
847
848
if (blendProgress >= 1) {
849
fromAnim.stop();
850
fromAnim.alpha = 1;
851
toAnim.alpha = 1;
852
Ticker.shared.remove(blendUpdate);
853
}
854
};
855
856
// Start target animation and blend
857
toAnim.gotoAndPlay(0);
858
toAnim.alpha = 0;
859
Ticker.shared.add(blendUpdate);
860
}
861
}
862
863
// Usage
864
const controller = new AnimationController();
865
await controller.loadAnimations();
866
867
// Keyboard controls
868
document.addEventListener('keydown', (event) => {
869
switch (event.key) {
870
case 'ArrowRight':
871
controller.playAnimation('run');
872
break;
873
case 'ArrowDown':
874
controller.playAnimation('walk');
875
break;
876
case ' ':
877
controller.playAnimation('idle');
878
break;
879
case '+':
880
controller.setGlobalSpeed(2);
881
break;
882
case '-':
883
controller.setGlobalSpeed(0.5);
884
break;
885
}
886
});
887
```
888
889
## Best Practices
890
891
### Performance Optimization
892
893
1. **Efficient Event Handling**: Use event delegation and avoid creating excessive listeners
894
2. **Ticker Management**: Remove unused ticker listeners and use appropriate priorities
895
3. **Animation Pooling**: Reuse AnimatedSprite instances for similar animations
896
4. **Hit Area Optimization**: Use simple geometric hit areas instead of pixel-perfect detection
897
898
### Memory Management
899
900
1. **Resource Cleanup**: Always call `destroy()` on unused animations and tickers
901
2. **Event Listener Removal**: Remove event listeners when objects are no longer needed
902
3. **Texture Sharing**: Share textures between multiple AnimatedSprite instances
903
4. **Pool Event Objects**: Use EventBoundary's event pooling for performance
904
905
### User Experience
906
907
1. **Responsive Feedback**: Provide immediate visual feedback for interactions
908
2. **Smooth Animations**: Use appropriate frame rates and easing for natural motion
909
3. **Accessibility**: Support keyboard navigation and screen readers
910
4. **Cross-Platform Compatibility**: Test touch interactions on mobile devices
911
912
This comprehensive system provides powerful animation and interaction capabilities that form the foundation for engaging, responsive applications with smooth animations and intuitive user interfaces.