css实现数字切换动画(封装成React组件)

2,111 阅读3分钟

背景

最近做项目遇到一个需求,要做选择优惠券后金额试算的一个金额切换的动画效果,如图所示:

金额切换效果.gif

然后我把做了几个小demo用于演示:

效果演示.gif

技术

所用技术为:React.js、css3
本效果用的是CSS3 transition 属性

transition 属性设置元素当过渡效果,四个简写属性为:

描述
transition-property指定CSS属性的name,transition效果
transition-durationtransition效果需要指定多少秒或毫秒才能完成
transition-timing-function指定transition效果的转速曲线
transition-delay定义transition效果开始的时候

实现

我把0~9的数字全部列出来,排成一列,然后再根据需要展示的数字去对这个数字列进行定位,再利用transition过渡效果实现数字切换动画。 如图所示:

移动展示.gif

布局

首先是布局代码,将0~9的数字排列出来

<div className="number-animation-wrap">
        <div className="number-animation-wrap-hidden">0</div>
        <div className="number-animation" style={style}>
          <div className="number">0</div>
          <div className="number">1</div>
          <div className="number">2</div>
          <div className="number">3</div>
          <div className="number">4</div>
          <div className="number">5</div>
          <div className="number">6</div>
          <div className="number">7</div>
          <div className="number">8</div>
          <div className="number">9</div>
        </div>
      </div>

样式

设置样式:
overflow: hidden; 超出隐藏
transition: top 0.2s实现过渡动画

@defaultSize: 16;
.number-animation-wrap {
    position: relative;
    display: inline-block;
    overflow: hidden;

    .number-animation {
        position: absolute;
        left: 0;
        top: 0;
        height: auto;
        transform-origin: 0 0;
        transition: top 0.2s;//过渡属性的名称 过渡动画所需的时间
    }

    .number,.number-animation-wrap-hidden {
        line-height: unit((36/@defaultSize),rem);
        font-size: unit((36/@defaultSize),rem);
        font-weight: bold;
        text-align: center;
    }

    .number-animation-wrap-hidden{
        visibility: hidden;
    }
}

控制定位

根据要显示的数字value计算出要偏移的距离, 利用top对布局中的数字列表进行定位,展示对应的数字:

   let style = {
      top: (-1 * value * this.lineHeight / this.defaultSize) + 'rem'
    }

随着value的变化,动态地生成样式并设置到数字列表div上

    <div className="number-animation" style={style}>

调用

我将其封装成了组件,可以很方便的调用,可以根据需要进行组装:

效果演示.gif

import NumberAnimation from "../../src/numberAnimation";


<div style={{textAlign:"center",fontSize: "36px",fontWeight: "bold"}}>
    <div style={{margin:"2rem 0"}}>
       倒计时 <NumberAnimation value={this.state.number} />
    </div>
    <div style={{margin:"2rem 0"}}>
        计时
        {
            this.state.number2.toFixed(2).toString().replace(".",":").split("").map((numberItem, index) => {
                return (
                    <NumberAnimation key={index} value={numberItem} />
                )
            })
        }
    </div>
    <div style={{margin:"2rem 0"}}>
        访问人数
        {
            this.state.number4.toString().split("").map((numberItem, index) => {
                return (
                    <NumberAnimation key={index} value={numberItem} />
                )
            })
        }
    </div>
    <div style={{margin:"2rem 0"}}>
        {
            this.state.number.toString().split("").map((numberItem, index) => {
                return (
                    <NumberAnimation key={index} value={numberItem} />
                )
            })
        }
    </div>

    <div style={{margin:"2rem 0"}}>
        {
            this.state.number2.toFixed(2).toString().split("").map((numberItem, index) => {
                return (
                    <NumberAnimation key={index} value={numberItem} />
                )
            })
        }
    </div>
    <div style={{margin:"2rem 0"}}>
        {
            this.state.number3.toFixed(2).toString().split("").map((numberItem, index) => {
                return (
                    <NumberAnimation key={index} value={numberItem} />
                )
            })
        }
        <button onClick={()=>{this.setState({
            number3: Math.random()*Math.random()*100
        })}}> 点击切换数字</button>
    </div>

