今日需求来咯:给一个垂直排列的list组件,在变换顺序期间,有个过渡效果,最终实现像这样:
开始之前,我对比了市面上比较主流的几个工具库react-flip-move
,react-spring
,和react-motion
。
- react-flip-move 用法稍微简单一点,不过只能适用于list,grid这一类的排列类型的组件。
- react-spring 很全面的动画库,当然用起来也很复杂,有个
springConfig
,用来调节动画的参数,学习成本很高。 - react-motion 将
react-sprin
g封装后做了简化,只是用起来还是比react-flip-move
要复杂,但适合各种需求,覆盖面很广。
我这个动画是加载可拖拽list上的,因为那个draggable list是用react-dnd
实现的(《用react-dnd做一个可拖拽排列的list》),考虑到react-flip-move
能更好地和react-dnd
结合,我就选用了react-flip-move
。如果你的前端项目有很多各种各样的动画需求,还是推荐你用react-motion
或react-spring
。
react-flip-move
官方文档中的示例是用react class component实现的,这篇文章我将仿照官网中Shuffle.tsx
介绍如何用函数式组件实现,尤其是ref
的部分会不太一样。
ListItem
首先画出ListItem
组件,后期可能允许使用者传入自己的children,目前我先做出最简单的形式:
tsx
// ListItem.tsx
import React from 'react';
type Props = {
id: number;
index: number;
text: string;
}
const ListItem = (props: Props) => {
const { id, index, text } = props;
const listClass = `list-item card list`;
const style = { zIndex: 100 - index, border: '2px blue solid', height: 30 };
return (
<li id={id} className={listClass} style={style}>
{text}
</li>
);
};
export default ListItem;
SortableList
SortableList
是ListItem
的父组件,我先mock了一点数据(PETS
),然后在父组件中引用flip-move
:
tsx
// SortableList.tsx
import shuffle from 'lodash/shuffle';
import React, { useState } from 'react';
import FlipMove from 'react-flip-move';
import ListItem from './components/listItem/ListItem2';
const PETS = [
{ id: 1, name: 'dog' },
{ id: 2, name: 'cat' },
{ id: 3, name: 'fish' },
{ id: 4, name: 'hamster' },
]
const SortableList = () => {
const [pets, setPets] = useState(PETS)
const shufflePets = () => {
setPets(prev => shuffle(prev));
}
return (
<div
style={{
position: 'relative',
width: '600px',
height: '400px',
border: '2px aqua solid'
}}
>
<button onClick={shufflePets}>Shuffle</button>
<FlipMove
staggerDurationBy="30"
duration={500}
enterAnimation="accordionVertical"
leaveAnimation="accordionVertical"
typeName="ul"
>
{pets.map((pet, index) => (
<ListItem
key={pet.id}
id={pet.id}
index={index}
text={pet.name}
/>
))}
</FlipMove>
</div>
);
}
export default SortableList;
这样我们在页面上可以看到这样的list,点了shuffle按钮后,顺序就重新随机排列,这是一瞬间的变化,没有任何过渡效果。
moveListItem动画(shuffle)
于class component不同的是,在functional component中,我们需要将目标组件加上ref
,让flip-move
能get到那个element,才能加上动画。
tsx
// ListItem.tsx
import React, { forwardRef } from 'react';
type Props = {
id: number;
index: number;
text: string;
}
const ListItem = forwardRef((props: Props, ref) => { // <--- use 'forwardRef' to connect ref
const { id, index, text } = props;
const listClass = `list-item card list`;
const style = { zIndex: 100 - index, border: '2px blue solid', height: 30 };
return (
<li id={id} className={listClass} style={style} ref={ref}>
{text}
</li>
);
});
export default ListItem;
这样就实现了我们想要的效果了,是不是很简单?
延展 - FLIP
这里要注意flip-move
,顾名思义,flip是这个动画实现的本质。
f
是first
,即在一个动画周期中组件的初始状态。l
是last
,即组件的最终状态。i
是invert
,意思是反方向推断出一个变化,比如初始状态到最终状态是向下移动90px
,那么invert
是transform: -90px
,方向是反的,这样让最终状态的组件看上去是在初始状态一样。p
是play
,在transition后,invert
的效果会被removed,这样整个过程看上去像是,从first
状态到last
状态,实际上是"虚假"的first
状态(inverted from last)到last
状态。
结尾
我在上一篇文章介绍了如何通过拖拽改变list的顺序(《用react-dnd做一个可拖拽排列的list》),如果加上这篇文章介绍的动画库,我们是不是可以做一个很丝滑的draggable list啦?那么接下来的实现可以参考这一篇文章(期待中)。