⚡ Powerful, high-performance animations for PixiJS
🔮 Simple, declarative API | 🎬 Based on Cocos2d/SKActions |
🚀 35+ built-in actions, 30+ timing modes | 🔀 Reuseable, chainable & reversible |
🍃 No dependencies & tree-shakeable | ⌚ Full speed/pausing control |
🤏 ~4.3kb minzipped |
✨ Supports PixiJS 8+, 7+, 6.3+ |
Create, configure, and run animations & actions.
// Define an action
const spinAndRemove = Action.sequence([
Action.rotateByDegrees(360, 0.2).easeInOut(),
Action.fadeOut(0.2).easeIn(),
Action.removeFromParent(),
Action.run(() => console.info('✨ done!'))
]);
// Run an action
mySprite.run(spinAndRemove);
Everything you need to quickly build beautiful animations.
PixiJS Actions is based off the idiomatic and expressive SKActions API, extending Container
to add first-class support for running and managing actions.
The core concepts are:
- Nodes: Any container (e.g.
Container
,Sprite
,Graphics
) - Actions: Stateless, reusable recipes (e.g. animations, triggers, and more)
- TimingMode & speed: Controls for the speed & smoothness of actions and animations
Note
See Timing Modes and Manipulating Action Speed for more information.
Quick start guide.
1. Install the latest pixijs-actions
package:
# npm
npm install pixijs-actions -D
# yarn
yarn add pixijs-actions --dev
2. Register the mixin & ticker:
import * as PIXI from 'pixi.js';
import { Action, registerPixiJSActionsMixin } from 'pixijs-actions';
// register container mixin
registerPixiJSActionsMixin(PIXI.Container);
// register `Action.tick(...)` with shared ticker
Ticker.shared.add(ticker => Action.tick(ticker.elapsedMS));
Tip
PixiJS 7 / 6.3+:
Ticker.shared.add(() => Action.tick(Ticker.shared.elapsedMS));
// or
Ticker.shared.add((dt) => Action.tick(dt));
Note
If not using a PixiJS ticker, then just put Action.tick(elapsedMs)
in the appropriate equivalent place (i.e. your requestAnimationFrame()
render loop).
3. Done!
✨ You are now ready to run your first action!
Running actions in your canvas.
// Hide me instantly!
mySprite.run(Action.hide(), () => {
console.log('where did I go?');
});
Nodes are extended with a few new methods and properties to make it easy to interact with actions.
Property | Description |
---|---|
speed |
A speed modifier applied to all actions executed by the node and its descendants. Defaults to 1.0 . |
isPaused |
A boolean value that determines whether actions on the node and its descendants are processed. Defaults to false . |
Method | Description |
---|---|
run(action) |
Run an action. |
run(action, completion) |
Run an action with a completion handler. |
runWithKey(action, withKey) |
Run an action, and store it so it can be retrieved later. |
runAsPromise(action): Promise<void> |
Run an action as a promise. |
action(forKey): Action | undefined |
Return an action associated with a specified key. |
hasActions(): boolean |
Return a boolean indicating whether the node is executing actions. |
removeAllActions(): void |
End and removes all actions from the node. |
removeAction(forKey): void |
Remove an action associated with a specified key. |
// Repeat an action forever!
const spin = Action.repeatForever(Action.rotateBy(5, 1.0));
mySprite.runWithKey(spin, 'spinForever');
// Or remove it later.
mySprite.removeAction('spinForever');
mySprite.isPaused = true;
// All actions will stop running.
Speed can be manipulated on both actions and nodes.
const moveAction = Action.moveByX(10, 4.0);
moveAction.speed = 2.0;
// moveAction will now take only 2 seconds instead of 4.
const repeatAction = Action.repeat(moveAction, 5);
repeatAction.speed = 2.0;
// Each moveAction will only take 1 second, for a total of 5 seconds.
mySprite.run(moveAction);
mySprite.speed = 2.0;
// mySprite is running at 2x speed!
// The entire action should now take ~2.5 seconds.
mySprite.parent!.speed = 1 / 4;
// Now we've slowed down mySprite's parent.
// The entire action will now take ~10 seconds.
Note
Changes to nodes' speed
will take effect immediately, however changes to an Action
initializer's speed
or timingMode
will not affect any actions that have already begun running.
Combine these initializers to create expressive animations and behaviors.
Most actions implement specific predefined animations that are ready to use. If your animation needs fall outside of the suite provided here, then you should implement a custom action (see Creating Custom Actions).
Action | Description | Reversible? |
---|---|---|
Chaining Actions | ||
Action.group(actions) |
Run multiple actions in parallel. | Yes |
Action.sequence(actions) |
Run multiple actions sequentially. | Yes |
Action.repeat(action, count) |
Repeat an action a specified number of times. | Yes |
Action.repeatForever(action) |
Repeat an action indefinitely. | Yes |
Animating a Node's Position in a Linear Path | ||
Action.moveBy(vector, duration) |
Move a node by a relative vector { x, y } (e.g. PIXI.Point ). |
Yes |
Action.moveBy(dx, dy, duration) |
Move a node by relative values. | Yes |
Action.moveByX(dx, duration) |
Move a node horizontally by a relative value. | Yes |
Action.moveByY(dy, duration) |
Move a node vertically by a relative value. | Yes |
Action.moveTo(position, duration) |
Move a node to a specified position { x, y } (e.g. PIXI.Point , PIXI.Container ). |
*No |
Action.moveTo(x, y, duration) |
Move a node to a specified position. | *No |
Action.moveToX(x, duration) |
Move a node to a specified horizontal position. | *No |
Action.moveToY(y, duration) |
Move a node to a specified vertical position. | *No |
Animating a Node's Position Along a Custom Path | ||
Action.follow(path, duration) |
Move a node along a path, optionally orienting the node to the path. | Yes |
Action.followAtSpeed(path, speed) |
Move a node along a path at a specified speed, optionally orienting the node to the path. | Yes |
Animating the Rotation of a Node | ||
Action.rotateBy(delta, duration) |
Rotate a node by a relative value (in radians). | Yes |
Action.rotateByDegrees(delta, duration) |
Rotate a node by a relative value (in degrees). | Yes |
Action.rotateTo(radians, duration) |
Rotate a node to a specified value (in radians). | *No |
Action.rotateToDegrees(degrees, duration) |
Rotate a node to a specified value (in degrees). | *No |
Animating the Scaling of a Node | ||
Action.scaleBy(vector, duration) |
Scale a node by a relative vector { x, y } (e.g. PIXI.Point ). |
Yes |
Action.scaleBy(scale, duration) |
Scale a node by a relative value. | Yes |
Action.scaleBy(dx, dy, duration) |
Scale a node in each axis by relative values. | Yes |
Action.scaleByX(dx, duration) |
Scale a node horizontally by a relative value. | Yes |
Action.scaleByY(dy, duration) |
Scale a node vertically by a relative value. | Yes |
Action.scaleTo(size, duration) |
Scale a node to achieve a specified size { width, height } (e.g. PIXI.ISize , PIXI.Container ). |
*No |
Action.scaleTo(scale, duration) |
Scale a node to a specified value. | *No |
Action.scaleTo(x, y, duration) |
Scale a node in each axis to specified values. | *No |
Action.scaleToX(x, duration) |
Scale a node horizontally to a specified value. | *No |
Action.scaleToY(y, duration) |
Scale a node vertically to a specified value. | *No |
Animating the Transparency of a Node | ||
Action.fadeIn(duration) |
Fade the alpha to 1.0 . |
Yes |
Action.fadeOut(duration) |
Fade the alpha to 0.0 . |
Yes |
Action.fadeAlphaBy(delta, duration) |
Fade the alpha by a relative value. | Yes |
Action.fadeAlphaTo(alpha, duration) |
Fade the alpha to a specified value. | *No |
Controlling a Node's Visibility | ||
Action.unhide() |
Set a node's visible property to true . |
Yes |
Action.hide() |
Set a node's visible property to false . |
Yes |
Removing a Node from the Canvas | ||
Action.removeFromParent() |
Remove a node from its parent. | †Identical |
Running Actions on Children | ||
Action.runOnChild(nameOrLabel, action) |
Add an action to a child node. | Yes |
Delaying Actions | ||
Action.waitForDuration(duration) |
Idle for a specified period of time. | †Identical |
Action.waitForDurationWithRange(duration, range) |
Idle for a randomized period of time. | †Identical |
Triggers and Custom Actions | ||
Action.run(callback) |
Execute a block (i.e. trigger another action). | †Identical |
Action.customAction(duration, stepHandler) |
Execute a custom stepping function over the action duration. | †Identical |
Manipulating the Action Speed of a Node | ||
Action.speedBy(delta, duration) |
Change how fast a node executes its actions by a relative value. | Yes |
Action.speedTo(speed, duration) |
Set how fast a node executes actions to a specified value. | *No |
Important
Every action initializer has a .reversed()
method which will return a new action. Some actions are not reversible, and these cases are noted in the table above:
- †Identical — The reversed action is identical to the original action.
- *No — The reversed action will idle for the equivalent duration.
Many actions can be joined together using Action.sequence()
, Action.group()
, Action.repeat()
and Action.repeatForever()
to quickly create complex animations:
import { Action } from 'pixijs-actions';
// Expand and contract smoothly over 2 seconds
const pulsate = Action.sequence([
Action.scaleTo(1.5, 1.0).easeOut(),
Action.scaleTo(1, 1.0).easeIn()
]);
// Follow a complex path (e.g. a bezier curve)
const path = [
{ x: 0, y: 0 },
{ x: 100, y: 0 },
{ x: 100, y: 100 },
{ x: 200, y: 200 }
];
const followPath = Action.follow(path, 5.0);
// Create a 10 second loop that goes back and forth
const moveBackAndForthWhilePulsating = Action.group([
Action.repeat(pulsate, 5),
Action.sequence([followPath, followPath.reversed()]),
]);
// ✨ Animate continuously
mySprite.run(Action.repeatForever(moveBackAndForthWhilePulsating));
Every action has a timingMode
which controls the timing curve of its execution.
The default timingMode for all actions is TimingMode.linear
, which causes an animation to occur evenly over its duration.
You can customize the speed curve of actions in many ways:
// Default easings:
Action.fadeIn(0.3).easeIn();
Action.fadeIn(0.3).easeOut();
Action.fadeIn(0.3).easeInOut();
// Set a specific TimingMode:
Action.fadeIn(0.3).setTimingMode(TimingMode.easeInOutCubic);
// Set a custom timing function:
Action.fadeIn(0.3).setTimingMode(x => x * x);
Important
Timing Mutators: The .easeIn()
, .easeOut()
, .easeInOut()
, setTimingMode(…)
, setSpeed(…)
methods mutate the underlying action.
See the following table for default TimingMode
options.
Pattern | Ease In, Ease Out | Ease In | Ease Out | Description |
---|---|---|---|---|
Linear | linear |
- | - | Constant motion with no acceleration or deceleration. |
Sine | easeInOutSine |
easeInSine |
easeOutSine |
Gentle start and end, with accelerated motion in the middle. |
Circular | easeInOutCirc |
easeInCirc |
easeOutCirc |
Smooth start and end, faster acceleration in the middle, circular motion. |
Cubic | easeInOutCubic |
easeInCubic |
easeOutCubic |
Gradual acceleration and deceleration, smooth motion throughout. |
Quadratic | easeInOutQuad |
easeInQuad |
easeOutQuad |
Smooth acceleration and deceleration, starts and ends slowly, faster in the middle. |
Quartic | easeInOutQuart |
easeInQuart |
easeOutQuart |
Slower start and end, increased acceleration in the middle. |
Quintic | easeInOutQuint |
easeInQuint |
easeOutQuint |
Very gradual start and end, smoother acceleration in the middle. |
Exponential | easeInOutExpo |
easeInExpo |
easeOutExpo |
Very slow start, exponential acceleration, slow end. |
Back | easeInOutBack |
easeInBack |
easeOutBack |
Starts slowly, overshoots slightly, settles into final position. |
Bounce | easeInOutBounce |
easeInBounce |
easeOutBounce |
Bouncy effect at the start or end, with multiple rebounds. |
Elastic | easeInOutElastic |
easeInElastic |
easeOutElastic |
Stretchy motion with overshoot and multiple oscillations. |
The .easeIn()
, .easeOut()
, .easeInOut()
, and .linear()
mutator methods on Action
instances will set the timing mode of that action to the global default timing mode for that curve type.
TimingMode mutator | Global setting | Default value |
---|---|---|
action.easeIn() |
Action.DefaultTimingModeEaseIn |
TimingMode.easeInSine |
action.easeOut() |
Action.DefaultTimingModeEaseOut |
TimingMode.easeOutSine |
action.easeInOut() |
Action.DefaultTimingModeEaseInOut |
TimingMode.easeInOutSine |
action.linear() |
(n/a) | TimingMode.linear |
Global default timing modes can be set like so:
// set default
Action.DefaultTimingModeEaseIn = TimingMode.easeInQuad;
// apply
myNode.run(myAction.easeIn());
myAction.timingMode
// TimingMode.easeInQuad
Beyond combining chaining actions like sequence()
, group()
, repeat()
and repeatForever()
, you can provide code that implements your own action.
Actions are stateless and reusable, so you can create complex animations once, and then run them on many nodes.
/** A nice gentle rock back and forth. */
const rockBackAndForth = Action.repeatForever(
Action.group([
Action.sequence([
Action.moveByX(5, 0.33).easeOut(),
Action.moveByX(-10, 0.34).easeInOut(),
Action.moveByX(5, 0.33).easeIn(),
]),
Action.sequence([
Action.rotateByDegrees(-2, 0.33).easeOut(),
Action.rotateByDegrees(4, 0.34).easeInOut(),
Action.rotateByDegrees(-2, 0.33).easeIn(),
]),
])
);
// Run it over here
someSprite.run(rockBackAndForth);
// Run it somewhere else
someOtherContainer.run(rockBackAndForth);
You can combine these with dynamic actions for more variety:
const MyActions = {
squash: (amount: number, duration: number = 0.3) => Action.sequence([
Action.scaleTo(amount, 1 / amount, duration / 2).easeOut(),
Action.scaleTo(1, duration / 2).easeIn()
]),
stretch: (amount: number, duration: number = 0.3) => Action.sequence([
Action.scaleTo(1 / amount, amount, duration / 2).easeOut(),
Action.scaleTo(1, duration / 2).easeIn()
]),
squashAndStretch: (amount: number, duration: number = 0.3) => Action.sequence([
MyActions.squash(amount, duration / 2),
MyActions.stretch(amount, duration / 2),
]),
};
// Small squish!
mySprite.run(MyActions.squashAndStretch(1.25));
// Big squish!
mySprite.run(MyActions.squashAndStretch(2.0));
You can use the built-in Action.customAction(duration, stepHandler)
to provide custom actions:
const rainbowColors = Action.customAction(5.0, (target, t, dt) => {
// Calculate color based on time "t".
const colorR = Math.sin(0.3 * t + 0) * 127 + 128;
const colorG = Math.sin(0.3 * t + 2) * 127 + 128;
const colorB = Math.sin(0.3 * t + 4) * 127 + 128;
// Apply random color with time-based variation.
target.tint = (colorR << 16) + (colorG << 8) + colorB;
});
// Start rainbow effect
mySprite.runWithKey(Action.repeatForever(rainbowColors), 'rainbow');
// Stop rainbow effect
mySprite.removeAction('rainbow');
Step functions:
target
= The node the aciton is runnning against.t
= Progress of time from 0 to 1, which has been passed through thetimingMode
function.dt
= delta/change int
since last step. Use for relative actions.Note:
t
can be outside of 0 and 1 in timing mode functions which overshoot, such asTimingMode.easeInOutBack
.
This function will be called as many times as the renderer asks over the course of its duration.
Here is a practical example:
// Create a custom action that relies on
// state (radius, inital target position).
const makeOrbitAction = (
radius: number,
duration: number
): Action => {
let startPos: PIXI.IPointData;
return Action.customAction(duration, (target, t, td) => {
if (!startPos) {
// Capture on first run
startPos = { x: target.x, y: target.y };
}
const angle = Math.PI * 2 * t;
target.position.set(
startPos.x + radius * Math.cos(angle),
startPos.y + radius * Math.sin(angle)
);
});
};
// Run the custom action
mySprite.run(
Action.repeatForever(makeOrbitAction(10, 15.0))
);
PixiJS Actions was originally forked from srpatel's awesome pixi-actions library, but became a hard fork when we changed approach.