221 lines
6.5 KiB
JavaScript
221 lines
6.5 KiB
JavaScript
import {root} from './env'
|
|
|
|
/* istanbul ignore file */
|
|
const Animate = (global => {
|
|
/* istanbul ignore next */
|
|
const time =
|
|
Date.now ||
|
|
(() => {
|
|
return +new Date()
|
|
})
|
|
const desiredFrames = 60
|
|
const millisecondsPerSecond = 1000
|
|
|
|
let running = {}
|
|
let counter = 1
|
|
|
|
return {
|
|
/**
|
|
* A requestAnimationFrame wrapper / polyfill.
|
|
*
|
|
* @param callback {Function} The callback to be invoked before the next repaint.
|
|
* @param root {HTMLElement} The root element for the repaint
|
|
*/
|
|
requestAnimationFrame: (() => {
|
|
// Check for request animation Frame support
|
|
const requestFrame =
|
|
global.requestAnimationFrame ||
|
|
global.webkitRequestAnimationFrame ||
|
|
global.mozRequestAnimationFrame ||
|
|
global.oRequestAnimationFrame
|
|
let isNative = !!requestFrame
|
|
|
|
if (requestFrame && !/requestAnimationFrame\(\)\s*\{\s*\[native code\]\s*\}/i.test(requestFrame.toString())) {
|
|
isNative = false
|
|
}
|
|
|
|
if (isNative) {
|
|
return (callback, root) => {
|
|
requestFrame(callback, root)
|
|
}
|
|
}
|
|
|
|
const TARGET_FPS = 60
|
|
let requests = {}
|
|
let requestCount = 0
|
|
let rafHandle = 1
|
|
let intervalHandle = null
|
|
let lastActive = +new Date()
|
|
|
|
return callback => {
|
|
const callbackHandle = rafHandle++
|
|
|
|
// Store callback
|
|
requests[callbackHandle] = callback
|
|
requestCount++
|
|
|
|
// Create timeout at first request
|
|
if (intervalHandle === null) {
|
|
intervalHandle = setInterval(() => {
|
|
const time = +new Date()
|
|
const currentRequests = requests
|
|
|
|
// Reset data structure before executing callbacks
|
|
requests = {}
|
|
requestCount = 0
|
|
|
|
for (const key in currentRequests) {
|
|
if (currentRequests.hasOwnProperty(key)) {
|
|
currentRequests[key](time)
|
|
lastActive = time
|
|
}
|
|
}
|
|
|
|
// Disable the timeout when nothing happens for a certain
|
|
// period of time
|
|
if (time - lastActive > 2500) {
|
|
clearInterval(intervalHandle)
|
|
intervalHandle = null
|
|
}
|
|
}, 1000 / TARGET_FPS)
|
|
}
|
|
|
|
return callbackHandle
|
|
}
|
|
})(),
|
|
|
|
/**
|
|
* Stops the given animation.
|
|
*
|
|
* @param id {Integer} Unique animation ID
|
|
* @return {Boolean} Whether the animation was stopped (aka, was running before)
|
|
*/
|
|
stop(id) {
|
|
const cleared = running[id] != null
|
|
cleared && (running[id] = null)
|
|
return cleared
|
|
},
|
|
|
|
/**
|
|
* Whether the given animation is still running.
|
|
*
|
|
* @param id {Integer} Unique animation ID
|
|
* @return {Boolean} Whether the animation is still running
|
|
*/
|
|
isRunning(id) {
|
|
return running[id] != null
|
|
},
|
|
|
|
/**
|
|
* Start the animation.
|
|
*
|
|
* @param stepCallback {Function} Pointer to function which is executed on every step.
|
|
* Signature of the method should be `function(percent, now, virtual) { return continueWithAnimation; }`
|
|
* @param verifyCallback {Function} Executed before every animation step.
|
|
* Signature of the method should be `function() { return continueWithAnimation; }`
|
|
* @param completedCallback {Function}
|
|
* Signature of the method should be `function(droppedFrames, finishedAnimation) {}`
|
|
* @param duration {Integer} Milliseconds to run the animation
|
|
* @param easingMethod {Function} Pointer to easing function
|
|
* Signature of the method should be `function(percent) { return modifiedValue; }`
|
|
* @param root {Element ? document.body} Render root, when available. Used for internal
|
|
* usage of requestAnimationFrame.
|
|
* @return {Integer} Identifier of animation. Can be used to stop it any time.
|
|
*/
|
|
start(stepCallback, verifyCallback, completedCallback, duration, easingMethod, root) {
|
|
const start = time()
|
|
let lastFrame = start
|
|
let percent = 0
|
|
let dropCounter = 0
|
|
const id = counter++
|
|
|
|
if (!root) {
|
|
root = document.body
|
|
}
|
|
|
|
// Compacting running db automatically every few new animations
|
|
if (id % 20 === 0) {
|
|
const newRunning = {}
|
|
for (const usedId in running) {
|
|
newRunning[usedId] = true
|
|
}
|
|
running = newRunning
|
|
}
|
|
|
|
// This is the internal step method which is called every few milliseconds
|
|
const step = virtual => {
|
|
// Normalize virtual value
|
|
const render = virtual !== true
|
|
|
|
// Get current time
|
|
const now = time()
|
|
|
|
// Verification is executed before next animation step
|
|
if (!running[id] || (verifyCallback && !verifyCallback(id))) {
|
|
running[id] = null
|
|
completedCallback &&
|
|
completedCallback(desiredFrames - dropCounter / ((now - start) / millisecondsPerSecond), id, false)
|
|
return
|
|
}
|
|
|
|
// For the current rendering to apply let's update omitted steps in memory.
|
|
// This is important to bring internal state variables up-to-date with progress in time.
|
|
if (render) {
|
|
const droppedFrames = Math.round((now - lastFrame) / (millisecondsPerSecond / desiredFrames)) - 1
|
|
for (let j = 0; j < Math.min(droppedFrames, 4); j++) {
|
|
step(true)
|
|
dropCounter++
|
|
}
|
|
}
|
|
|
|
// Compute percent value
|
|
if (duration) {
|
|
percent = (now - start) / duration
|
|
if (percent > 1) {
|
|
percent = 1
|
|
}
|
|
}
|
|
|
|
// Execute step callback, then...
|
|
let value = easingMethod ? easingMethod(percent) : percent
|
|
value = isNaN(value) ? 0 : value
|
|
if ((stepCallback(value, now, render) === false || percent === 1) && render) {
|
|
running[id] = null
|
|
completedCallback &&
|
|
completedCallback(
|
|
desiredFrames - dropCounter / ((now - start) / millisecondsPerSecond),
|
|
id,
|
|
percent === 1 || duration == null,
|
|
)
|
|
} else if (render) {
|
|
lastFrame = now
|
|
this.requestAnimationFrame(step, root)
|
|
}
|
|
}
|
|
|
|
// Mark as running
|
|
running[id] = true
|
|
|
|
// Init first step
|
|
this.requestAnimationFrame(step, root)
|
|
|
|
// Return unique animation ID
|
|
return id
|
|
},
|
|
}
|
|
})(root)
|
|
|
|
export const easeOutCubic = pos => {
|
|
return Math.pow(pos - 1, 3) + 1
|
|
}
|
|
|
|
export const easeInOutCubic = pos => {
|
|
if ((pos /= 0.5) < 1) {
|
|
return 0.5 * Math.pow(pos, 3)
|
|
}
|
|
|
|
return 0.5 * (Math.pow(pos - 2, 3) + 2)
|
|
}
|
|
|
|
export default Animate
|