数字动画的几种实现方式

2022-10-19  本文已影响0人  金桔柠檬加冰

数字动画的几种实现方式

react-countup和DataV-React插件均为数字跳动动画

原生css实现数字滚动,是数字上下滚动效果动画

时钟翻牌器,实现上下翻动效果

1、react-countup

安装

yarn add react-countup
npm i react-countup

使用

引入

import CountUp from 'react-countup';

简单示例

        <CountUp
          start={0} // 开始数据
          end={this.state.end} // 结束数据
          duration={2.75} // 持续时间
          separator="," // 分隔符
          decimals={2} // 保留小数位
          delay={0} // 设置为0的时候立即执行
          decimal="." // 小数点符号
          redraw={true}  //如果为true,则组件将始终在每次重新渲染时进行动画处理。
          prefix="EUR " // 前缀
          suffix=" left" // 后缀
          onComplete={this.onComplete} //动画完成后调用的函数
          onStart={() => console.log('Started! 💨')} //在动画开始前调用的函数
        >
          {({ countUpRef, start }) => (
            <div>
              <span ref={countUpRef} />
              <button onClick={start}>Start</button>
            </div>
          )}
        </CountUp>

完整示例

import { useCountUp } from 'react-countup';

const CompleteHook = () => {
  const countUpRef = React.useRef(null);
  const { start, pauseResume, reset, update } = useCountUp({
    ref: countUpRef,
    start: 0,
    end: 1234567,
    delay: 1000,
    duration: 5,
    onReset: () => console.log('Resetted!'),
    onUpdate: () => console.log('Updated!'),
    onPauseResume: () => console.log('Paused or resumed!'),
    onStart: ({ pauseResume }) => console.log(pauseResume),
    onEnd: ({ pauseResume }) => console.log(pauseResume),
  });
  return (
    <div>
      <div ref={countUpRef} />
      <button onClick={start}>Start</button>
      <button onClick={reset}>Reset</button>
      <button onClick={pauseResume}>Pause/Resume</button>
      <button onClick={() => update(2000)}>Update to 2000</button>
    </div>
  );
};

优点:功能齐全,数字生成的是span标签包裹,方便对应样式修改,无需指定数字宽高

官网:http://inorganik.github.io/countUp.js/

github:https://github.com/glennreyes/react-countup

2、DataV-React-翻牌器

安装

npm i @jiaminghi/data-view-react

使用

引入

import DigitalFlop from '@jiaminghi/data-view-react/es/digitalFlop'

简单示例

<DigitalFlop config={config} style={{width: '200px', height: '50px'}} />

config属性

属性 说明 类型 可选值 默认值
number 数字数值[1] Array<Number> --- []
content 内容模版[1] String --- ''
toFixed 小数位数 Number --- 0
textAlign 水平对齐方式 String [2] 'center'
rowGap 行间距 Number [3] 0
style 样式配置 Object CRender Style [4]
formatter 格式化数字 Function [5] undefined
animationCurve 动效曲线 String Transition 'easeOutCubic'
animationFrame 动效帧数 Number [5] 50

优点:使用简单

缺点:数字生成的为canvas画布,需要事先指定好数据的宽高,不能很好的动态适应数据大小

文档:http://datav-react.jiaminghi.com/guide/

3、原生css实现数字滚动

