Skip to content
PixiJS Animation & Interactivity — Ticker, Events, Drag-and-Drop, Filters

PixiJS Animation & Interactivity — Ticker, Events, Drag-and-Drop, Filters

DodaTech Updated Jun 6, 2026 12 min read

PixiJS animation uses a ticker that runs code every frame with delta time, keeping motion smooth. Combined with pointer events, you can build interactive scenes.

What You’ll Learn

  • How the Ticker drives frame-based animation with delta timing
  • How to make sprites interactive with eventMode and pointer events
  • The drag-and-drop pattern (stage-level tracking)
  • How hit areas define clickable regions
  • How filters (blur, color matrix) create visual effects
  • How to build a custom tween (animation) using the ticker

Why Animation & Interactivity Matter

Static graphics are informative — animated, interactive graphics are engaging. A threat dashboard that animates incoming alerts and lets you drag threat nodes to inspect them is far more usable than a static screenshot.

In Durga Antivirus Pro, PixiJS powers the real-time threat map. Security alerts pulse with animated icons, threat nodes are draggable for inspection, and color filters highlight critical vs. low-priority threats. These interactive features help security analysts spot anomalies in seconds rather than minutes.

Prerequisites: Complete https://tutorials.dodatech.com/frontend/libraries/pixijs/pixijs-getting-started/ and https://tutorials.dodatech.com/frontend/libraries/pixijs/pixijs-graphics-textures/. You should know how to create sprites, use the stage, and work with textures.

Learning Path

    flowchart LR
    GS["Getting Started"] --> GT["Graphics & Textures"]
    GT --> AI["Animation & Interactivity ⬅ You Are Here"]
    AI --> TF["Text & Bitmap Fonts"]
    TF --> PB["Performance & Best Practices"]
    style AI fill:#4a90d9,stroke:#fff,color:#fff
    linkStyle default stroke:#4a90d9,stroke-width:2
  

PixiJS Animation vs CSS Animations vs requestAnimationFrame

ApproachControlPerformanceComplexity
PixiJS TickerFrame-level, delta timeExcellent (GPU)Low
CSS animationsLimited transformsGood (GPU)Very low
requestAnimationFrameFull controlGoodMedium
GSAPTween-based, easingGoodMedium

When to use PixiJS Ticker: You need per-frame control of hundreds of objects — position, rotation, scale, alpha all changing simultaneously. The ticker automatically integrates with PixiJS’s render loop.

The Ticker — PixiJS’s Built-in Animation Clock

The Ticker runs a callback function every frame (typically 60 times per second). It provides a delta parameter that represents how much time passed since the last frame.

Think of the ticker like a metronome that ticks 60 times per second. Each tick, you move your objects a little bit. The delta value ensures movement stays smooth even when the frame rate dips.

app.ticker.add((delta) => {
  sprite.rotation += 0.1 * delta;
});

Why multiply by delta? Without delta, your animation runs slower when the frame rate drops — the sprite moves less because fewer frames execute. With delta, the movement per frame scales up to compensate, keeping the speed consistent at 30fps or 60fps.

Adding and Removing Callbacks

const tickerFn = (delta) => {
  sprite.x += 2 * delta;
};

app.ticker.add(tickerFn);      // Start
app.ticker.remove(tickerFn);   // Stop (must keep reference)

Always keep a reference to your callback. If you pass an anonymous function to add(), you can’t remove() it later. This is a common source of memory leaks.

Controlling the Ticker

app.ticker.stop();    // Pause all updates
app.ticker.start();   // Resume
app.ticker.destroy(); // Clean up and remove all listeners

Making Objects Interactive

In PixiJS 7+, you make an object interactive by setting eventMode:

const sprite = new PIXI.Sprite(texture);
sprite.eventMode = 'static';  // Enable click/touch events
sprite.cursor = 'pointer';    // Show hand cursor on hover

sprite.on('pointerdown', () => {
  console.log('Clicked!');
});

Why eventMode instead of interactive = true? The old interactive property was deprecated in PixiJS 7. eventMode is more explicit about how events are processed. 'static' means the object doesn’t move in ways that affect hit testing — the most common and fastest mode.

Pointer Events Reference

EventWhen it fires
pointerdownFinger/mouse is pressed
pointerupFinger/mouse is released
pointermovePointer moves over the object
pointeroverPointer enters the object’s hit area
pointeroutPointer leaves the object’s hit area
clickComplete click (down + up within same target)
rightclickRight mouse button click
pointertapTap (touch equivalent of click)
sprite.on('pointerover', () => { sprite.tint = 0x999999; });
sprite.on('pointerout', () => { sprite.tint = 0xffffff; });

Drag-and-Drop Pattern