</div>

全部代码

js文件

//numberAnimationPage.js

import React, { Component } from 'react';
import './style.less'

class NumberAnimation extends Component {

  constructor(props) {
    super(props);
    this.lineHeight = 36;
    this.defaultSize = 16;
  }
  render() {
    const { value } = this.props;
    if (isNaN(value)) {//非数字
        return (
          <div className="number-animation-wrap">
            <div className="number-animation-wrap-hidden">.</div>
            <div className="number-animation">
              <div className="number">{value}</div>
            </div>
          </div>
        )
    }
    let style = {
      top: (-1 * value * this.lineHeight / this.defaultSize) + 'rem'
    }

    return (
      <div className="number-animation-wrap">
        <div className="number-animation-wrap-hidden">0</div>
        <div className="number-animation" style={style}>
          <div className="number">0</div>
          <div className="number">1</div>
          <div className="number">2</div>
          <div className="number">3</div>
          <div className="number">4</div>
          <div className="number">5</div>
          <div className="number">6</div>
          <div className="number">7</div>
          <div className="number">8</div>
          <div className="number">9</div>
        </div>
      </div>
    )
  }
}

export default NumberAnimation;


样式文件

//style.less

@defaultSize: 16;
.number-animation-wrap {
    position: relative;
    display: inline-block;
    overflow: hidden;

    .number-animation {
        position: absolute;
        left: 0;
        top: 0;
        height: auto;
        transform-origin: 0 0;
        transition: top 0.2s;//过渡属性的名称 过渡动画所需的时间
    }

    .number,.number-animation-wrap-hidden {
        line-height: unit((36/@defaultSize),rem);
        font-size: unit((36/@defaultSize),rem);
        font-weight: bold;
        text-align: center;
    }

    .number-animation-wrap-hidden{
        visibility: hidden;
    }
}

调用示例文件

//numberAnimationPage.js

import React, { Component } from "react";
import NumberAnimation from "../../src/numberAnimation";

class NumberAnimationPage extends Component {
    constructor(props) {
        super(props);
        this.state = {
            number: 0,
            number2: 0,
            number3: 0
        };
        this.number = 0;
        this.number2 = 0;
    }
    componentDidMount() {
        setInterval(() => {
            console.log(1)
            this.setState({
                number: this.number++,
                number2: this.number2 += 0.01
            })
        }, 1000)
    }
    render() {

        return (
            <div style={{textAlign:"center"}}>
                <div style={{margin:"2rem 0"}}>
                    <NumberAnimation value={this.state.number} />
                </div>
                <div style={{margin:"2rem 0"}}>
                    {
                        this.state.number.toString().split("").map((numberItem, index) => {
                            return (
                                <NumberAnimation key={index} value={numberItem} />
                            )
                        })
                    }
                </div>
                <div style={{margin:"2rem 0"}}>
                    {
                        this.state.number2.toFixed(2).toString().split("").map((numberItem, index) => {
                            return (
                                <NumberAnimation key={index} value={numberItem} />
                            )
                        })
                    }
                </div>
                <div style={{margin:"2rem 0"}}>
                    {
                        this.state.number3.toFixed(2).toString().split("").map((numberItem, index) => {
                            return (
                                <NumberAnimation key={index} value={numberItem} />
                            )
                        })
                    }
                    <button onClick={()=>{this.setState({
                        number3: Math.random()*Math.random()*100
                    })}}> 点击切换数字</button>
                </div>
                
            </div>
        );
    }
}

module.exports = NumberAnimationPage;

总结

  • 本组件是以单个数字形式进行展示,可以供使用者灵活组装
  • 组件中过滤了传入值为非数字的情况,支持用于展示整数、小数

大家有什么建议,或者其他的实现思路,欢迎在评论区分享。