手把手教你写 JS 动画
相信大家对这种数字渐变动画效果应该不面生吧. 接下来分析一下如何用 JS 实现这个动画.
数字渐变动画的实现
仔细想想, 这种数字变化不就是在一段时间内从 0 到 5000 吗. 换个思维, 这不就是 从 0 到 1 的过程 (也可以说是 从 0% 到 100%). 动画是需要时间的, 在指定的时间内, 数字从 0 到 1, 是不是可以抽象成时间逐渐流逝? 换句话说不就是时间消耗完了? 那么数字从 0 到 1 的过程, 不就可以转变为时间从开始到结束的过程. 接着这个思路, 就可以把动画抽象成, 当前时间过了百分之多少, 再拿这个百分比做相关转换不就可以了. 接下来实现这个数字渐变.
- <div class="box">0</div>
- <script>
- const oBox = document.querySelector('.box')
- const startTime = + new Date
- let timer = null
- function step() {
- const percent = Math.min(1, (+new Date - startTime) / 5000)// 动画时间为 5s
- if (percent <1) {
- oBox.innerhtml = ~~(5000 * percent)
- timer = requestAnimationFrame(step)
- } else {
- oBox.innerHTML = ~~(5000 * 1)
- cancelAnimationFrame(timer)
- }
- }
- timer = requestAnimationFrame(step)
- </script>
可以看到动画效果和预期是一致的, 说明通过时间消耗的多少来做动画的思路是对的.
实现 Animator 类
总结上面数字动画的实现方式, 我们可以实现这样一个动画类, 它可以设置 动画时长, 并在 动画过程 中主动调用 onUpdate 函数, 在动画完成后, 调用 onComplete 函数.
- class Animator {
- constructor() {
- this.durationTime = 0
- this.eventHandlers = new Map()
- }
- duration(time) {
- if (typeof time !== 'number') {
- throw new Error('Duration must be a number')
- }
- this.durationTime = time
- return this
- }
- on(type, handler) {
- if (typeof handler !== 'function') {
- throw new Error('Handler must be a function')
- }
- this.eventHandlers.set(type, handler)
- return this
- }
- animate() {
- const duration = this.durationTime
- const update = this.eventHandlers.get('update') || (t => t)
- const complete = this.eventHandlers.get('complete') || (() => {})
- let timer = null
- const startTime = +new Date()
- function step() {
- const percent = Math.min(1, (+new Date() - startTime) / duration)
- if (percent <1) {
- update(percent)
- timer = requestAnimationFrame(step)
- } else {
- cancelAnimationFrame(timer)
- update(1)
- complete()
- }
- }
- timer = requestAnimationFrame(step)
- }
- }
使用该类也可以实现上面数字渐变动画:
- const oBox = document.querySelector('.box')
- new Animator()
- .duration(3000)
- .on('update', t => (oBox.innerHTML = ~~(t * 5000)))
- .on('complete', () => alert('ok'))
- .animate()
增强 Animator 类
相信很多前端同学对 easing,easing-in-out 都不陌生, 那么如何用 JS 实现类似的动画效果呢? 这其中的奥秘就是在于 缓动函数. 我们通过将动画转变为时间流逝的百分比来做动画, 时间的流逝是线性的, 可以想象从 0 到 1 时间的曲线是不变的, 但是我们可以用时间流逝百分比 乘以某个函数, 只要该函数能保证从 0 到 1 仍旧是从 0 到 1, 中间百分比变化我们就可以不管. 举个例子: y = x * x, 当 0 <= x <=1 的时候, y 的结果便还是 0 到 1 之间, 很显然 x => x * x, 就是一个缓动函数.
增强 Animator 类:
- class Animator {
- constructor() {
- this.durationTime = 0
- this.easingFn = k => k
- this.eventHandlers = new Map()
- }
- easing(fn) {
- if (typeof fn !== 'function') {
- throw new Error('Easing must be a function, such as k => k')
- }
- this.easingFn = fn
- return this
- }
- duration(time) {
- if (typeof time !== 'number') {
- throw new Error('Duration must be a number')
- }
- this.durationTime = time
- return this
- }
- on(type, handler) {
- if (typeof handler !== 'function') {
- throw new Error('Handler must be a function')
- }
- this.eventHandlers.set(type, handler)
- return this
- }
- animate() {
- const duration = this.durationTime
- const easing = this.easingFn
- const update = this.eventHandlers.get('update') || (t => t)
- const complete = this.eventHandlers.get('complete') || (() => {})
- let timer = null
- const startTime = +new Date()
- function step() {
- const percent = Math.min(1, (+new Date() - startTime) / duration)
- if (percent <1) {
- update(easing(percent))
- timer = requestAnimationFrame(step)
- } else {
- cancelAnimationFrame(timer)
- update(easing(1))
- complete()
- }
- }
- timer = requestAnimationFrame(step)
- }
- }
使用 :
- const oBox1 = document.querySelector('.box1')
- const oBox2 = document.querySelector('.box2')
- const oBox3 = document.querySelector('.box3')
- new Animator()
- .duration(3000)
- .easing(x => k * k)
- .on('update', t => oBox1.style.left = t * 500 + 'px')
- .on('complete', () => alert('ok'))
- .animate()
- new Animator()
- .duration(3000)
- .easing(k => (1 - --k * k * k * k))
- .on('update', t => oBox2.style.left = t * 500 + 'px')
- .on('complete', () => alert('ok'))
- .animate()
- new Animator()
- .duration(3000)
- .easing(k => 1 - Math.sqrt(1 - k * k))
- .on('update', t => oBox3.style.left = t * 500 + 'px')
- .on('complete', () => alert('ok'))
- .animate()
更多好玩的 easing 函数, 推荐看 tween.JS 的缓动函数.
来源: https://juejin.im/entry/5bc8058b6fb9a05d3c802948