I am looking for feedback on what I can improve to a tool I have been using for several months now, my AnimationFrameController
. I find requestAnimationFrame an incredibly hassle and hard to control, so this controller does it for you. Are there better/more efficient ways to do this? The below is an example of how to use the code and the ES6 version of the source.
The main reason I built this is to be able to quickly throw together animation or things you can do every frame, but in the requestAnimationFrame
function you get exactly one call, and you better hope it calls all animations etc... With my animationFrameController its as easy as going AF.add( myFunction )
and it will be called continuously until it gets removed (by AF.remove
) or it returns false
itself and cancels itself out. The animationFrame ends when no more handlers remain.
"use strict";
/*
* AnimationFrameController 1.0
*/
class AnimationFrameController {
constructor( autostart = true ){
this.autostart = true;
this.paused = true;
this.time = 0;
this.fps = 0;
this.callees = [];
this.calleesTime = [];
}
get time(){
return this._time;
}
set time(v){
this.fps = (v - this.time) / 1000;
this._time = v;
return this._time;
}
get paused(){
return this._paused;
}
set paused(v){
this._paused = v;
if( this._paused === false ){
this.loop( this.time );
}
}
add( ...handlers ){
handlers.forEach( handler => {
if(handler instanceof Function){
this.callees.unshift( handler );
this.calleesTime.unshift( this.time );
}
});
if(this.callees.length && this.autostart && this.paused) this.paused = false;
}
remove( ...handlers ){
handlers.forEach( handler => {
let index = -1;
while((index = this.callees.lastIndexOf( handler )) >= 0){
this.callees.splice( index, 1 );
this.calleesTime.splice( index, 1 );
}
});
if(this.callees.length <= 0) this.paused = true;
}
loop( time ){
try {
if( !this.paused ){
window.requestAnimationFrame( this.loop.bind(this) );
let delta = time - this.time;
this.time = time;
this.callees.map(( handler, index ) => {
let progress = time - this.calleesTime[index];
return handler( delta, progress ) === false ? handler : true;
}).forEach(handler => {
return handler !== true ? this.remove(handler) : 0;
});
}
} catch(e) {
this.paused = true;
console.warn('An error has occurred while running the AnimationFrame.', e);
}
}
}
/* IMPLEMENTATION */
var AF = new AnimationFrameController;
var button = document.getElementById('clickme');
var animation = false;
var x = 0, y = 0;
button.addEventListener('click', function( event ){
var fromX = x;
var fromY = y;
var toX = Math.round( Math.random() * (window.innerWidth - button.clientWidth) );
var toY = Math.round( Math.random() * (window.innerHeight - button.clientHeight) );
AF.remove( animation );
AF.add(animation = function( delta, progress ){
var now = progress / 2000;
if( now >= 1 ){
x = toX;
y = toY;
return false;
} else {
x = fromX + (toX - fromX) * now;
y = fromY + (toY - fromY) * now;
}
});
});
AF.add(function(){
button.style.left = x + 'px';
button.style.top = y + 'px';
});
<input type="button" value="click me" id="clickme" style="position:absolute;" />
(You can find an ES5 version over at github if you want to try it in al older browser: https://github.com/jasperlinsen/AnimationFrameController)