评论

开源推荐|小程序数字滚动动效组件

数字动画组件源码分析

上效果

使用方法

组件介绍

两种组件类型:
animateNumber: 范围内的所有数字连贯滚动,显示效果佳,但仅限于上下500内,否则页面卡顿
animateNumbers: 各个数位的数字单独滚动,0以上皆可

// animateNumber 使用示例
<animate-number value="{{value}}" min='{{-50}}' max='{{300}}' options='{{options}}'/>

// 配置项如下
options: {
  during: 1,            // (number) 动画时间
  height: 40,           // (number) 滚动行高 px
  width: '100%',        // (string) 组件整体宽度
  ease: 'cubic-bezier(0, 1, 0, 1)',   // (string) 动画过渡效果
  color: '#FF5837',     // (string) 字体颜色
  columnStyle: '',      // (string) 字体单元 覆盖样式
}


// animateNumbers 使用示例
<animate-numbers value="{{value}}" min='{{0}}' max='{{999}}' options='{{options}}'/>

// 配置项如下
options: {
  during: 1,            // (number) 动画时间
  height: 40,           // (number) 滚动行高 px
  cellWidth: 24,        // (number) 单个数字宽度 px
  ease: 'cubic-bezier(0, 1, 0, 1)',   // (string) 动画过渡效果
  color: '#FF5837',     // (string) 字体颜色
  columnStyle: '',      // (string) 字体单元 覆盖样式
}

使用代码

wxml代码

<!--index.wxml-->
<view class="container">
  <view class="userinfo">
    <button class="btn" bindtap="onReset">重置</button>
    <input class="input" value="{{numberVal}}" type='number' bindinput="onInput" bindconfirm="onInput"></input>
    <button class="btn" type="primary" bindtap="onRun">运行</button>
  </view>

  <!-- 组件 -->
  <view class="title">单维数字阵列:</view>
  <view class="count-up">
    <view>默认范围 0~100,配置项options</view>
    <animate-number value="{{numberVal}}" options='{{options}}'/>
  </view>

  <view class="count-up">
    <view>设置最大值为 limit:200</view>
    <animate-number value="{{numberVal}}" max='{{limit}}' />
  </view>

  <view class="count-up">
    <view>设置范围为 -50~200</view>
    <animate-number value="{{numberVal}}" min='{{start}}' max='{{limit}}' />
  </view>

  <view class="title">多维数字阵列:</view>
  <view class="count-up">
    <view>配置同上</view>
    <animate-numbers value="{{numberVal}}" max='{{limit}}' options='{{options}}' />
  </view>
  
</view>

js 代码

//index.js
//获取应用实例
const app = getApp()

Page({
  data: {
    value: 0,
    numberVal: '',

    start: -50,
    limit: 200,

    options: {
      color: 'green',
      during: 2,
      height: 50,
      width: '100px',
      columnStyle: 'font-weight: normal',
    },
  },
  //事件处理函数
  onReset: function() {
    this.setData({
      numberVal: 0
    })
  },
  onRun(){
    this.setData({
      numberVal: this.data.value
    })
  },
  onInput(e){
    let value = e.detail.value
    this.setData({
      value
    })
  },
  
})

源码分析

animateNumber

先从animateNumber组件开始,从js部分开始
先看常量属性部分分别是:
CONFIG:作为默认样式配置
LOCK:最大值,上面有提到超过500会卡顿

问题:为什么超过500就卡顿呢?

const CONFIG = {
  during: 1,        // :number 动画时间
  height: 40,       // :number 滚动行高 px
  width: '100%',    // 组件整体宽度
  ease: 'cubic-bezier(0, 1, 0, 1)',   // 动画过渡效果
  color: '#FF5837', // 字体颜色
  columnStyle: '',  // 字体单元 覆盖样式
}
const LOCK = 500    // 锁止

再来看看属性列表分别是:
value:需要滚动到的目标值,设置监听变化时调用run方法
max:最大值,设置监听变化时调用setRange方法
min:最小值,设置监听变化时调用setRange方法

问题:setRangerun方法都是起到什么作用呢?

/**
   * 组件的属性列表
   */
  properties: {
    value: {
      type: Number,
      value: 0,
      observer (n){
        this.run(n)
      }
    },
    max: {
      type: Number,
      value: 100,
      observer (){
        this.setRange()
      }
    },
    min: {
      type: Number,
      value: 0,
      observer (){
        this.setRange()
      }
    },
    options: {
      type: Object,
      value: {}
    },
  },

前面这些都是准备阶段,解析来才是重头戏,敲黑板!
页面创建时执行的时候调用了setRangerenderStyle方法。
主要目的是初始化数据范围和页面样式。
setRange方法作用把所有目标值区间的数字都遍历生成出来。如:设置了200为最大值,columns数组里面就会有1-200的数字。
renderStyle方法作用把设置的一些基础样式。
run设置具体当前显示的数字


  /**
   * 内存数据
   */
  data: {
    columns: [],
    key: 0,
    _options: JSON.parse(JSON.stringify(CONFIG)),
  },
  // 页面创建时执行
  attached(){
    this.setRange()
    this.renderStyle()
  },

  /**
   * 组件的方法列表
   */
  methods: {
    setRange(){
      let { max,min } = this.properties
      let arr = []

      min = parseInt(min)
      max = parseInt(max)
      if(max - min < 0){
        max = min
      }else if(max - min > LOCK){
        max = min + LOCK
      }

      for(let i = min; i<= max; i++){
        arr.push(i)
      }
      this.setData({
        columns: arr,
        max,
        min,
      })
      
      // 范围调整后,修正当前 value
      this.run(this.properties.value)

    },
    run(n){
      let { max,min } = this.data
      let index;
      n = parseInt(n)
      n = n<min ? min :
          n>max ? max : n
      index = this.data.columns.indexOf(n)
      this.setData({
        key: index>-1 ? index : 0
      })
    },
    renderStyle(){
      /**
       * color,
       * columnStyle, 
       * width, 
       * height, 
       * during, 
       * ease, 
       */
      let options = this.properties.options,
        _options = this.data._options;
      // console.log('options:',options)
      Object.keys(options).map(i=>{
        let val = options[i]
        switch (i) {
          case 'during':
          case 'height':
            if(parseInt(val) || val === 0 || val === '0'){
              _options[i] = val
            }
            break;
          default:
            val && (_options[i] = val);
            break;
        }
        
      })
      this.setData({
        _options
      })
    },
  }

