19.react-spring

1. react-spring基本使用

1.安装动画库

1
npm install react-spring

2.引入动画库

1
import { useSpring, animated, config } from 'react-spring'

3.声明改变透明度的动画

1
2
3
4
5
6
7
8
9
10
11
12
13
 const opacityAnimation = useSpring({
from: { opacity: 0 },
to: { opacity: 1 }
})
{/*把要动画的元素通过animated来修饰*/}
<animated.div style={opacityAnimation}>Demo1</animated.div>

//当我们要对组件进行动画的时候,我们需要使用animated方法把组件包裹起来,然后直接使用包裹后的组件名字
const AnimatedDonut4 = animated(Donut4)
return (
<AnimatedDonut4>
</AnimatedDonut4>
)

2. 动画的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//4.声明改变颜色的动画
/*
config的一些配置参数:
mass:影响速度以及过渡的效果。
tension:影响整体速度。
friction:控制阻力及其减速的速度。
clamp:是否应该瞬间过渡。

config.default { mass: 1, tension: 170, friction: 26 }
config.gentle { mass: 1, tension: 120, friction: 14 }
config.wobbly { mass: 1, tension: 180, friction: 12 }
config.stiff { mass: 1, tension: 210, friction: 20 }
config.slow { mass: 1, tension: 280, friction: 60 }
config.molasses { mass: 1, tension: 280, friction: 120 }
*/
const colorAnimation = useSpring({
from: { color: 'blue' },
color: 'red', //默认情况下 to可以省略
config: config.slow, //动画的配置
config: { duration: 2000,tension: 210 },
delay: 2000, //动画的延迟时间
reset:true, //从from状态过渡到to状态的时候有动画
reverse: true, //反转from和to的状态(reverse要生效,一定要先设置reset为true)
//动画开始的时候调用
onStart:()=>{
console.log("onStart")
},
//动画停止的时候调用
onRest:()=>{
console.log("onRest")
},
//每一帧动画都会调用onFrame,即使动画没有开始也会调用,会将动画的属性值作为参数入参
onFrame:(value)=>{
console.log("onFrame",value)
}
})

3. 添加多个动画属性和动画状态

1
2
3
4
5
6
7
8
9
10
11
//5.创建多个属性的动画
const multiAnimation = useSpring({
from: { opacity: 0, color: 'red' },
to: [
{ opacity: 1, color: '#ffaaee' },
{ opacity: 0.1, color: 'red' },
{ opacity: .5, color: '#008000' },
{ opacity: .8, color: 'black' }
],
config: { duration: 2000 },
});

//通过async和await来控制多个动画状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const animation = useSpring({
from: { left: '0%', top: '0%', width: '0%', height: '0%', background: 'lightgreen' },
to: async (next, cancel) => {
while (true) {
await next({ left: '0%', top: '0%', width: '100%', height: '100%',background: 'lightblue' })
await next({ height: '50%', background: 'lightgreen' })
await next({ width: '50%', left: '50%', background: 'lightgoldenrodyellow' })
await next({ top: '0%', height: '100%', background: 'lightpink' })
await next({ top: '50%', height: '50%', background: 'lightsalmon' })
await next({ width: '100%', left: '0%', background: 'lightcoral' })
await next({ width: '50%', background: 'lightseagreen' })
await next({ top: '0%', height: '100%', background: 'lightskyblue' })
await next({ width: '100%', background: 'lightslategrey' })
}
},
})

4. interpolate的使用

interpolate方法可以用来在动画执行的过程中,根据给定的起始、最终值,计算动画的每一步需要的值。

interpolate还有一个作用可以让我们模拟css的关键帧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const { o, xyz, color } = useSpring({
from: { o: 0, xyz: [0, 0, 0], color: 'red' },
to: { o: 1, xyz: [10, 20, 5], color: 'green' },
config: { duration: 2000 }
})

