154 lines
3.7 KiB
JavaScript
154 lines
3.7 KiB
JavaScript
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);
|
|
}
|
|
}
|