时空网前端
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

220 lines
6.5 KiB

5 years ago
  1. import {root} from './env'
  2. /* istanbul ignore file */
  3. const Animate = (global => {
  4. /* istanbul ignore next */
  5. const time =
  6. Date.now ||
  7. (() => {
  8. return +new Date()
  9. })
  10. const desiredFrames = 60
  11. const millisecondsPerSecond = 1000
  12. let running = {}
  13. let counter = 1
  14. return {
  15. /**
  16. * A requestAnimationFrame wrapper / polyfill.
  17. *
  18. * @param callback {Function} The callback to be invoked before the next repaint.
  19. * @param root {HTMLElement} The root element for the repaint
  20. */
  21. requestAnimationFrame: (() => {
  22. // Check for request animation Frame support
  23. const requestFrame =
  24. global.requestAnimationFrame ||
  25. global.webkitRequestAnimationFrame ||
  26. global.mozRequestAnimationFrame ||
  27. global.oRequestAnimationFrame
  28. let isNative = !!requestFrame
  29. if (requestFrame && !/requestAnimationFrame\(\)\s*\{\s*\[native code\]\s*\}/i.test(requestFrame.toString())) {
  30. isNative = false
  31. }
  32. if (isNative) {
  33. return (callback, root) => {
  34. requestFrame(callback, root)
  35. }
  36. }
  37. const TARGET_FPS = 60
  38. let requests = {}
  39. let requestCount = 0
  40. let rafHandle = 1
  41. let intervalHandle = null
  42. let lastActive = +new Date()
  43. return callback => {
  44. const callbackHandle = rafHandle++
  45. // Store callback
  46. requests[callbackHandle] = callback
  47. requestCount++
  48. // Create timeout at first request
  49. if (intervalHandle === null) {
  50. intervalHandle = setInterval(() => {
  51. const time = +new Date()
  52. const currentRequests = requests
  53. // Reset data structure before executing callbacks
  54. requests = {}
  55. requestCount = 0
  56. for (const key in currentRequests) {
  57. if (currentRequests.hasOwnProperty(key)) {
  58. currentRequests[key](time)
  59. lastActive = time
  60. }
  61. }
  62. // Disable the timeout when nothing happens for a certain
  63. // period of time
  64. if (time - lastActive > 2500) {
  65. clearInterval(intervalHandle)
  66. intervalHandle = null
  67. }
  68. }, 1000 / TARGET_FPS)
  69. }
  70. return callbackHandle
  71. }
  72. })(),
  73. /**
  74. * Stops the given animation.
  75. *
  76. * @param id {Integer} Unique animation ID
  77. * @return {Boolean} Whether the animation was stopped (aka, was running before)
  78. */
  79. stop(id) {
  80. const cleared = running[id] != null
  81. cleared && (running[id] = null)
  82. return cleared
  83. },
  84. /**
  85. * Whether the given animation is still running.
  86. *
  87. * @param id {Integer} Unique animation ID
  88. * @return {Boolean} Whether the animation is still running
  89. */
  90. isRunning(id) {
  91. return running[id] != null
  92. },
  93. /**
  94. * Start the animation.
  95. *
  96. * @param stepCallback {Function} Pointer to function which is executed on every step.
  97. * Signature of the method should be `function(percent, now, virtual) { return continueWithAnimation; }`
  98. * @param verifyCallback {Function} Executed before every animation step.
  99. * Signature of the method should be `function() { return continueWithAnimation; }`
  100. * @param completedCallback {Function}
  101. * Signature of the method should be `function(droppedFrames, finishedAnimation) {}`
  102. * @param duration {Integer} Milliseconds to run the animation
  103. * @param easingMethod {Function} Pointer to easing function
  104. * Signature of the method should be `function(percent) { return modifiedValue; }`
  105. * @param root {Element ? document.body} Render root, when available. Used for internal
  106. * usage of requestAnimationFrame.
  107. * @return {Integer} Identifier of animation. Can be used to stop it any time.
  108. */
  109. start(stepCallback, verifyCallback, completedCallback, duration, easingMethod, root) {
  110. const start = time()
  111. let lastFrame = start
  112. let percent = 0
  113. let dropCounter = 0
  114. const id = counter++
  115. if (!root) {
  116. // root = document.body
  117. }
  118. // Compacting running db automatically every few new animations
  119. if (id % 20 === 0) {
  120. const newRunning = {}
  121. for (const usedId in running) {
  122. newRunning[usedId] = true
  123. }
  124. running = newRunning
  125. }
  126. // This is the internal step method which is called every few milliseconds
  127. const step = virtual => {
  128. // Normalize virtual value
  129. const render = virtual !== true
  130. // Get current time
  131. const now = time()
  132. // Verification is executed before next animation step
  133. if (!running[id] || (verifyCallback && !verifyCallback(id))) {
  134. running[id] = null
  135. completedCallback &&
  136. completedCallback(desiredFrames - dropCounter / ((now - start) / millisecondsPerSecond), id, false)
  137. return
  138. }
  139. // For the current rendering to apply let's update omitted steps in memory.
  140. // This is important to bring internal state variables up-to-date with progress in time.
  141. if (render) {
  142. const droppedFrames = Math.round((now - lastFrame) / (millisecondsPerSecond / desiredFrames)) - 1
  143. for (let j = 0; j < Math.min(droppedFrames, 4); j++) {
  144. step(true)
  145. dropCounter++
  146. }
  147. }
  148. // Compute percent value
  149. if (duration) {
  150. percent = (now - start) / duration
  151. if (percent > 1) {
  152. percent = 1
  153. }
  154. }
  155. // Execute step callback, then...
  156. let value = easingMethod ? easingMethod(percent) : percent
  157. value = isNaN(value) ? 0 : value
  158. if ((stepCallback(value, now, render) === false || percent === 1) && render) {
  159. running[id] = null
  160. completedCallback &&
  161. completedCallback(
  162. desiredFrames - dropCounter / ((now - start) / millisecondsPerSecond),
  163. id,
  164. percent === 1 || duration == null,
  165. )
  166. } else if (render) {
  167. lastFrame = now
  168. this.requestAnimationFrame(step, root)
  169. }
  170. }
  171. // Mark as running
  172. running[id] = true
  173. // Init first step
  174. this.requestAnimationFrame(step, root)
  175. // Return unique animation ID
  176. return id
  177. },
  178. }
  179. })(root)
  180. export const easeOutCubic = pos => {
  181. return Math.pow(pos - 1, 3) + 1
  182. }
  183. export const easeInOutCubic = pos => {
  184. if ((pos /= 0.5) < 1) {
  185. return 0.5 * Math.pow(pos, 3)
  186. }
  187. return 0.5 * (Math.pow(pos - 2, 3) + 2)
  188. }
  189. export default Animate