import React, { Component } from 'react'
import './index.css'
export default class Index extends Component {
  constructor() {
    super(...arguments)
    this.state = ({
      listAll: [1, 2, 3, 4]
    })
  }
  render () {
    const { listAll } = this.state
    return (
      <div>
        <div onClick={this.handleClick.bind(this)}>变化数字</div>
        <div style={{margin: '10px'}}>
          {
            listAll.map((item) => {
              return (
                <div className="turn_box_container" style={{width: '80px', height: '100px'}}>
                  <div className="turn_box" style={ {top:  ( -1 * item * 100) +'px'} }> 
                    {
                      [...new Array(10)].map((item,index) => {
                        return ( <div className="turn_box_number">{index}</div> )
                      })
                    }
                  </div>
              </div>
              )
            })
          }
        </div>
      </div>
    )
    
  }
  // 模拟测试数据
  getNumber(){
    let random = Math.floor(Math.random() * (100000- 1) + 1)    
    let randomString = random.toString()
    let arr = []
    for (var i = 0, len = randomString.length; i < len; i += 1) {
      arr.push(randomString.charAt(i))
    }
    this.setState({
      listAll: arr
    })
  }
  handleClick() {
    this.getNumber()
  }
}
.turn_box_container {
  margin-left: 10px;
}
 
.turn_box_container {
  position: relative;
  display: inline-block;
  float: left;
  overflow: hidden;
}
 
.turn_box {
  position: absolute;
  left: 0;
  top: 0;
  height: auto;
  width: 100%;
  transform-origin: 0 0;
  transition: top 0.8s;
}
 
.turn_box_number {
  line-height: 100px;
  font-size: 66px;
  font-family: MicrosoftYaHei-Bold;
  font-weight: bold;
  color: #4898F1;
  text-align: center;
}
image.png

4、时钟翻牌器

import React, { Component } from 'react'
import './index.css'
import {Flipper,formatDate} from './common'

// 翻牌时钟的实现
export default class AnimateNum2 extends Component {

  componentDidMount(){
    // 定位时钟模块
      let clock = document.getElementById('clock')
      // 定位6个翻板
      let flips = clock.querySelectorAll('.flip')
      // 获取当前时间
      let now = new Date()
      // 格式化当前时间,例如现在是20:30:10,则输出"203010"字符串
      let nowTimeStr = formatDate(now, 'hhiiss')
      // 格式化下一秒的时间
      let nextTimeStr = formatDate(new Date(now.getTime() + 1000), 'hhiiss')
      // 定义牌板数组,用来存储6个Flipper翻板对象
      let flipObjs = []
      for (let i = 0; i < flips.length; i++) {
          // 创建6个Flipper实例,初始化并存入flipObjs
          flipObjs.push(new Flipper({
              // 每个Flipper实例按数组顺序与翻板DOM的顺序一一对应
              node: flips[i],
              // 按数组顺序取时间字符串对应位置的数字
              frontText: 'number' + nowTimeStr[i],
              backText: 'number' + nextTimeStr[i]
          }))
      }

      setInterval(function() {
        // 获取当前时间
        let now = new Date()
        // 格式化当前时间
        let nowTimeStr = formatDate(new Date(now.getTime() - 1000), 'hhiiss')
        // 格式化下一秒时间
        let nextTimeStr = formatDate(now, 'hhiiss')
        // 将当前时间和下一秒时间逐位对比
        for (let i = 0; i < flipObjs.length; i++) {
            // 如果前后数字没有变化,则直接跳过,不翻牌
            if (nowTimeStr[i] === nextTimeStr[i]) {
                continue
            }
            // 传递前后牌的数字,进行向下翻牌动画
            flipObjs[i].flipDown('number' + nowTimeStr[i], 'number' + nextTimeStr[i])
        }
    }, 1000)
  }

  render() {
    return (
      <div class="clock" id="clock">
        <div class="flip down">
            <div class="digital front number0"></div>
            <div class="digital back number1"></div>
        </div>
        <div class="flip down">
            <div class="digital front number0"></div>
            <div class="digital back number1"></div>
        </div>
        <em>:</em>
        <div class="flip down">
            <div class="digital front number0"></div>
            <div class="digital back number1"></div>
        </div>
        <div class="flip down">
            <div class="digital front number0"></div>
            <div class="digital back number1"></div>
        </div>
        <em>:</em>
        <div class="flip down">
            <div class="digital front number0"></div>
            <div class="digital back number1"></div>
        </div>
        <div class="flip down">
            <div class="digital front number0"></div>
            <div class="digital back number1"></div>
        </div>
    </div>
    )
  }
}
.clock {
  text-align: center;
  margin-top: 200px;
}

