Promise‑Based JavaScript Animation Library: Design and Implementation
This article introduces a Promise‑based JavaScript animation library, explains how to create sequential animations using async/await, provides polyfills for requestAnimationFrame and es6‑promise, details the Animator class implementation, and demonstrates usage with code examples and easing extensions.
In a recent class on JavaScript animation, the author discusses simple uniform and accelerated motions as well as more complex composite animations, then proposes a simpler API for modern browsers that enables sequential animation playback.
Promise‑Based Animation Library
Sequencing animations—starting a new animation after the previous one finishes—can be elegantly achieved by exposing the animation interface as a Promise . The following example shows a concise async/await implementation that works in browsers supporting these features.
let animator = new Animator(2000, function(p){
let tx = -100 * Math.sin(2 * Math.PI * p),
ty = -100 * Math.cos(2 * Math.PI * p);
block.style.transform = 'translate(' + tx + 'px,' + ty + 'px)';
});
block.addEventListener('click', async function(evt){
let i = 0;
while(1){
await animator.animate();
block.style.background = ['red','green','blue'][i++%3];
}
});If older browsers need support, a polyfill for es6‑promise or a third‑party library can be added. Another example demonstrates chaining multiple Animator instances:
var a1 = new Animator(1000, function(p){
var tx = 100 * p;
block.style.transform = 'translateX(' + tx + 'px)';
});
var a2 = new Animator(1000, function(p){
var ty = 100 * p;
block.style.transform = 'translate(100px,' + ty + 'px)';
});
var a3 = new Animator(1000, function(p){
var tx = 100 * (1-p);
block.style.transform = 'translate(' + tx + 'px, 100px)';
});
var a4 = new Animator(1000, function(p){
var ty = 100 * (1-p);
block.style.transform = 'translateY(' + ty + 'px)';
});
block.addEventListener('click', async function(){
await a1.animate();
await a2.animate();
await a3.animate();
await a4.animate();
});Implementation details start with a simple time‑retrieval helper that prefers performance.now() and falls back to Date.now() when unavailable.
function nowtime(){
if(typeof performance !== 'undefined' && performance.now){
return performance.now();
}
return Date.now ? Date.now() : (new Date()).getTime();
}A polyfill for requestAnimationFrame is provided for environments where it is undefined, using setTimeout at roughly 60 fps.
if(typeof global.requestAnimationFrame === 'undefined'){
global.requestAnimationFrame = function(callback){
return setTimeout(function(){
callback.call(this, nowtime());
}, 1000/60);
};
global.cancelAnimationFrame = function(qId){
return clearTimeout(qId);
};
}The core Animator constructor accepts duration , an update callback, and an optional easing function. Its animate method returns a Promise that resolves when the animation completes and can be cancelled, rejecting the promise.
function Animator(duration, update, easing){
this.duration = duration;
this.update = update;
this.easing = easing;
}
Animator.prototype = {
animate: function(){
var startTime = 0,
duration = this.duration,
update = this.update,
easing = this.easing,
self = this;
return new Promise(function(resolve, reject){
var qId = 0;
function step(timestamp){
startTime = startTime || timestamp;
var p = Math.min(1.0, (timestamp - startTime) / duration);
update.call(self, easing ? easing(p) : p, p);
if(p < 1.0){
qId = requestAnimationFrame(step);
} else {
resolve(self);
}
}
self.cancel = function(){
cancelAnimationFrame(qId);
update.call(self, 0, 0);
reject('User canceled!');
};
qId = requestAnimationFrame(step);
});
},
ease: function(easing){
return new Animator(this.duration, this.update, easing);
}
};
module.exports = Animator;The Animator can be used with async/await to create smooth sequential animations, and the ease method allows chaining new easing functions or reversing existing animations.
var a1 = new Animator(1414, function(p){
var ty = 200 * p * p;
block.style.transform = 'translateY(' + ty + 'px)';
});
var a2 = new Animator(1414, function(p){
var ty = 200 - 200 * p * (2 - p);
block.style.transform = 'translateY(' + ty + 'px)';
});
block.addEventListener('click', async function(){
while(1){
await a1.animate();
await a2.animate();
}
});An additional example shows how to apply a custom Bezier easing and create a reversed animation:
var easeInOutBack = BezierEasing(0.68, -0.55, 0.265, 1.55);
var a1 = new Animator(2000, function(ep, p){
var x = 200 * ep;
block.style.transform = 'translateX(' + x + 'px)';
}, easeInOutBack);
var a2 = a1.ease(p => easeInOutBack(1 - p)); // reverse a1
block.addEventListener('click', async function(){
await a1.animate();
await a2.animate();
});While many animations can be expressed with CSS3, JavaScript‑based animations provide finer control over time and progress, making them suitable for DOM, Canvas, SVG, media streams, or any asynchronous actions. The presented Promise‑based library offers a low‑level API that is flexible and extensible.
Conclusion
Using Promise to implement a lightweight animation library enables clean sequential animations with async/await, offers strong extensibility, and is poised to become a primary approach for JavaScript animation in modern browsers. The latest code is available in the linked GitHub repository.
References
[1] Previous article: https://www.h5jun.com/post/animations-you-should-know.html
[2] ES6‑Promise polyfill: https://github.com/stefanpenner/es6-promise
[3] Third‑party library: http://bluebirdjs.com/docs/getting-started.html
[4] requestAnimationFrame spec: https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
[5] DOMHighResTimeStamp: https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
[6] Easing functions: http://easings.net/zh-cn
[7] Earlier animation tutorial: https://www.h5jun.com/post/animations-you-should-know.html
[8] GitHub repo: https://github.com/akira-cn/animator.js
ByteFE
Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.
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.