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
 |