UNPKG

zwip

Version:

Utility for creating JS animations on top of W3C [requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) function.

187 lines (132 loc) 4.64 kB
import Emitter from 'klak'; import { assert, isRequired, isFunction, isString, isObject, isInteger, noop } from './utils'; const internal = { animations: [], state: { status: 'initialized' }, fps: 60, listenerTypes: ['start', 'stop', 'pause', 'unpause'] }; internal.loop = () => { if (internal.state.status !== 'started') return; internal.requestId = requestAnimationFrame(internal.loop); internal.now = Date.now(); internal.interval = 1000 / internal.fps; internal.delta = internal.now - internal.then; if (internal.delta > internal.interval) { internal.Zwip.frame(); } }; // internal.MethodCaller = (key, ...args) => { // // isRequired(key, 'key'); // isString(key, 'key'); // // return animation => { // // // console.log(internal.counter, animation, animation.frequency, internal.counter % animation.frequency) // if (animation[key] && (internal.counter % animation.frequency === 0)) // animation[key](...args); // // } // }; internal.MethodCaller = (key, ...args) => object => object[key](...args); internal.emitTick = internal.MethodCaller('emit', 'tick'); internal.callUpdate = internal.MethodCaller('update'); internal.callRender = internal.MethodCaller('render'); internal.callPause = internal.MethodCaller('pause'); internal.isNotPaused = object => !object.pausedAt; internal.isAnimationStarted = animation => animation.status === 'started'; internal.isAnimationStale = animation => internal.counter % animation.frequency === 0; internal.Zwip = { start() { if (internal.requestId) throw new Error('Loop is already started'); internal.counter = 0; internal.paused = null; internal.then = Date.now(); internal.state.status = 'started'; internal.loop(); internal.Zwip.emit('start'); }, stop() { if (internal.requestId) cancelAnimationFrame(internal.requestId); internal.requestId = null; internal.state.status = 'stopped'; internal.Zwip.emit('stop'); }, pause() { if (internal.paused) { internal.paused = null; internal.state.status = 'started'; internal.animations.forEach(internal.callPause); internal.Zwip.emit('unpause'); internal.loop(); return; } internal.animations.forEach(internal.callPause); internal.paused = Date.now(); internal.state.status = 'paused'; internal.Zwip.emit('pause'); }, isRegistered: animation => internal.animations.includes(animation), register(animation, auto = true) { isObject(animation, 'animation'); assert(animation.isZwipAnimation === true, `'animation' must be a ZwipAnimation object`); assert(isFunction(animation.render) || isFunction(animation.update), `At least 'render' or 'update' method is required`); animation.render = animation.render || noop; animation.update = animation.update || noop; if (auto && !internal.requestId) internal.Zwip.start(); if (internal.Zwip.isRegistered(animation)) return; internal.animations.push(animation); }, deregister(animation, auto = true) { const index = internal.animations.indexOf(animation); if (~index) internal.animations.splice(index, 1); if (auto && internal.requestId && internal.animations.length < 1) internal.Zwip.stop(); }, frame() { internal.Zwip.emit('tick'); internal.counter++; internal.elapsed = internal.now - internal.then; internal.then = internal.now - (internal.delta % internal.interval); internal.state.fps = 1000 / internal.elapsed; internal.state.animations = internal.animations.length; internal.state.frames = internal.counter; const startedAnimations = internal.animations.filter(internal.isNotPaused); const staleAnimations = startedAnimations.filter(internal.isAnimationStale); staleAnimations.forEach(internal.emitTick); staleAnimations.forEach(internal.callUpdate); staleAnimations.filter(internal.isAnimationStarted).forEach(internal.callRender); // const states = staleAnimations.map(internal.callUpdate); // // console.log('states:', states) // states.forEach((state, i) => { // const animation = staleAnimations[i]; // // animation.render(state || {}); // }); // startedAnimations.forEach(internal.callRender); }, get state() { return internal.state; }, get fps() { return internal.fps; }, set fps(newValue) { isInteger(newValue, 'fps'); internal.fps = newValue; } }; export default Object.assign( internal.Zwip, Emitter(['start', 'stop', 'pause', 'unpause', 'tick']) );