Skip to content

Css实现数字滚轮动画

之前遇到一个需求,需要实现两种数字滚筒动画效果,经过一番搜索,成功实现了需要的效果,特此记录下来。

效果图

第一种效果:滚筒

第二种效果:随机过渡

实现

楼主是将其封装成了Vue组件的形式,只要知道其原理就可以实现。

第一种效果实现代码:

js
/**
 * H5数字滚动效果
 * @class DigitRoll
 * @desc 没有任何依赖, 只兼容webkit内核, 主要用于H5页面. 组件本身没有css, 如果需要修改默认样式 可以添加css样式修饰.
 * @param {object} opts 实例化参数
 * @param {string} opts.container 容器选择器 selector
 * @param {number} opts.width=1 数字的总宽度个数, 即要显示几位数
 * @example
    HTML:
    <div id="num-roll"></div>
 * @example
    js:
    var r1=new DigitRoll({
        container:'#num-roll',
        width:9
    });
 */
function DigitRoll(opts) {
    this.container = document.querySelector(opts.container); //容器
    this.width = opts.width || 1;
    if (!this.container) {
        throw Error('no container');
    }
    this.container.style.overflow = 'hidden';
    this.rollHeight = parseInt(getComputedStyle(this.container).height); //容器高度 也用于滚动间隔距离

    if (this.rollHeight < 1) {//只有容器的高度是必选样式  如果没有设置 那就给一个默认的
        this.container.style.height = '20px';
        this.rollHeight = 20;
    }
    this.setWidth();
}
/**  @lends DigitRoll */
DigitRoll.prototype = {
    /** 
     * 滚动数字
     * @param {number} n 要滚动的数字
     * @example
        r1.roll(314159);
        //定时更新
        setInterval(function(){
            r1.roll(314159);
        },5000)
     */
    roll: function (n) {
        var self = this;
        this.number = parseInt(n) + '';
        if (this.number.length < this.width) {
            this.number = new Array(this.width - this.number.length + 1).join('0') + this.number;
        } else if (this.number.length > this.width) {
            this.width = this.number.length;
            this.setWidth();
        }
        Array.prototype.forEach.call(this.container.querySelectorAll('.my-transition-num'), function (item, i) {
            var currentNum = parseInt(item.querySelector('div:last-child').innerHTML);//当前数字
            var goalNum = parseInt(self.number[i]);//目标数字
            var gapNum = 0; //数字滚动的间隔个数
            var gapStr = '';
            if (currentNum == goalNum) { //数字没变 不处理
                return;
            } else if (currentNum < goalNum) { // 比如数字从1到3   
                gapNum = goalNum - currentNum;
                for (var j = currentNum; j < goalNum + 1; j++) {
                    gapStr += '<div>' + j + '</div>'
                }
            } else {// 比如 数字从6到5  因为所有情况都是从下往上滚动 所以如果是6到5的话 要滚动9个数字
                gapNum = 10 - currentNum + goalNum;
                for (var j = currentNum; j < 10; j++) {
                    gapStr += '<div>' + j + '</div>'
                }
                for (var j = 0; j < goalNum + 1; j++) {
                    gapStr += '<div>' + j + '</div>'
                }
            }
            item.style.cssText += '-webkit-transition-duration:0s;-webkit-transform:translateY(0)';//重置位置
            item.innerHTML = gapStr;
            setTimeout(function () {
                item.style.cssText += '-webkit-transition-duration:1s;-webkit-transform:translateY(-' + self.rollHeight * gapNum + 'px)';
            }, 50)
        })
    },
    /** 
     * 重置宽度
     * @desc 一般用不到这个方法  
     * @param {number} n 宽度 即数字位数
     * @example
        r1.setWidth(10);
     */
    setWidth: function (n) {
        n = n || this.width;
        var str = '';
        for (var i = 0; i < n; i++) {
            str += '<div class="my-transition-num" style="float:left;height:100%;line-height:' + this.rollHeight + 'px"><div>0</div></div>';
        }
        this.container.innerHTML = str;
    }
}


Vue.component('animated-integer-plus', {
    template: '<span><div v-bind:id="id" v-bind:style="domStyle">{{ value }}</div></span>',
    props: {
        value: {
            type: Number,
            required: true
        },
        id: {
            type: String,
            required: true,
            default: ''
        },
        height: {
            type: Number,
            required: true,
            default: ''
        },
        styleStr: {
            type: String,
            required: false,
            default: ''
        }
    },
    data() {
        return {
            current: null
        }
    },
    watch: {
        value: {
            handler(newVal) {
                setTimeout(() => {
                    this.current.roll(newVal)
                }, 1000)
            },
            deep: true
        }
    },
    computed:{
        domStyle(){
            if(this.style){
                return `height:${this.height}px;${this.styleStr}`
            }else{
                return `height:${this.height}px;float: left;`
            }
        }
    },
    mounted() {
        this.current = new DigitRoll({
            container: `#${this.id}`
        });
    }
})

第二种效果代码实现:

js
/**
 * 状态过渡
 */
Vue.component('animated-integer', {
    template: '<span style="float:unset;">{{ tweeningValue }}</span>',
    props: {
      value: {
        type: Number,
        required: true
      }
    },
    data: function () {
      return {
        tweeningValue: 0
      }
    },
    watch: {
      value: function (newValue, oldValue) {
        this.tween(oldValue, newValue)
      }
    },
    mounted: function () {
      this.tween(0, this.value)
    },
    methods: {
      tween: function (startValue, endValue) {
        var vm = this
        function animate () {
          if (TWEEN.update()) {
            requestAnimationFrame(animate)
          }
        }
  
        new TWEEN.Tween({ tweeningValue: startValue })
          .to({ tweeningValue: endValue }, 500)
          .onUpdate(function () {
            vm.tweeningValue = this.tweeningValue.toFixed(0)
          })
          .start()
  
        animate()
      }
    }
  })

总结

第一种效果较好,实现起来稍微难点,主要借助Css去实现。第二种效果一般,实现较为简单,值得一提的是实现原理借助的是requestAnimationFrame这个API。

上次更新于: