利用window.requestAnimationFrame实现简单的数字累加动画

2,835 阅读2分钟

原理

  1. window.requestAnimationFrame可以保证动画以每秒60次的频率执行(有时也会出现降频的现象)
  2. 每一次数字的变动即算一次动画
  3. 再执行每次动画之前,利用window.cancelAnimationFrame取消之前的动画,从而保证浏览器性能

方式

  1. 指定时间完成动画
  2. 指定速度完成动画

注:目前两种方式是互斥的

核心代码

// count.js
/**
 * 数字累加滚动,支持两种方式:
 * 1. 指定速度,通过interval控制,即控制帧与帧的间隔
 * 2. 指定时间,即:无论数字多大,指定时间内必须完成累加
 * @param {Object}   option              配置
 * @param {Number}   option.start        起始数字
 * @param {Number}   option.end          结束数字
 * @param {Number}   option.interval     帧与帧之间的间隔(ms)
 * @param {Boolean}  option.limitTime    指定时间
 * @param {Function} option.callback     回调函数,参数为每次累加后的数字
 */

export default function Count({
    start = 0,
    end = 100,
    interval = 0,
    limitTime = 0,
    callback
}) {
    // 每帧所需的时间(ms)
    // 按照MDN介绍,通常是每秒60帧
    const frameTime = 1000 / 60;

    // 帧数
    let frameAmount;

    // 步长
    // 即每次的累加值,默认: 1
    let frameStep = 1;

    // 计数器
    // 当指定interval时,计数器才起作用
    // 作用:用于与interval比较,等于interval时,执行回调,然后清零重新计数,达到控制速度的效果
    let counter = 0;

    // 如果指定了limitTime,则重新计算步长
    if (limitTime && !interval) {
        let length = end - start;

        // 指定时间内可以完成多少帧
        frameAmount = limitTime / frameTime;

        // 帧数与真实长度取两者之间最小值
        if (frameAmount > length) {
            frameAmount = length;
        }

        frameStep = Math.round((end - start) / frameAmount);
    }

    // 帧的回调函数
    function step() {
        let req;

        // 方式1和2的公共逻辑部分
        function commonLogic() {
            start += frameStep;
                
            // 防止最后一次累加时出现数字越界的情况
            if (start >= end) {
                callback(end);
            } else {
                callback(start);
                window.cancelAnimationFrame(req);
                req = window.requestAnimationFrame(step);
            }
        }

        // 方式1:按时间间隔
        if (interval !== 0) {
            counter++;

            if (counter === interval) {
                commonLogic();
                counter = 0;
            } else {
                window.cancelAnimationFrame(req);
                req = window.requestAnimationFrame(step);
            }
        // 方式2:按指定时间
        } else {
            if (start < end) {
                commonLogic();
            }
        }
    }

    window.requestAnimationFrame(step);
}

示例1:在指定时间内完成动效

注:结合Vue实现

<template>
  <div id="app">
    <div>{{num}}</div>
  </div>
</template>

<script>
import Count from './count';

export default {
  name: 'app',
  data () {
    return {
      num: 0
    }
  },
  mounted () {
    // 调用api
    Count({
      end: 100,         // 结束时间
      limitTime: 2000,  // 指定时间
      callback: (num) => {
        this.num = num;
      }
    })
  }
}
</script>

运行结果

示例1

示例2:按指定速度完成动效

注:结合Vue实现

<template>
  <div id="app">
    <div>{{num}}</div>
  </div>
</template>

<script>
import Count from './count';

export default {
  name: 'app',
  data () {
    return {
      num: 0
    }
  },
  mounted () {
    Count({
      end: 100,         // 结束数字
      interval: 6,      // 指定速度
      callback: (num) => {
        this.num = num;
      }
    })
  }
}

运行结果

示例2

原文链接:https://www.guoyunfeng.com/2018/05/31/number-counter/