.clock em {
  display: inline-block;
  line-height: 102px;
  font-size: 66px;
  font-style: normal;
  vertical-align: top;
}
.flip {
  display: inline-block;
  position: relative;
  width: 60px;
  height: 100px;
  line-height: 100px;
  border: solid 1px #000;
  border-radius: 10px;
  background: #fff;
  font-size: 66px;
  color: #fff;
  box-shadow: 0 0 6px rgba(0, 0, 0, .5);
  text-align: center;
  font-family: "Helvetica Neue"
}
.flip .digital:before,
.flip .digital:after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    background: #000;
    overflow: hidden;
    box-sizing: border-box; 
}

.flip .digital:before {
    top: 0;
    bottom: 50%;
    border-radius: 10px 10px 0 0;
    border-bottom: solid 1px #666;
}

.flip .digital:after {
    top: 50%;
    bottom: 0;
    border-radius: 0 0 10px 10px;
    line-height: 0;
}


.flip .number0:before,
.flip .number0:after {
    content: "0";
}

.flip .number1:before,
.flip .number1:after {
    content: "1";
}

.flip .number2:before,
.flip .number2:after {
    content: "2";
}

.flip .number3:before,
.flip .number3:after {
    content: "3";
}

.flip .number4:before,
.flip .number4:after {
    content: "4";
}

.flip .number5:before,
.flip .number5:after {
    content: "5";
}

.flip .number6:before,
.flip .number6:after {
    content: "6";
}

.flip .number7:before,
.flip .number7:after {
    content: "7";
}

.flip .number8:before,
.flip .number8:after {
    content: "8";
}

.flip .number9:before,
.flip .number9:after {
    content: "9";
}

.flip.down .front:before {
  z-index: 3;
}

.flip.down .back:after {
  z-index: 2;
  transform-origin: 50% 0%;
  transform: perspective(160px) rotateX(180deg);
}

.flip.down .front:after,
.flip.down .back:before {
  z-index: 1;
}

/*向上翻*/
.flip.up .front:after {
  z-index: 3;
}

.flip.up .back:before {
  z-index: 2;
  transform-origin: 50% 100%;
  transform: perspective(160px) rotateX(-180deg);
}

.flip.up .front:before,
.flip.up .back:after {
  z-index: 1;
}

.flip.down.go .front:before {
  transform-origin: 50% 100%;
  animation: frontFlipDown 0.6s ease-in-out both;
  box-shadow: 0 -2px 6px rgba(255, 255, 255, 0.3);
  backface-visibility: hidden;
}

.flip.down.go .back:after {
  animation: backFlipDown 0.6s ease-in-out both;
}

@keyframes frontFlipDown {
  0% {
      transform: perspective(160px) rotateX(0deg);
  }

  100% {
      transform: perspective(160px) rotateX(-180deg);
  }
}

@keyframes backFlipDown {
  0% {
      transform: perspective(160px) rotateX(180deg);
  }

  100% {
      transform: perspective(160px) rotateX(0deg);
  }
}

.flip.up.go .front:after {
  transform-origin: 50% 0;
  animation: frontFlipUp 0.6s ease-in-out both;
  box-shadow: 0 2px 6px rgba(255, 255, 255, 0.3);
  backface-visibility: hidden;
}

.flip.up.go .back:before {
  animation: backFlipUp 0.6s ease-in-out both;
}
@keyframes frontFlipUp {
  0% {
      transform: perspective(160px) rotateX(0deg);
  }

  100% {
      transform: perspective(160px) rotateX(180deg);
  }
}

