UNPKG

amator

Version:
155 lines (125 loc) 3.76 kB
var BezierEasing = require('bezier-easing') // Predefined set of animations. Similar to CSS easing functions var animations = { ease: BezierEasing(0.25, 0.1, 0.25, 1), easeIn: BezierEasing(0.42, 0, 1, 1), easeOut: BezierEasing(0, 0, 0.58, 1), easeInOut: BezierEasing(0.42, 0, 0.58, 1), linear: BezierEasing(0, 0, 1, 1) } module.exports = animate; module.exports.makeAggregateRaf = makeAggregateRaf; module.exports.sharedScheduler = makeAggregateRaf(); function animate(source, target, options) { var start = Object.create(null) var diff = Object.create(null) options = options || {} // We let clients specify their own easing function var easing = (typeof options.easing === 'function') ? options.easing : animations[options.easing] // if nothing is specified, default to ease (similar to CSS animations) if (!easing) { if (options.easing) { console.warn('Unknown easing function in amator: ' + options.easing); } easing = animations.ease } var step = typeof options.step === 'function' ? options.step : noop var done = typeof options.done === 'function' ? options.done : noop var scheduler = getScheduler(options.scheduler) var keys = Object.keys(target) keys.forEach(function(key) { start[key] = source[key] diff[key] = target[key] - source[key] }) var durationInMs = typeof options.duration === 'number' ? options.duration : 400 var durationInFrames = Math.max(1, durationInMs * 0.06) // 0.06 because 60 frames pers 1,000 ms var previousAnimationId var frame = 0 previousAnimationId = scheduler.next(loop) return { cancel: cancel } function cancel() { scheduler.cancel(previousAnimationId) previousAnimationId = 0 } function loop() { var t = easing(frame/durationInFrames) frame += 1 setValues(t) if (frame <= durationInFrames) { previousAnimationId = scheduler.next(loop) step(source) } else { previousAnimationId = 0 setTimeout(function() { done(source) }, 0) } } function setValues(t) { keys.forEach(function(key) { source[key] = diff[key] * t + start[key] }) } } function noop() { } function getScheduler(scheduler) { if (!scheduler) { var canRaf = typeof window !== 'undefined' && window.requestAnimationFrame return canRaf ? rafScheduler() : timeoutScheduler() } if (typeof scheduler.next !== 'function') throw new Error('Scheduler is supposed to have next(cb) function') if (typeof scheduler.cancel !== 'function') throw new Error('Scheduler is supposed to have cancel(handle) function') return scheduler } function rafScheduler() { return { next: window.requestAnimationFrame.bind(window), cancel: window.cancelAnimationFrame.bind(window) } } function timeoutScheduler() { return { next: function(cb) { return setTimeout(cb, 1000/60) }, cancel: function (id) { return clearTimeout(id) } } } function makeAggregateRaf() { var frontBuffer = new Set(); var backBuffer = new Set(); var frameToken = 0; return { next: next, cancel: next, clearAll: clearAll } function clearAll() { frontBuffer.clear(); backBuffer.clear(); cancelAnimationFrame(frameToken); frameToken = 0; } function next(callback) { backBuffer.add(callback); renderNextFrame(); } function renderNextFrame() { if (!frameToken) frameToken = requestAnimationFrame(renderFrame); } function renderFrame() { frameToken = 0; var t = backBuffer; backBuffer = frontBuffer; frontBuffer = t; frontBuffer.forEach(function(callback) { callback(); }); frontBuffer.clear(); } function cancel(callback) { backBuffer.delete(callback); } }