Drag-and-drop is one of the most common interactive patterns. Here’s the key insight: track drag on the stage, not on the dragged object. If you track on the object, you lose events when the pointer moves faster than the object.

let dragTarget = null;
let dragOffset = { x: 0, y: 0 };

function makeDraggable(obj) {
  obj.eventMode = 'static';
  obj.cursor = 'grab';
  obj.on('pointerdown', (e) => startDrag(e, obj));
}

function startDrag(e, obj) {
  dragTarget = obj;
  // Store the offset between pointer and object position
  dragOffset = {
    x: e.data.global.x - obj.x,
    y: e.data.global.y - obj.y,
  };
  dragTarget.alpha = 0.6;
  dragTarget.cursor = 'grabbing';
}

// Stage-level tracking — always works
app.stage.on('pointermove', (e) => {
  if (dragTarget) {
    dragTarget.x = e.data.global.x - dragOffset.x;
    dragTarget.y = e.data.global.y - dragOffset.y;
  }
});

app.stage.on('pointerup', () => endDrag());
app.stage.on('pointerupoutside', () => endDrag());

function endDrag() {
  if (dragTarget) {
    dragTarget.alpha = 1;
    dragTarget.cursor = 'grab';
    dragTarget = null;
  }
}

Why store an offset? Without the offset, the object jumps so its top-left corner is at the pointer position. Storing pointer position - object position on pointerdown lets you maintain the same relative position while dragging.

Hit Areas

By default, the clickable region of a sprite is its rectangular bounding box. You can override this with hitArea:

// Rectangle hit area
sprite.hitArea = new PIXI.Rectangle(0, 0, 64, 64);

// Circle hit area (great for round objects)
sprite.hitArea = new PIXI.Circle(32, 32, 32);

// Polygon hit area (triangular, hexagonal, etc.)
sprite.hitArea = new PIXI.Polygon([
  32, 0,
  64, 64,
  0, 64,
]);

When to use custom hit areas: For non-rectangular objects (circles, stars), the default rectangular hit area captures clicks outside the visible shape. A matching hit area improves user experience.

Reset to default: sprite.hitArea = null.

Filters — Visual Effects Made Easy

Filters apply GPU-powered visual effects to display objects. Think of them like Instagram filters for your sprites.

// Blur filter
const blurFilter = new PIXI.BlurFilter();
blurFilter.blur = 4;

// Color matrix filter (hue rotation)
const colorFilter = new PIXI.ColorMatrixFilter();
colorFilter.hue(30, true);

// Apply multiple filters
sprite.filters = [blurFilter, colorFilter];

Built-in Filter Gallery

FilterEffectUse Case
BlurFilterGaussian blurMotion blur, focus effects
ColorMatrixFilterColor transformationSepia, hue rotation, desaturate
DisplacementFilterImage distortionRipple, heat haze effects
AlphaFilterPer-object alphaFade effects

Filter Performance

Filters require rendering to an offscreen texture, which costs GPU time. To limit the affected area:

sprite.filterArea = new PIXI.Rectangle(0, 0, 200, 200);

Best practice: Apply filters to a parent Container rather than individual children. This reduces the number of offscreen render passes.

Tweening with the Ticker

A tween (in-between animation) smoothly transitions a property from one value to another over time. PixiJS doesn’t include a built-in tween library, but you can build one with the ticker:

function tweenTo(obj, targetProps, duration = 60) {
  const start = {
    x: obj.x, y: obj.y,
    alpha: obj.alpha,
    rotation: obj.rotation,
  };
  let elapsed = 0;

  const update = (delta) => {
    elapsed += delta;
    const t = Math.min(elapsed / duration, 1);
    // easeInOutCubic — smooth acceleration and deceleration
    const ease = t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;

    if (targetProps.x !== undefined) obj.x = start.x + (targetProps.x - start.x) * ease;
    if (targetProps.y !== undefined) obj.y = start.y + (targetProps.y - start.y) * ease;
    if (targetProps.alpha !== undefined) obj.alpha = start.alpha + (targetProps.alpha - start.alpha) * ease;
    if (targetProps.rotation !== undefined) obj.rotation = start.rotation + (targetProps.rotation - start.rotation) * ease;

    if (t >= 1) app.ticker.remove(update); // Auto-cleanup
  };

  app.ticker.add(update);
}

// Usage: tween a sprite to x:400 over 90 frames
tweenTo(mySprite, { x: 400 }, 90);

Why ease functions? Linear animation looks robotic. Easing functions simulate natural motion — objects accelerate when starting and decelerate when stopping, like real physics.

For complex animations, integrate GSAP — it provides professional-grade tweening, timelines, and easing.

Common Mistakes

