上效果
使用方法
组件介绍
两种组件类型:
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
方法
问题:
setRange
,run
方法都是起到什么作用呢?
/**
* 组件的属性列表
*/
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: {}
},
},
前面这些都是准备阶段,解析来才是重头戏,敲黑板!
页面创建时执行的时候调用了setRange
,renderStyle
方法。
主要目的是初始化数据范围和页面样式。
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
属性做到只显示当前数字,如果把去掉这个属性就是这样的:
滚动的效果是通过改变translate3d
的y
轴实现的,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
})
},