@keyframes backFlipUp {
  0% {
      transform: perspective(160px) rotateX(-180deg);
  }

  100% {
      transform: perspective(160px) rotateX(0deg);
  }
}
// common.js
function Flipper(config) {
  // 默认配置
  this.config = {
      // 时钟模块的节点
      node: null,
      // 初始前牌文字
      frontText: 'number0',
      // 初始后牌文字
      backText: 'number1',
      // 翻转动画时间(毫秒,与翻转动画CSS 设置的animation-duration时间要一致)
      duration: 600,
  }
  // 节点的原本class,与html对应,方便后面添加/删除新的class
  this.nodeClass = {
      flip: 'flip',
      front: 'digital front',
      back: 'digital back'
  }
  // 覆盖默认配置
  Object.assign(this.config, config)
  // 定位前后两个牌的DOM节点
  this.frontNode = this.config.node.querySelector('.front')
  this.backNode = this.config.node.querySelector('.back')
  // 是否处于翻牌动画过程中(防止动画未完成就进入下一次翻牌)
  this.isFlipping = false
  // 初始化
  this._init()
}
Flipper.prototype = {
  constructor: Flipper,
  // 初始化
  _init: function() {
      // 设置初始牌面字符
      this._setFront(this.config.frontText)
      this._setBack(this.config.backText)
  },
  // 设置前牌文字
  _setFront: function(className) {
      this.frontNode.setAttribute('class', this.nodeClass.front + ' ' + className)
  },
  // 设置后牌文字
  _setBack: function(className) {
      this.backNode.setAttribute('class', this.nodeClass.back + ' ' + className)
  },
  _flip: function(type, front, back) {
      // 如果处于翻转中,则不执行
      if (this.isFlipping) {
          return false
      }
      // 设置翻转状态为true
      this.isFlipping = true
      // 设置前牌文字
      this._setFront(front)
      // 设置后牌文字
      this._setBack(back)
      // 根据传递过来的type设置翻转方向
      let flipClass = this.nodeClass.flip;
      if (type === 'down') {
          flipClass += ' down'
      } else {
          flipClass += ' up'
      }
      // 添加翻转方向和执行动画的class,执行翻转动画
      this.config.node.setAttribute('class', flipClass + ' go')
      // 根据设置的动画时间,在动画结束后,还原class并更新前牌文字
      setTimeout(() => {
          // 还原class
          this.config.node.setAttribute('class', flipClass)
          // 设置翻转状态为false
          this.isFlipping = false
          // 将前牌文字设置为当前新的数字,后牌因为被前牌挡住了,就不用设置了。
          this._setFront(back)
      }, this.config.duration)
  },
  // 下翻牌
  flipDown: function(front, back) {
      this._flip('down', front, back)
  },
  // 上翻牌
  flipUp: function(front, back) {
      this._flip('up', front, back)
  }
}

//正则格式化日期
function formatDate(date, dateFormat) {
  /* 单独格式化年份,根据y的字符数量输出年份
   * 例如:yyyy => 2019
          yy => 19
          y => 9
   */
  if (/(y+)/.test(dateFormat)) {
      dateFormat = dateFormat.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
  }
  // 格式化月、日、时、分、秒
  let o = {
      'm+': date.getMonth() + 1,
      'd+': date.getDate(),
      'h+': date.getHours(),
      'i+': date.getMinutes(),
      's+': date.getSeconds()
  };
  for (let k in o) {
      if (new RegExp(`(${k})`).test(dateFormat)) {
          // 取出对应的值
          let str = o[k] + '';
          /* 根据设置的格式,输出对应的字符
           * 例如: 早上8时,hh => 08,h => 8
           * 但是,当数字>=10时,无论格式为一位还是多位,不做截取,这是与年份格式化不一致的地方
           * 例如: 下午15时,hh => 15, h => 15
           */
          dateFormat = dateFormat.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str));
      }
  }
  return dateFormat;
};

//日期时间补零
function padLeftZero(str) {
  return ('00' + str).substr(str.length);
}

export {
  Flipper,
  formatDate,
  padLeftZero
}
image.png

转载文字:https://juejin.cn/post/6844904003889790983

上一篇 下一篇

猜你喜欢

热点阅读