Unlock Stunning Canvas Animations with SpriteJS – A Complete Beginner’s Guide
This article introduces SpriteJS, a cross‑platform canvas library from 360 Qiwang Team, showing how to create structured UI, draw shapes, apply transitions, use the Web Animation API, work with textures, paths, groups, events, D3 integration, physics engines and particle systems, all with practical JavaScript code examples.
What is SpriteJS?
SpriteJS is an open‑source, cross‑terminal canvas drawing library created by the 360 Qiwang Team. It provides a DOM‑like object model for canvas, allowing developers to create sprites, paths, groups and apply animations in browsers, mini‑programs and Node.js environments.
Core Features
Canvas‑based document object model
Four basic sprite types: Sprite, Path, Label, Group
CSS‑compatible properties (box model, transform, zIndex, etc.)
Powerful Transition and Animation APIs
Support for sprite sheets, resource pre‑loading and texture atlases
Event handling similar to DOM (mouse, touch, keyboard)
Compatibility with D3, Matter‑js, Proton and other third‑party libraries
Creating a Simple Sprite
Below is the basic code to create a red rounded rectangle using the native Canvas API and the equivalent SpriteJS code.
const canvas = document.getElementById('paper');
const context = canvas.getContext('2d');
const [x, y, w, h, r] = [200, 200, 200, 200, 50];
context.fillStyle = 'red';
context.beginPath();
context.moveTo(x + r, y);
context.arcTo(x + w, y, x + w, y + h, r);
context.arcTo(x + w, y + h, x, y + h, r);
context.arcTo(x, y + h, x, y, r);
context.arcTo(x, y, x + w, y, r);
context.closePath();
context.fill(); const scene = new spritejs.Scene('#container');
const layer = scene.layer();
const s = new spritejs.Sprite({
anchor: 0.5,
bgcolor: 'red',
pos: [300, 300],
size: [200, 200],
borderRadius: 50
});
layer.append(s);Transition API
SpriteJS makes animation as easy as chaining transition calls that return promises.
const scene = new spritejs.Scene('#container');
const layer = scene.layer();
const s = new spritejs.Sprite({
anchor: 0.5,
bgcolor: 'red',
pos: [300, 300],
size: [200, 200],
borderRadius: 50
});
layer.append(s);
s.transition(2.0).attr({bgcolor: 'green'});Multiple properties can be animated together, and promises enable sequential animations.
s.transition(2.0).attr({bgcolor: 'green', width: w => w + 100});
await s.transition(2.0).attr({bgcolor: 'yellow', height: h => h + 100});Web Animation API
SpriteJS also supports the standard Web Animation API for more complex keyframe animations.
const scene = new spritejs.Scene('#container');
const layer = scene.layer();
const s = new spritejs.Sprite({
anchor: 0.5,
bgcolor: 'red',
pos: [300, 300],
size: [200, 200],
borderRadius: 50
});
layer.append(s);
s.animate([
{rotate: 0, borderRadius: 50, bgcolor: 'red'},
{rotate: 360, borderRadius: 0, bgcolor: 'green', offset: 0.7},
{rotate: 720, borderRadius: 50, bgcolor: 'blue'}
], {
duration: 3000,
iterations: Infinity,
direction: 'alternate',
easing: 'ease-in-out'
});Using Textures
Sprites can display images by setting the textures attribute.
const scene = new spritejs.Scene('#container');
const layer = scene.layer();
const s = new spritejs.Sprite({
textures: 'https://p0.ssl.qhimg.com/t01a72262146b87165f.png',
anchor: 0.5,
pos: [300, 300],
size: [200, 200],
scale: 0.5
});
layer.append(s);Multiple textures can be combined with rect and srcRect for sprite sheets.
const texture = 'https://p5.ssl.qhimg.com/t01a2bd87890397464a.png';
const s = new spritejs.Sprite();
s.attr({
anchor: 0.5,
textures: [{src: texture, rect: [0,0,190,268], srcRect: [0,0,190,268]},
{src: texture, rect: [200,278,190,268], srcRect: [191,269,190,268]},
{src: texture, rect: [0,278,190,268], srcRect: [0,269,190,268]},
{src: texture, rect: [200,0,190,268], srcRect: [191,0,190,268]}],
border: [2, 'grey'],
pos: [300, 300]
});
layer.append(s);Path – Vector Shapes
SpriteJS provides a Path type that can render SVG path data.
const scene = new spritejs.Scene('#container');
const layer = scene.layer();
const p = new spritejs.Path({
path: {d: 'M280,250A200,200,0,1,1,680,250A200,200,0,1,1,280,250Z', transform: {scale: 0.5}, trim: true},
strokeColor: '#033',
fillColor: '#839',
lineWidth: 12,
pos: [100, 50]
});
layer.appendChild(p);Paths can also be animated with transition or animate .
p.transition(2.0).attr({d: paths[++i % paths.length]});Group – Nested Elements
Groups allow multiple sprites to be transformed together.
const {Scene, Group, Sprite} = spritejs;
const scene = new Scene('#paper', {viewport: ['auto','auto'], resolution: [1200,1200]});
const layer = scene.layer('fglayer');
const lemon = new Sprite();
lemon.attr({bgcolor: '#fed330', size: [180,180], border: [6,'#fdbd2c'], borderRadius: 90});
const lemonGroup = new Group();
lemonGroup.attr({anchor: 0.5, pos: [610,600], size: [180,180], bgcolor: '#faee35', border: [6,'#fdbd2c'], borderRadius: 90});
lemonGroup.append(lemon);
layer.append(lemonGroup);
lemonGroup.animate([{rotate: 360}], {duration: 10000, iterations: Infinity});Event Handling
Sprites can listen to mouse, touch and custom events just like DOM elements.
const s1 = new spritejs.Sprite();
s1.attr({anchor: [0.5,0.5], pos: [770,300], size: [300,300], rotate: 45, bgcolor: '#3c7'});
layer.append(s1);
s1.on('mouseenter', () => s1.attr('border', [4, 'blue']));
s1.on('mouseleave', () => s1.attr('border', [0, '']));Keyboard events can be delegated by overriding pointCollision in a custom class.
class KeyButton extends spritejs.Label {
pointCollision(evt) { return evt.originalEvent.key === this.text; }
}
KeyButton.defineAttributes({init(attr){ attr.setDefault({font: '42px Arial', border: {width:4,color:'black',style:'solid'}, width:50, height:50, anchor:[0.5,0.5], textAlign:'center', lineHeight:50}); }});Integration with D3
Because SpriteJS mimics DOM APIs, it works smoothly with D3 for data‑driven visualizations.
const paper = new spritejs.Scene('#paper', {viewport:['auto','auto'], resolution:[1600,1200], stickMode:'width'});
const dataset = [125,121,127,193,309];
const linear = d3.scaleLinear().domain([100, d3.max(dataset)]).range([0,500]);
const colors = ['#fe645b','#feb050','#c2af87','#81b848','#55abf8'];
const s = d3.select(paper).append('fglayer');
const chart = s.selectAll('sprite').data(dataset).enter().append('sprite')
.attr('x', 450)
.attr('y', (d,i)=>200+i*95)
.attr('width',0)
.attr('height',80)
.attr('bgcolor','#ccc');
chart.transition().duration(2000).attr('width', d=>linear(d)).attr('bgcolor',(d,i)=>colors[i]);Physics Engine (Matter‑js)
SpriteJS can render bodies from Matter‑js, updating their position and rotation each frame.
const {Engine, World, Bodies, Composite} = Matter;
const engine = Engine.create();
const stack = Composites.stack(100,100,6,6,0,0,(x,y)=>Bodies.rectangle(x,y,15,15));
World.add(engine.world, [stack]);
function render(){
Engine.update(engine,16);
const bodies = Composite.allBodies(engine.world);
bodies.forEach((body,i)=>{
const {position, angle} = body;
const pos = [Math.round(position.x*10)/10, Math.round(position.y*10)/10];
const rotate = Math.round(180*angle*10/Math.PI)/10;
let block = blocks[i];
if(!block){
const {min, max} = body.bounds;
block = new spritejs.Sprite();
block.attr({anchor:0.5, size:[max.x-min.x, max.y-min.y], pos, rotate, bgcolor: body.render.fillStyle});
blocks[i]=block; layer.append(block);
} else {
block.attr({pos, rotate});
}
});
requestAnimationFrame(render);
}
render();Particle System (Proton)
SpriteJS can host Proton particle emitters.
const {Scene, ProtonRenderer} = spritejs;
const scene = new Scene('#container', {viewport:[600,600], resolution:[600,600]});
const layer = scene.layer('fglayer');
const proton = new Proton();
const emitter = new Proton.Emitter();
emitter.rate = new Proton.Rate(Proton.getSpan(10,20), 0.1);
emitter.addInitialize(new Proton.Radius(1,12));
emitter.addInitialize(new Proton.Life(2,4));
emitter.addInitialize(new Proton.Velocity(3, Proton.getSpan(0,360), 'polar'));
emitter.addBehaviour(new Proton.Color('#ff0000','random'));
emitter.addBehaviour(new Proton.Alpha(1,0));
emitter.p.x = layer.canvas.width/2;
emitter.p.y = layer.canvas.height/2;
emitter.emit(5);
proton.addEmitter(emitter);
const renderer = new ProtonRenderer(layer);
proton.addRenderer(renderer);
function tick(){ requestAnimationFrame(tick); proton.update(); }
tick();External Clock Example (Curvejs)
SpriteJS can be driven by an external animation clock, as demonstrated with Curvejs.
(async function(){
const scene = new spritejs.Scene('#curvejs', {resolution:[1540,600], viewport:'auto'});
const layer = scene.layer('fglayer', {autoRender:false});
await scene.preload(['https://p0.ssl.qhimg.com/t01a72262146b87165f.png','https://s5.ssl.qhres.com/static/5f6911b7b91c88da.json']);
const s = new spritejs.Sprite('bird1.png');
s.attr({anchor:[0.5,0.5], pos:[300,100], transform:{scale:[0.5,0.5]}, offsetPath:'M10,80 q100,120 120,20 q140,-50 160,0', zIndex:200});
s.animate([{offsetDistance:0},{offsetDistance:1}],{duration:3000,direction:'alternate',iterations:Infinity});
s.animate([{scale:[0.5,0.5],offsetRotate:'auto'},{scale:[0.5,-0.5],offsetRotate:'reverse'},{scale:[0.5,0.5],offsetRotate:'auto'}],{duration:6000,iterations:Infinity,easing:'step-end'});
s.animate([{textures:'bird1.png'},{textures:'bird2.png'},{textures:'bird3.png'}],{duration:300,direction:'alternate',iterations:Infinity});
layer.appendChild(s);
const {Stage, Curve, motion} = curvejs;
const stage = new Stage(layer.canvas);
const randomColor = () => ['#22CAB3','#90CABE','#A6EFE8','#C0E9ED','#DBD4B7','#D4B879','#ECCEB2','#F2ADA6','#FF7784'][Math.floor(Math.random()*9)];
stage.add(new Curve({points:[378,123,297,97,209,174,217,258], color:randomColor(), motion:motion.rotate, data:Math.PI/20}));
stage.add(new Curve({points:[378,123,385,195,293,279,217,258], color:randomColor(), motion:motion.rotate, data:Math.PI/20}));
function tick(){ stage.update(); layer.draw(false); requestAnimationFrame(tick); }
tick();
})();This guide demonstrates how SpriteJS simplifies canvas programming, offering a high‑level API for drawing, animating, handling events, and integrating with other libraries, making it a powerful tool for modern front‑end developers.
360 Zhihui Cloud Developer
360 Zhihui Cloud is an enterprise open service platform that aims to "aggregate data value and empower an intelligent future," leveraging 360's extensive product and technology resources to deliver platform services to customers.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.