As a best-selling author, I invite you to explore my books on Amazon. Don't forget to follow me on Medium and show your support. Thank you! Your support means the world!
Building custom animation libraries requires mastering several fundamental techniques that ensure smooth performance, flexible control, and efficient resource management. Through years of working with web animations, I've discovered that successful animation systems depend on these seven core approaches.
Frame Rate Management
Creating adaptive frame rate systems forms the foundation of any professional animation library. I implement intelligent throttling mechanisms that monitor device capabilities and adjust animation smoothness accordingly. This prevents performance degradation on lower-end devices while maximizing visual quality on capable hardware.
class FrameRateManager {
constructor() {
this.targetFPS = 60;
this.frameTime = 1000 / this.targetFPS;
this.lastFrameTime = 0;
this.frameCount = 0;
this.actualFPS = 60;
this.performanceSamples = [];
this.adaptiveMode = true;
this.deviceTier = this.detectDeviceTier();
}
detectDeviceTier() {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (!gl) return 'low';
const renderer = gl.getParameter(gl.RENDERER);
const vendor = gl.getParameter(gl.VENDOR);
if (renderer.includes('Apple') || renderer.includes('NVIDIA')) return 'high';
if (renderer.includes('Intel')) return 'medium';
return 'low';
}
shouldUpdateFrame(currentTime) {
const elapsed = currentTime - this.lastFrameTime;
if (this.adaptiveMode) {
this.updatePerformanceMetrics(elapsed);
this.adjustFrameRate();
}
if (elapsed >= this.frameTime) {
this.lastFrameTime = currentTime;
this.frameCount++;
return true;
}
return false;
}
updatePerformanceMetrics(frameTime) {
this.performanceSamples.push(frameTime);
if (this.performanceSamples.length > 30) {
this.performanceSamples.shift();
}
if (this.performanceSamples.length >= 10) {
const averageFrameTime = this.performanceSamples.reduce((sum, time) => sum + time, 0) / this.performanceSamples.length;
this.actualFPS = 1000 / averageFrameTime;
}
}
adjustFrameRate() {
if (this.actualFPS < 45 && this.deviceTier === 'low') {
this.targetFPS = 30;
} else if (this.actualFPS < 55 && this.deviceTier === 'medium') {
this.targetFPS = 45;
} else if (this.actualFPS >= 55) {
this.targetFPS = 60;
}
this.frameTime = 1000 / this.targetFPS;
}
}
The frame rate manager continuously monitors performance and adapts to maintain smooth animations. I track actual frame times and adjust target frame rates based on device capabilities, ensuring consistent performance across different hardware configurations.
Timeline Architecture
Building robust timeline systems enables management of multiple simultaneous animations with precise timing controls. I create hierarchical timelines that support nested animations, pausing, and playback speed modifications through a comprehensive timeline architecture.
class Timeline {
constructor(options = {}) {
this.animations = [];
this.children = [];
this.parent = null;
this.startTime = 0;
this.duration = options.duration || 0;
this.progress = 0;
this.timeScale = 1;
this.paused = false;
this.reversed = false;
this.loop = options.loop || false;
this.yoyo = options.yoyo || false;
this.onUpdate = options.onUpdate;
this.onComplete = options.onComplete;
this.labels = new Map();
}
add(animation, position = '+=0') {
const insertTime = this.parsePosition(position);
if (animation instanceof Timeline) {
animation.parent = this;
this.children.push({
timeline: animation,
startTime: insertTime
});
} else {
this.animations.push({
animation: animation,
startTime: insertTime,
duration: animation.duration || 1000
});
}
this.updateDuration();
return this;
}
parsePosition(position) {
if (typeof position === 'number') {
return position;
}
if (position.startsWith('+=')) {
return this.duration + parseFloat(position.substring(2));
}
if (position.startsWith('-=')) {
return this.duration - parseFloat(position.substring(2));
}
if (this.labels.has(position)) {
return this.labels.get(position);
}
return this.duration;
}
addLabel(name, position = null) {
const time = position !== null ? this.parsePosition(position) : this.duration;
this.labels.set(name, time);
return this;
}
to(target, properties, duration, position = '+=0') {
const animation = new TweenAnimation(target, properties, duration);
return this.add(animation, position);
}
from(target, properties, duration, position = '+=0') {
const animation = new TweenAnimation(target, properties, duration, { reversed: true });
return this.add(animation, position);
}
stagger(targets, properties, duration, staggerTime = 0.1) {
targets.forEach((target, index) => {
const animation = new TweenAnimation(target, properties, duration);
this.add(animation, `+=${index * staggerTime}`);
});
return this;
}
updateDuration() {
let maxDuration = 0;
this.animations.forEach(item => {
const endTime = item.startTime + item.duration;
maxDuration = Math.max(maxDuration, endTime);
});
this.children.forEach(child => {
const endTime = child.startTime + child.timeline.duration;
maxDuration = Math.max(maxDuration, endTime);
});
this.duration = maxDuration;
}
update(globalTime) {
if (this.paused) return;
const localTime = (globalTime - this.startTime) * this.timeScale;
this.progress = Math.min(localTime / this.duration, 1);
if (this.reversed) {
this.progress = 1 - this.progress;
}
this.updateAnimations(localTime);
this.updateChildren(localTime);
if (this.onUpdate) {
this.onUpdate(this.progress);
}
if (this.progress >= 1 && !this.paused) {
this.handleComplete();
}
}
updateAnimations(localTime) {
this.animations.forEach(item => {
if (localTime >= item.startTime && localTime <= item.startTime + item.duration) {
const animationProgress = (localTime - item.startTime) / item.duration;
item.animation.update(animationProgress);
}
});
}
updateChildren(localTime) {
this.children.forEach(child => {
if (localTime >= child.startTime) {
child.timeline.update(localTime - child.startTime + child.timeline.startTime);
}
});
}
play() {
this.paused = false;
this.startTime = performance.now();
return this;
}
pause() {
this.paused = true;
return this;
}
reverse() {
this.reversed = !this.reversed;
return this;
}
seek(position) {
const seekTime = this.parsePosition(position);
this.progress = seekTime / this.duration;
this.update(seekTime);
return this;
}
timeScale(scale) {
this.timeScale = scale;
this.children.forEach(child => {
child.timeline.timeScale(scale);
});
return this;
}
}
This timeline system provides hierarchical control over complex animation sequences. I can nest timelines within other timelines, creating sophisticated animation orchestration with precise timing control and flexible playback options.
Easing Function Implementation
Developing comprehensive easing systems creates smooth, natural motion curves that enhance the visual appeal of animations. I implement custom easing functions using mathematical formulas that simulate realistic physics-based motion.
class EasingSystem {
constructor() {
this.functions = new Map();
this.registerDefaultEasings();
}
registerDefaultEasings() {
// Basic easing functions
this.register('linear', t => t);
this.register('easeIn', t => t * t);
this.register('easeOut', t => 1 - Math.pow(1 - t, 2));
this.register('easeInOut', t => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2);
// Cubic bezier implementations
this.register('ease', this.createCubicBezier(0.25, 0.1, 0.25, 1));
this.register('easeInCubic', this.createCubicBezier(0.32, 0, 0.67, 0));
this.register('easeOutCubic', this.createCubicBezier(0.33, 1, 0.68, 1));
// Elastic easing
this.register('elasticIn', t => {
if (t === 0 || t === 1) return t;
return -Math.pow(2, 10 * (t - 1)) * Math.sin((t - 1.1) * 5 * Math.PI);
});
this.register('elasticOut', t => {
if (t === 0 || t === 1) return t;
return Math.pow(2, -10 * t) * Math.sin((t - 0.1) * 5 * Math.PI) + 1;
});
// Bounce easing
this.register('bounceOut', t => {
if (t < 1 / 2.75) {
return 7.5625 * t * t;
} else if (t < 2 / 2.75) {
return 7.5625 * (t -= 1.5 / 2.75) * t + 0.75;
} else if (t < 2.5 / 2.75) {
return 7.5625 * (t -= 2.25 / 2.75) * t + 0.9375;
} else {
return 7.5625 * (t -= 2.625 / 2.75) * t + 0.984375;
}
});
// Back easing with overshoot
this.register('backIn', t => {
const c1 = 1.70158;
const c3 = c1 + 1;
return c3 * t * t * t - c1 * t * t;
});
this.register('backOut', t => {
const c1 = 1.70158;
const c3 = c1 + 1;
return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
});
}
createCubicBezier(x1, y1, x2, y2) {
return t => {
if (t <= 0) return 0;
if (t >= 1) return 1;
return this.solveCubicBezier(t, x1, y1, x2, y2);
};
}
solveCubicBezier(t, x1, y1, x2, y2) {
// Newton-Raphson method for solving cubic bezier
let x = t;
for (let i = 0; i < 8; i++) {
const fx = this.cubicBezierX(x, x1, x2) - t;
const fpx = this.cubicBezierXDerivative(x, x1, x2);
if (Math.abs(fx) < 1e-7) break;
if (Math.abs(fpx) < 1e-7) break;
x = x - fx / fpx;
}
return this.cubicBezierY(x, y1, y2);
}
cubicBezierX(t, x1, x2) {
return 3 * (1 - t) * (1 - t) * t * x1 + 3 * (1 - t) * t * t * x2 + t * t * t;
}
cubicBezierY(t, y1, y2) {
return 3 * (1 - t) * (1 - t) * t * y1 + 3 * (1 - t) * t * t * y2 + t * t * t;
}
cubicBezierXDerivative(t, x1, x2) {
return 3 * (1 - t) * (1 - t) * x1 + 6 * (1 - t) * t * (x2 - x1) + 3 * t * t * (1 - x2);
}
register(name, easingFunction) {
this.functions.set(name, easingFunction);
}
get(name) {
return this.functions.get(name) || this.functions.get('linear');
}
createSpring(tension = 0.8, friction = 0.2) {
return t => {
const w = Math.sqrt(tension);
const zeta = friction / (2 * Math.sqrt(tension));
if (zeta < 1) {
const wd = w * Math.sqrt(1 - zeta * zeta);
return 1 - Math.exp(-zeta * w * t) * (Math.cos(wd * t) + (zeta * w / wd) * Math.sin(wd * t));
} else {
return 1 - Math.exp(-w * t) * (1 + w * t);
}
};
}
createCustom(points) {
// Create custom easing from control points
return t => {
if (points.length < 2) return t;
const segment = Math.floor(t * (points.length - 1));
const localT = (t * (points.length - 1)) % 1;
if (segment >= points.length - 1) {
return points[points.length - 1];
}
return points[segment] + (points[segment + 1] - points[segment]) * localT;
};
}
}
This easing system provides mathematical precision in motion curves. I implement various easing types from simple linear interpolation to complex spring physics, enabling natural-feeling animations that respond appropriately to user interactions.
Animation State Management
Creating state machines that track animation progress and handle interruptions gracefully ensures robust animation behavior. I implement systems that can pause, resume, reverse, and modify animations mid-execution without visual glitches.
class AnimationStateMachine {
constructor() {
this.states = new Map();
this.currentState = 'idle';
this.previousState = null;
this.stateData = {};
this.transitions = new Map();
this.activeAnimations = new Set();
this.stateHistory = [];
this.setupDefaultStates();
}
setupDefaultStates() {
this.addState('idle', {
enter: () => this.pauseAllAnimations(),
exit: () => {},
update: () => {}
});
this.addState('playing', {
enter: () => this.resumeAllAnimations(),
exit: () => {},
update: (deltaTime) => this.updateAnimations(deltaTime)
});
this.addState('paused', {
enter: () => this.pauseAllAnimations(),
exit: () => {},
update: () => {}
});
this.addState('reversing', {
enter: () => this.reverseAllAnimations(),
exit: () => {},
update: (deltaTime) => this.updateAnimations(deltaTime)
});
this.addState('seeking', {
enter: (data) => this.seekAnimations(data.position),
exit: () => {},
update: () => {}
});
}
addState(name, config) {
this.states.set(name, {
enter: config.enter || (() => {}),
exit: config.exit || (() => {}),
update: config.update || (() => {}),
canEnter: config.canEnter || (() => true),
canExit: config.canExit || (() => true)
});
}
addTransition(from, to, condition = () => true) {
if (!this.transitions.has(from)) {
this.transitions.set(from, new Map());
}
this.transitions.get(from).set(to, condition);
}
transition(newState, data = {}) {
if (newState === this.currentState) return false;
const currentStateConfig = this.states.get(this.currentState);
const newStateConfig = this.states.get(newState);
if (!newStateConfig) {
console.warn(`State ${newState} does not exist`);
return false;
}
// Check if transition is allowed
if (!this.canTransition(this.currentState, newState)) {
return false;
}
// Check if current state can be exited
if (!currentStateConfig.canExit(data)) {
return false;
}
// Check if new state can be entered
if (!newStateConfig.canEnter(data)) {
return false;
}
// Execute transition
currentStateConfig.exit(data);
this.previousState = this.currentState;
this.currentState = newState;
this.stateData = data;
this.stateHistory.push({
state: newState,
timestamp: performance.now(),
data: { ...data }
});
newStateConfig.enter(data);
return true;
}
canTransition(from, to) {
const fromTransitions = this.transitions.get(from);
if (!fromTransitions) return true;
const condition = fromTransitions.get(to);
return condition ? condition() : true;
}
update(deltaTime) {
const stateConfig = this.states.get(this.currentState);
if (stateConfig) {
stateConfig.update(deltaTime);
}
}
addAnimation(animation) {
animation.stateMachine = this;
animation.state = 'idle';
animation.previousState = null;
this.activeAnimations.add(animation);
}
removeAnimation(animation) {
this.activeAnimations.delete(animation);
}
pauseAllAnimations() {
this.activeAnimations.forEach(animation => {
if (animation.state === 'playing') {
animation.previousState = animation.state;
animation.state = 'paused';
animation.pausedTime = performance.now();
}
});
}
resumeAllAnimations() {
this.activeAnimations.forEach(animation => {
if (animation.state === 'paused') {
const pauseDuration = performance.now() - animation.pausedTime;
animation.startTime += pauseDuration;
animation.state = 'playing';
}
});
}
reverseAllAnimations() {
this.activeAnimations.forEach(animation => {
animation.reversed = !animation.reversed;
animation.state = 'playing';
});
}
seekAnimations(position) {
this.activeAnimations.forEach(animation => {
const seekTime = position * animation.duration;
animation.startTime = performance.now() - seekTime;
animation.update(position);
});
}
updateAnimations(deltaTime) {
const completedAnimations = [];
this.activeAnimations.forEach(animation => {
if (animation.state === 'playing') {
const elapsed = performance.now() - animation.startTime;
const progress = Math.min(elapsed / animation.duration, 1);
animation.update(animation.reversed ? 1 - progress : progress);
if (progress >= 1) {
completedAnimations.push(animation);
}
}
});
completedAnimations.forEach(animation => {
this.handleAnimationComplete(animation);
});
}
handleAnimationComplete(animation) {
if (animation.loop) {
animation.startTime = performance.now();
} else {
animation.state = 'completed';
this.removeAnimation(animation);
if (animation.onComplete) {
animation.onComplete();
}
}
}
getState() {
return this.currentState;
}
getStateHistory() {
return [...this.stateHistory];
}
canPlay() {
return this.currentState !== 'playing';
}
canPause() {
return this.currentState === 'playing';
}
canReverse() {
return this.currentState === 'playing' || this.currentState === 'paused';
}
}
The state management system provides predictable animation behavior through well-defined states and transitions. I track animation progress comprehensively and handle state changes gracefully, ensuring animations behave consistently even when interrupted or modified during execution.
Performance Optimization
Building animation engines that minimize DOM manipulation and leverage hardware acceleration maintains 60fps performance. I use transform matrices and opacity changes instead of layout-affecting properties to achieve optimal rendering performance.
javascript
class PerformanceOptimizer {
constructor() {
this.transformCache = new WeakMap();
this.batchedUpdates = new Set();
this.scheduledFrame = null;
this.acceleratedProperties = new Set([
'transform', 'opacity', 'filter', 'perspective',
'transform-origin', 'backface-visibility'
]);
this.layerPromotionThreshold = 3;
this.promotedElements = new WeakSet();
}
optimizeElement(element) {
// Promote to GPU layer if necessary
if (this.shouldPromoteToLayer(element)) {
this.promoteToGPULayer(element);
}
// Initialize transform cache
if (!this.transformCache.has(element)) {
this.transformCache.set(element, {
x: 0, y: 0, z: 0,
rotateX: 0, rotateY: 0, rotateZ: 0,
scaleX: 1, scaleY: 1, scaleZ: 1,
skewX: 0, skewY: 0,
matrix: null,
dirty: false
});
}
return this.transformCache.get(element);
}
shouldPromoteToLayer(element) {
const animations = this.getActiveAnimations(element);
return animations.length >= this.layerPromotionThreshold;
}
promoteToGPULayer(element) {
if (this.promotedElements.has(element)) return;
element.style.willChange = 'transform, opacity';
element.style.backfaceVisibility = 'hidden';
element.style.perspective = '1000px';
this.promotedElements.add(element);
}
unpromoteFromGPULayer(element) {
if (!this.promotedElements.has(element)) return;
element.style.willChange = 'auto';
element.style.backfaceVisibility = '';
element.style.perspective = '';
this.promotedElements.delete(element);
}
batchTransformUpdate(element, transforms) {
const cache = this.optimizeElement(element);
// Update transform cache
Object.assign(cache, transforms);
cache.dirty = true;
// Schedule batched update
this.batchedUpdates.add(element);
this.scheduleUpdate();
}
scheduleUpdate() {
if (this.scheduledFrame) return;
this.scheduledFrame = requestAnimationFrame(() => {
this.flushBatchedUpdates();
this.scheduledFrame = null;
});
}
flushBatchedUpdates() {
const fragment = document.createDocumentFragment();
this.batchedUpdates.forEach(element => {
const cache = this.transformCache.get(element);
if (cache && cache.dirty) {
this.applyTransform(element, cache);
cache.dirty = false;
}
});
this.batchedUpdates.clear();
}
applyTransform(element, cache) {
// Build transform matrix if transforms are complex
if (this.hasComplexTransforms(cache)) {
cache.matrix = this.buildTransformMatrix(cache);
element.style.transform = `matrix3d(${cache.matrix.join(',')})`;
} else {
// Use individual transform functions for simpler cases
const transformParts = [];
if (cache.x !== 0 || cache.y !== 0 || cache.z !== 0) {
transformParts.push(`translate3d(${cache.x}px, ${cache.y}px, ${cache.z}px)`);
}
if (cache.rotateZ !== 0) {
transformParts.push(`rotateZ(${cache.rotateZ}deg)`);
}
if (cache.rotateX !== 0) {
transformParts.push(`rotateX(${cache.rotateX}deg)`);
}
if (cache.rotateY !== 0) {
transformParts.push(`rotateY(${cache.rotateY}deg)`);
}
if (cache.scaleX !== 1 || cache.scaleY !== 1) {
transformParts.push(`scale3d(${cache.scaleX}, ${cache.scaleY}, ${cache.scaleZ})`);
}
if (cache.skewX !== 0 || cache.skewY !== 0) {
transformParts.push(`skew(${cache.skewX}deg, ${cache.skewY}deg)`);
}
element.style.transform = transformParts.join(' ');
}
}
hasComplexTransforms(cache) {
return cache.rotateX !== 0 || cache.rotateY !== 0 ||
cache.z !== 0 || cache.scaleZ !== 1 ||
(cache.skewX !== 0 && cache.skewY !== 0);
}
buildTransformMatrix(cache) {
// Create 4x4 transformation matrix
const matrix = [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
];
// Apply transformations in order
this.applyTranslation(matrix, cache.x, cache.y, cache.z);
this.applyRotation(matrix, cache.rotateX, cache.rotateY, cache.rotateZ);
this.applyScale(matrix, cache.scaleX, cache.scaleY, cache.scaleZ);
this.applySkew(matrix, cache.skewX, cache.skewY);
return matrix;
}
applyTranslation(matrix, x, y, z) {
matrix[12] += x;
matrix[13] += y;
matrix[14] += z;
}
applyRotation(matrix, rx, ry, rz) {
if (rz !== 0) {
const cos = Math.cos(rz * Math.PI / 180);
const sin = Math.sin(rz * Math.PI / 180);
const m00 = matrix[0] * cos - matrix[1] * sin;
const m01 = matrix[0] * sin + matrix[1] * cos;
const m10 = matrix[4] * cos - matrix[5] * sin;
const m11 = matrix[4] * sin + matrix[5] * cos;
matrix[0] = m00; matrix[1] = m01;
matrix[4] = m10; matrix[5] = m11;
}
}
applyScale(matrix, sx, sy, sz) {
matrix[0] *= sx; matrix[1] *= sx; matrix[2] *= sx;
matrix[4] *= sy; matrix[5] *= sy; matrix[6] *= sy;
matrix[8] *= sz; matrix[9] *= sz; matrix[10] *= sz;
}
applySkew(matrix, skewX, skewY) {
if (skewX !== 0) {
const tan = Math.tan(skewX * Math.PI / 180);
matrix[4] += matrix[0] * tan;
matrix[5] += matrix[1] * tan;
}
if (skewY !== 0) {
const tan = Math.tan(skewY * Math.PI / 180);
matrix[0] += matrix[4] * tan;
matrix[1] += matrix[5] * tan;
}
}
getActiveAnimations(element) {
// This would integrate with your animation system
return element.animations || [];
}
measurePerformance(callback) {
const start = performance.now();
callback();
const end = performance.now();
return end - start;
}
shouldU
---
## 101 Books
**101 Books** is an AI-driven publishing company co-founded by author **Aarav Joshi**. By leveraging advanced AI technology, we keep our publishing costs incredibly low—some books are priced as low as **$4**—making quality knowledge accessible to everyone.
Check out our book **[Golang Clean Code](https://www.amazon.com/dp/B0DQQF9K3Z)** available on Amazon.
Stay tuned for updates and exciting news. When shopping for books, search for **Aarav Joshi** to find more of our titles. Use the provided link to enjoy **special discounts**!
## Our Creations
Be sure to check out our creations:
**[Investor Central](https://www.investorcentral.co.uk/)** | **[Investor Central Spanish](https://spanish.investorcentral.co.uk/)** | **[Investor Central German](https://german.investorcentral.co.uk/)** | **[Smart Living](https://smartliving.investorcentral.co.uk/)** | **[Epochs & Echoes](https://epochsandechoes.com/)** | **[Puzzling Mysteries](https://www.puzzlingmysteries.com/)** | **[Hindutva](http://hindutva.epochsandechoes.com/)** | **[Elite Dev](https://elitedev.in/)** | **[JS Schools](https://jsschools.com/)**
---
### We are on Medium
**[Tech Koala Insights](https://techkoalainsights.com/)** | **[Epochs & Echoes World](https://world.epochsandechoes.com/)** | **[Investor Central Medium](https://medium.investorcentral.co.uk/)** | **[Puzzling Mysteries Medium](https://medium.com/puzzling-mysteries)** | **[Science & Epochs Medium](https://science.epochsandechoes.com/)** | **[Modern Hindutva](https://modernhindutva.substack.com/)**
Top comments (0)