让前端飞Web前端之路

使用React动画库——react-spring

2019-11-08  本文已影响0人  虚拟J

为了让后台系统视觉体验更好,决定增加过渡动画效果。
React官网中提到的动画库有3个:React Transition GroupReact Motion 以及 React Spring。·
我选择用react-spring的原因,无非就是文档是3个中看的最顺眼的,所以就选它了。

react-spring 是一个基于弹簧物理学的动画库,满足大多数与UI相关的动画需求,提供了足够灵活的工具,可以自信地将想法投射到不断变化的界面中。
该库代表了一种现代动画方法。它的灵感很大程度上来自克里斯托弗·切多(Christopher Chedeau)的 animated 和成娄(Cheng Lou)的 react-motion 。它继承了animated强大的插值和性能,以及react-motion的易用性。
虽然animated主要是命令式的,而react-motion主要是声明式的,但react-spring却在两者之间架起了桥梁。

react-spring库提供了2种API,一种是 Hooks api(就是 React 16.8 的新增特性——Hook),一种是 Render-props api

下面是我加到系统后台的过渡动画(以后增加再写出来慢慢补充):

当值改变时,数字的过渡动画:
// 用 Hooks api 写的话,就是重新写一个函数组件
 import { useSpring, animated } from 'react-spring'
 function Number(props) {
  const props = useSpring({ number: props.number ? props.number : 0 });
   return (
     <animated.span>
      {props.number.interpolate(x => (x * 100).toFixed(0))}
     </animated.span>
   )
 }
// 如果要再已有的Class组件内直接加的话,用 Render-props api
import { Spring, animated } from 'react-spring/renderprops'
 <Spring native to={{ number: classWrongScore ? classWrongScore : 0 }}>
   {props => (<animated.span>
           {props.number.interpolate(x => (x * 100).toFixed(0))}
        </animated.span>)}
 </Spring>
当值改变时,环形图的过渡动画:

下面的是从我之前用css写的环形图改过来的。

import { Spring, animated } from 'react-spring/renderprops'

 <div className={style.annulusBasics}>
     <div className={style.centerCircle}></div>
     <div className={style.annulusOuter}></div>
     <Spring native to={{ number: classWrongScore ? classWrongScore : 0 }}>
      {props => <>
        <animated.div className={style.leftRectangle} style={{
            transform: props.number.interpolate(x => (x > 0.5 ? `rotate(${180 * x}deg)` : 'rotate(0deg)'))
        }}></animated.div>
        <animated.div className={style.rightRectangle} style={{
            transform: props.number.interpolate(x => (x <= 0.5 ? `rotate(${360 * x}deg)` : 'rotate(0deg)')),
            background: props.number.interpolate(x => (x > 0.5 ? '#FF7F69' : '#EDEDED'))
        }} ></animated.div>
        </>}
     </Spring>
     {/* 加下面一个div是因为hidde在移动端失效导致样式不对 */}
     <div className={style.repairAnnulus}></div>
  </div>

下面的是从我用canvas写的环形图demo改过来的,我是写成函数组件的形式。

import React, { useEffect } from "react";

export default function canvasDonut(props) {
    useEffect(() => {
        const ctx = document.getElementById("myCanvas").getContext("2d");
        //外圆环
        ctx.beginPath();
        ctx.arc(50, 50, 47, 0, 2 * Math.PI);
        ctx.lineWidth = 1;
        ctx.strokeStyle = "#fff";
        ctx.fillStyle = "#EDEDED";
        ctx.fill();
        ctx.stroke();
        //内圆环
        ctx.beginPath();
        ctx.arc(50, 50, 35, 0, 2 * Math.PI);
        ctx.lineWidth = 1;
        ctx.strokeStyle = "#fff";
        ctx.fillStyle = "#fff";
        ctx.fill();
        ctx.stroke();
        // //圆环图的进度条
        ctx.beginPath();
        ctx.arc(50, 50, 41, -Math.PI / 2, -Math.PI / 2 + props.percent * (Math.PI * 2), false);
        ctx.lineWidth = props.percent!==0 && 12;
        ctx.lineCap = "round";
        ctx.strokeStyle = "rgb(255, 127, 105)";
        ctx.stroke();
    });
    return (
        <canvas id="myCanvas" width="100" height="100" />
    );
}
<Spring to={{ number: classWrongScore ? classWrongScore : 0 }}>
     {props => <CanvasDonut percent={props.number}/>}
</Spring>
定时上下滚动:
//定时上下滚动
function useTimingRolling(time = 1000, distance) {
    const [scrolling, setScrolling] = useSpring(() => ({ from: { scroll: 0 } }))
    const ref = useRef(null)
    useEffect(() => {
        if (!distance) { distance = ref.current.clientHeight }
    }, [])
    useEffect(() => {
        let intervalID = setInterval(() => {
            setScrolling({ scroll: ref.current.scrollTop + distance });
            if (Math.ceil(ref.current.scrollTop + ref.current.clientHeight) >= ref.current.scrollHeight) {
                setScrolling({ scroll: 0 });
            }
        }, time);
        return () => { clearInterval(intervalID); }
    }, [])
    return [scrolling.scroll, ref]
}

function chestnut() {
    const [timeTable, timeTableRef] = useTimingRolling(10000);
    return (
        <animated.div scrollTop={timeTable} ref={timeTableRef} >
            {new Array(10).fill('chestnut').map((item, i) => (
                <animated.div key={i}> {item}{i} </animated.div>
            ))}
        </animated.div>
    )
}
逐条滚动:
function chestnut() {

    const [delayTable, setDelayTable] = useState(new Array(10).fill('chestnut'))
    useEffect(() => {
        let intervalID;
        intervalID = setInterval(() => {
            new Promise(resolve => {
                let ago;
                setDelayTable(delayTable => { ago = delayTable.shift(); return delayTable })
                resolve(ago)
            }).then((res) => {
                setDelayTable(delayTable => { delayTable.push(res); return delayTable })
            })
        }, 5000)
        return () => { clearInterval(intervalID); clearTimeout(dingshi) }
    }, [])

    const transitions = useTransition(delayTable, (item, i) => i, {
        from: { transform: 'rotateX(0deg)' },
        enter: { transform: 'rotateX(0deg)' },
        leave: { transform: 'rotateX(90deg)' },
    })

    return (
        <animated.div>
            {transitions.map(({ item, props, key }) =>
                <animated.div style={props} key={key}>
                    <animated.span>{item}{i}</animated.span>
                </animated.div>
            )}
        </animated.div>
    )
}

使用 Hooks api注意点:

使用 Render-props api 注意点:(renderprops是:Spring,Transition,Trail,Keyframes,Parallax)

这个库在打包的时候可能会有个坑:
我是用roadhog,所以使用了这个库之后编译打包,会报错 Failed to minify the bundle. Error: 2.async.js from UglifyJs。原因是这个 npm 包没有遵守约定,没有转成 es5 就发上去,可以看下这个文章
解决方案就是可以在.webpackrc里加入以下代码,让 babel 编译 node_modules 下指定的文件

{
  "extraBabelIncludes": [
    "node_modules/react-spring",
  ]
}
上一篇下一篇

猜你喜欢

热点阅读