return (
<animated.div style={{
color,
opacity: o.interpolate(v => v),
transform: xyz.interpolate((x, y, z) => { return `translate3d(${x}px,${y}px,${z}px)` }),
//直接调用interpolate方法可以合并多个值,这边border使用多个值来设定样式
//第一个参数是要合并的值;第二个参数是一个箭头函数,用来返回指定样式
border: interpolate([o, color], (o, color) => { return `${o * 10}px solid ${color}` }),
//interpolate还可以来做关键帧动画,range对应动画的阶段,output对应每一个动画阶段的输出值*
padding: o.interpolate({ range: [0, 0.5, 1], output: [0, 1, 10] }).interpolate(o => `${o}%`),
borderColor: o.interpolate({ range: [0, 1], output: ['pink', 'yellow'] }),
//interpolate在做关键帧动画的时候,可以省略range和output
opacity: o.interpolate([0.1, 0.2, 0.6, 1], [1, 0.1, 0.5, 1])
}}>{/*如果要把动画属性渲染到页面,此时只能有一个元素*/}
{o.interpolate(v => v.toFixed(2))}
</animated.div>
)

注意点:interpolate只能被动画属性调用,普通字段是不可以调用interpolate方法

5. 使用state来控制动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//hook:允许我们在函数组件中使用state
const [flag, setFlag] = useState(0);

//这边所创建的animation的opacity的变化是通过一个flag的state来控制的
const animation = useSpring({
from: { opacity: 0 },
to: { opacity: flag ? 1 : 0 }
})