MistakeWhy it happensHow to fix it
Not using eventModeIn PixiJS 7+, interactive = true is deprecatedSet eventMode = 'static' on interactive objects
Ticker callback memory leakAdded callback without keeping a referenceStore the function in a variable and call remove()
Drag events lost on fast movementEvents attached to dragged object, not stageTrack pointermove and pointerup on the stage
Applying filters to every childEach filter render pass costs GPU timeApply filters to parent Container instead
Filter area too smallEffect gets clipped visiblyExpand filterArea to cover the effect’s bounds
Animation speed varies with FPSNo delta multiplication in tickerAlways multiply movement by delta

Practice Questions

  1. What does the delta parameter in the ticker callback represent?

    • It’s a time multiplier where 1.0 = 60fps. At 30fps, delta is ~2.0, so movements scale up to keep speed consistent.
  2. Why should pointermove be handled on the stage for drag-and-drop?

    • If attached to the dragged object, events are lost when the pointer moves faster than the object. The stage always receives events.
  3. What is the difference between pointerdown and click?

    • pointerdown fires immediately on press. click fires only after down + up on the same target — useful for buttons.
  4. How do you remove a ticker callback?

    • Keep a reference to the function and call app.ticker.remove(myFn). Anonymous functions cannot be removed.
  5. What happens if you apply too many filters to many objects?

    • Each filter requires an offscreen render pass. Performance degrades quickly. Use parent containers and limit filterArea.

Challenge: Build a particle system where 100 small circles spawn from a center point, fade out while expanding, and are recycled. Use the ticker for position/alpha updates and a Pool for object reuse.

FAQ

What is the difference between pointerdown and click?
: pointerdown fires on press. click fires after down + up on the same target — useful for buttons to avoid triggering on drag.
How do I improve filter performance?
: Reduce filterArea, apply filters to parent containers, and avoid full-screen blur on large canvases.
Can I detect when an animation loop completes?
: Yes. In your ticker callback, check an elapsed time or position threshold, then call app.ticker.remove(callback).
Why is my drag offset wrong?
: You must subtract the pointer’s global position from the object’s position on pointerdown. Store this offset and apply it during pointermove.
Is there a built-in tween library in PixiJS?
: No. Use the ticker-based tween above or integrate GSAP for professional animation.

Try It Yourself