wxml代码

<view class="count-box" style="width:{{_options.width}};">
  <view class="viewport" style="height:{{_options.height}}px;">
    <view class="column-wrap" style="transform: translate3d(0, -{{key * _options.height}}px, 0); transition-duration:{{_options.during}}s; transition-timing-function:{{_options.ease}}">
      <view 
        wx:for="{{columns}}" 
        wx:key="index" 
        class="item"
        style="color:{{_options.color}};height:{{_options.height}}px;line-height: {{_options.height}}px;{{_options.columnStyle}}">{{item}}</view>
    </view>
  </view>
</view>

css代码

/* components/animateNumber/index.wxss */

.count-box{
  /* width: 100%; */
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}
.viewport{
  width: 100%;
  overflow: hidden;
}
.column-wrap{
  width: 100%;
  transform: translate3d(0, 0, 0);
  transition: all 1s cubic-bezier(0, 1, 0, 1);
}
.item{
  width: 100%;
  text-align: center;
  font-size: 40px;
  font-weight: bold;
}

columns是所有区间内的值,然后布局会wx:for="{{columns}}"通过全部渲染出来,这里也就是为什么上限设置了500,因为一个页面显示的元素过多就会导致卡顿。

那么遍历出了200个为什么看不到了?因为通过overflow:hidden属性做到只显示当前数字,如果把去掉这个属性就是这样的:
滚动的效果是通过改变translate3dy轴实现的,y的值是通过-key * _options.height计算出来的,height好理解就是一个数字的高度,而key就是具体当前要显示的数字。如:我输入数字2,每个高度是50px,那么就是2*50=100px。

那么这个时候你肯定会有个疑问,很多时候我们会有超过500的数字,那怎么办呢?这个问题好解决可以用animateNumbers组件。

animateNumbers

animateNumber 和 animateNumbers 大同小异,很多内容都是一样除了一个地方。不一样的地方在于单维数字阵列和多维数字阵列。如:value为100,animateNumber是一个内容为100的view,animateNumbers是三个view组合而成一个内容为1的view和两个内容为0的view。

再看看wxml布局代码,采用的是双重循环。

<view class="count-box">
  <view class="viewport" wx:for="{{columns}}" wx:key="index" style="width:{{_options.cellWidth}}px;height:{{_options.height}}px;">
    <view class="column-wrap" style="transform: translate3d(0, -{{keys[index] * _options.height}}px, 0);transition-duration:{{_options.during}}s; transition-timing-function:{{_options.ease}}">
      <view 
        wx:for="{{item}}" 
        wx:for-item="row"
        wx:for-index="idx"
        wx:key="idx" 
        class="item"
        style="color:{{_options.color}};height:{{_options.height}}px;line-height: {{_options.height}}px;{{_options.columnStyle}}">{{row}}</view>
    </view>
  </view>
</view>

再看看js代码
注:同样的js代码就不重复解释了,主要看看不一样的地方。
initColumn把数字进行了分割,如:最大值100获取100的长度为3,那么就变成了3个数组,每个数组都是1-9的数字。
run方法里面按位分别计算key值放入放到keys

setRange(){
      let { max,min } = this.properties

      min = parseInt(min) >= 0 ? parseInt(min): 0
      max = parseInt(max) > min ? parseInt(max): min

      let columns = this.initColumn(max);

      this.setData({
        columns,
        max, 
        min,
      })

      // 范围调整后,修正当前 value
      if(this.properties.value) {
        this.run(this.properties.value)
      }

    },
    initColumn(n){
      let digit = (n + '').length,
          arr = [],
          rows = [' ', '0','1','2','3','4','5','6','7','8','9'];
      for(let i = 0; i< digit; i++){
        if(i) {
          arr.unshift(rows)
        }else{
          arr.unshift( rows.slice(1) )
        }
      }
      return arr
    },

    run(n){
      let { max,min } = this.data;
      let value = parseInt(n);
          value = value<min ? min :
          value>max ? max : value;
      let valueArr = value.toString().split(''), 
          lengths = this.data.columns.length,
          indexs = [];

      while(valueArr.length){
        let figure = valueArr.pop();
        if(indexs.length){
          indexs.unshift(parseInt(figure) +1)
        }else{
          indexs.unshift(parseInt(figure))
        }
      }
      while( indexs.length < lengths ){
        indexs.unshift(0)
      }
      this.setData({
        keys: indexs
      })
    },

源码地址

源码地址:https://github.com/Lyuing/wxAnimateNumber

最后一次编辑于  2021-06-08  
点赞 1
收藏
评论
登录 后发表内容