return (
<div>
<button onClick={()=>{setFlag(!flag)}}>点我切换透明度</button>
<animated.div onMouseOver={()=>{setFlag(true)}} onMouseOut={()=>{setFlag(false)}} style={animation}>透明度变了</animated.div>
</div>

6. 通过useSpring入参箭头函数来控制动画

1
2
3
4
5
6
7
8
9
//1.useSpring()接收箭头函数,此时会返回一个数组
//通过解构赋值获取到useSpring()函数返回的结果 animation表示动画属性,set可以用来修改动画属性,stop可以用来停止动画
const [animation, set, stop] = useSpring(() => ({ opacity: 0 }))

return (
<div>
<animated.div style={animation} onMouseOver={()=>{set({opacity: 1})}} onMouseOut={()=>{set({opacity: 0})}}>移入移出动画</animated.div>
</div>
)

7. useSprings动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
useSprings可以用来创建一组Spring,每个Spring都有自己的配置。

const items = [0.2, 0.4, 0.6, 0.8, 1.0];
//创建5个spring
//第一个spring的opacity=0.2
//第二个spring的opacity=0.4
//第三个spring的opacity=0.6
//第四个spring的opacity=0.8
//第五个spring的opacity=1.0
const springs = useSprings(5, items.map(item => ({ opacity: item,from:{opacity:0} })));

return (
springs.map((props,idx) => <animated.div key={idx} style={props}>item springs</animated.div>)
)

8. useTrail动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//useTrail可以用来创建一组Spring,每个Spring都有相同的配置,每个Spring跟随前一个Spring,在交叉的动画中使用

//这种写法可以
//const trail = useTrail(5, {opacity: 1})

//这种写法也可以
//创建5个spring,每个spring的配置都是一样的
const [trail, set, stop] = useTrail(5, () => ({ opacity: 1 }))

return (<div>
{trail.map((p, idx) => <animated.div key={idx} style={p} >哈哈</animated.div>)}
<button onClick={() => { set({ opacity: 0 }) }}>点我改变透明度</button>
</div>
)

9. useTransition动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
useTransition 可以进行过渡动画 (显示->隐藏)

//1.useTransition 可以对一组元素进行过渡动画
const [items, setItems] = useState([{ text: 'zhangsan', key: 1 }, { text: 'lisi', key: 2 }, { text: 'wangwu', key: 3 }])
//使用useTransition创建过渡动画
const transitions = useTransition(items, item => item.key, {
from: { transform: 'translate3d(0,-40px,0)' },
enter: { transform: 'translate3d(0,0px,0)' },
leave: { transform: 'translate3d(0,-40px,0)' },
})
return (<div>
<button onClick={() => { setItems(state => [{ text: 'zhaoliu', key: 12 }, ...state]) }}>增加</button>
{
transitions.map(({ item, props, key }) =>
<animated.div key={key} style={{ ...props, display: "inline-block", marginLeft: "20px" }}>{item.text}</animated.div>
)
}
</div>)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
//2.使用useTransition还可以两个组件的切换动画、以及单个组件的切换动画
const [toggle, setToggle] = useState(false)

//创建一个transition动画
const transitions = useTransition(toggle, null, {
from: { position: 'absolute', opacity: 0 },
enter: { opacity: 1 },
leave: { opacity: 0 },
})

return (<div>
<button onClick={() => { setToggle(state => !state) }}>切换</button>
{
transitions.map(({ item, key, props }) =>
<React.Fragment key={key}>
{/* {item && <animated.div key={key} style={props}>切换单个组件</animated.div>} */}
{item ? <animated.div style={props}>组件1</animated.div> : <animated.div style={props}>组件2</animated.div>}
</React.Fragment>
)
}
</div>)


//使用useTransition切换路由

//App2.jsx
import React from 'react'
import About from '@/components2/About'
import Home from '@/components2/Home'
import Search from '@/components2/Search'

import { useTransition, animated } from 'react-spring'
import { HashRouter, Route, Link, Switch, useLocation } from 'react-router-dom'

const App2 = (props) => {
//1.获取当前的location位置,useLocation这个方法必须要在被HashRouter包裹的节点/子节点中才可以使用
const location = useLocation();
//2.创建一个过渡动画
const transitions = useTransition(location, location => location.pathname, {
from: { position: 'absolute', opacity: 0, transform: 'translate3d(0,-40px,0)' },
enter: { opacity: 1, transform: 'translate3d(0,0px,0)' },
leave: { opacity: 0, transform: 'translate3d(0,-40px,0)' },
})

return (
<div>
<Link to="/">来到首页</Link>
<Link to="/about">来到About</Link>
<Link to="/search">来到Search</Link>

{/*路由切换动画*/}
{transitions.map(({ item, props, key }) => (
<animated.div key={key} style={props}>
<Switch>
<Route path='/' exact component={Home} />
<Route path='/about' component={About} />
<Route path='/search' strict component={Search} />
</Switch>
</animated.div>
))}

</div>
)
}

export default App2;


//main.js
//这边必须要用HashRouter包裹App2,以便在App2中能使用useLocation方法
ReactDom.render(<HashRouter><App2></App2></HashRouter>,document.getElementById("app"))

10. useChain使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
useChain可以创建动画链,让多个动画按照顺序执行


import React, { useState,useRef } from 'react'
import { useSpring ,useTransition,useChain, animated } from 'react-spring'
import '@/styles/demo14.css'

const Demo14 = (props) => {
//transition动画的数据源
const data = [{ name: 1, css: "red" }, { name: 2, css: "blue" }, { name: 3, css: "yellow" }, { name: 4, css: "pink" }];
//定义一个open state用于控制窗口是否被打开
const [open, setOpen] = useState(false)

//useRef()方法可以创建一个引用,这边使用useRef()创建一个spring动画的引用
const springRef = useRef()
//创建Spring动画,作用是当open为true的时候,让父盒子变大
const { size, background } = useSpring({
ref: springRef,
from: { size: '30%', background: 'hotpink' },
to: { size: open ? '100%' : '30%', background: open ? 'white' : 'hotpink' }
})

//创建一个transRef的引用
const transRef = useRef()
//创建过渡动画(当父盒子打开后,若干子盒子呈现)
const transitions = useTransition(open ? data : [], item => item.name, {
ref: transRef,
unique: true, //动画进入和离开使用相同的key
trail: 400/data.length, //动画的延迟时间
from: { opacity: 0, transform: 'scale(0)' },
enter: { opacity: 1, transform: 'scale(1)' },
leave: { opacity: 0, transform: 'scale(0)' }
})

// 创建动画链 当open是true,先执行springRef动画;否则先执行transRef动画
// 当open为true,0~0.1执行springRef动画,0.1之后执行transRef动画
// 当open为false,0~0.6执行transRef动画,0.6之后执行springRef动画
useChain(open ? [springRef, transRef] : [transRef, springRef], [0, open ? 0.1 : 0.6])

return (
<animated.div className="parent" style={{ width: size, height: size, background }} onClick={() => setOpen(open => !open)}>
{transitions.map(({ item, key, props }) => (
<animated.div className="item" key={key} style={{ ...props, background: item.css }} />
))}
</animated.div>
)
}

export default Demo14;