Open this interactive toy in your browser — drag shapes, toggle blur/color filters, enable auto-rotation, and watch shapes bounce on click.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>PixiJS Interactive Toy</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { background: #1a1a2e; font-family: system-ui, sans-serif; display: flex; justify-content: center; padding: 20px; }
    .app { display: flex; gap: 20px; flex-wrap: wrap; }
    canvas { border-radius: 12px; box-shadow: 0 8px 32px rgba(0,0,0,0.4); }
    .panel { background: #16213e; border-radius: 12px; padding: 20px; color: #fff; width: 220px; display: flex; flex-direction: column; gap: 12px; }
    .panel h2 { font-size: 18px; }
    .toggle { display: flex; align-items: center; gap: 10px; }
    .toggle input[type="checkbox"] { width: 20px; height: 20px; accent-color: #4a90d9; cursor: pointer; }
    .btn { padding: 10px; border: none; border-radius: 8px; background: #4a90d9; color: #fff; font-weight: 600; cursor: pointer; }
    .btn:hover { background: #357abd; }
    .btn.success { background: #2d6a4f; }
    .stats { font-size: 12px; color: #8892b0; }
  </style>
</head>
<body>
<div class="app">
  <div id="canvas-container"></div>
  <div class="panel">
    <h2>Interactive Toy</h2>
    <div class="toggle">
      <input type="checkbox" id="chkBlur" />
      <label for="chkBlur">Blur Filter</label>
    </div>
    <div class="toggle">
      <input type="checkbox" id="chkColor" />
      <label for="chkColor">Color Shift</label>
    </div>
    <div class="toggle">
      <input type="checkbox" id="chkRotate" checked />
      <label for="chkRotate">Auto Rotate</label>
    </div>
    <button class="btn" id="addBtn">+ Add Shape</button>
    <button class="btn success" id="animateBtn">Bounce All</button>
    <div class="stats" id="stats">Shapes: 0</div>
  </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/pixi.js@7.x/dist/pixi.min.js"></script>
<script>
const COLORS = [0xff6600, 0x9933ff, 0x00cc99, 0xff3366, 0x3399ff];
const SHAPES = ['rect', 'circle', 'star', 'polygon'];
const app = new PIXI.Application({ width: 700, height: 600, backgroundColor: 0x1a1a2e, antialias: true });
document.getElementById('canvas-container').appendChild(app.view);

const shapes = [];
let counter = 0;
const blurFilter = new PIXI.BlurFilter(); blurFilter.blur = 0;
const colorFilter = new PIXI.ColorMatrixFilter(); colorFilter.hue(0, true);

function createShape(x, y) {
  const g = new PIXI.Graphics();
  g.beginFill(COLORS[counter % COLORS.length], 0.85);
  g.lineStyle(2, 0xffffff, 0.5);
  const s = 30 + Math.random() * 40;
  const t = SHAPES[counter % SHAPES.length]; counter++;
  switch (t) {
    case 'rect': g.drawRoundedRect(-s/2, -s/2, s, s, 6); break;
    case 'circle': g.drawCircle(0, 0, s/2); break;
    case 'star': g.drawStar(0, 0, 5, s/2, s/5); break;
    case 'polygon': g.drawPolygon([0,-s/2, s/2,s/3, -s/2,s/3]); break;
  }
  g.endFill();
  g.x = x ?? 100 + Math.random() * 500;
  g.y = y ?? 100 + Math.random() * 400;
  g.eventMode = 'static'; g.cursor = 'grab';

  g.on('pointerdown', (e) => {
    g.alpha = 0.6; g.cursor = 'grabbing';
    g.parent.setChildIndex(g, g.parent.children.length - 1);
    g.dragData = { x: e.data.global.x - g.x, y: e.data.global.y - g.y };
  });
  g.on('pointertap', () => { g.scale.set(1.3); setTimeout(() => g.scale.set(1), 200); });

  app.stage.addChild(g); shapes.push(g); document.getElementById('stats').textContent = `Shapes: ${shapes.length}`;
}

app.stage.on('pointermove', (e) => {
  shapes.forEach(g => { if (g.dragData) { g.x = e.data.global.x - g.dragData.x; g.y = e.data.global.y - g.dragData.y; } });
});
app.stage.on('pointerup', () => shapes.forEach(g => { if (g.dragData) { g.dragData = null; g.alpha = 1; g.cursor = 'grab'; } }));
app.stage.on('pointerupoutside', () => shapes.forEach(g => { if (g.dragData) { g.dragData = null; g.alpha = 1; g.cursor = 'grab'; } }));

let autoRotate = true;
app.ticker.add((delta) => { if (autoRotate) shapes.forEach(g => g.rotation += 0.02 * delta); });

document.getElementById('chkBlur').addEventListener('change', e => {
  blurFilter.blur = e.target.checked ? 6 : 0;
  shapes.forEach(g => { g.filters = e.target.checked ? [blurFilter] : []; });
});
document.getElementById('chkColor').addEventListener('change', e => {
  colorFilter.hue(e.target.checked ? 60 : 0, true);
  shapes.forEach(g => { g.filters = e.target.checked ? [colorFilter] : []; });
});
document.getElementById('chkRotate').addEventListener('change', e => { autoRotate = e.target.checked; });
document.getElementById('addBtn').addEventListener('click', () => createShape());
document.getElementById('animateBtn').addEventListener('click', () => {
  shapes.forEach((g, i) => {
    setTimeout(() => {
      const targetY = g.y < 300 ? 500 : 100;
      const fn = (delta) => { g.y += (targetY - g.y) * 0.08 * delta; if (Math.abs(g.y - targetY) < 1) app.ticker.remove(fn); };
      app.ticker.add(fn);
    }, i * 50);
  });
});

createShape(200, 200); createShape(400, 300); createShape(550, 200); createShape(150, 400);
window.addEventListener('resize', () => {
  const w = Math.min(window.innerWidth - 280, 700); const h = Math.min(window.innerHeight - 40, 600);
  app.renderer.resize(Math.max(w, 200), Math.max(h, 200));
});
</script>
</body>
</html>

What’s Next

TopicLink
Text and bitmap fontshttps://tutorials.dodatech.com/frontend/libraries/pixijs/pixijs-text-fonts/
Performance and best practiceshttps://tutorials.dodatech.com/frontend/libraries/pixijs/pixijs-performance/
Professional animations with GSAPGSAP Animation Guide
JavaScript event handlingJavaScript Events

Related Tutorials

  • https://tutorials.dodatech.com/frontend/libraries/pixijs/pixijs-getting-started/ — review sprites and stage
  • https://tutorials.dodatech.com/frontend/libraries/pixijs/pixijs-graphics-textures/ — drawing shapes to animate
  • WebGL for 2D — what’s happening under the hood

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Durga Antivirus Pro uses animated, interactive threat nodes so analysts can drag, inspect, and filter security alerts in real time.

What’s Next

Congratulations on completing this Pixijs Animation Interactivity tutorial! Here’s where to go from here:

  • Practice daily — Consistency is more important than long study sessions
  • Build a project — Apply what you learned by building something real
  • Explore related topics — Check out other tutorials in the same category
  • Join the community — Discuss with other learners and share your progress

Remember: every expert was once a beginner. Keep coding!

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro