当前位置:网站首页>FLIP动画实现思路
FLIP动画实现思路
2022-06-13 01:07:00 【馒头老爸】
如果让你实现下面的这种动画效果你会怎么做?
可能很多人第一想法就是使用绝对定位进行布局,当顺序发生变化后,计算出变化后的位置,然后通过动画过渡到指定位置。这是一种很常见的实现方式,但存在几个问题:
- 需要维护每个节点的位置信息
- 顺序变化后,需要计算每个DOM的目标位置
- 使用绝对定位的方式,每行显示的小方块个数是固定的,不能自适应容器的变化。
过程分析
无论多么复杂的动画,都可以拆解成多个动画的组合。对于上面的效果,就可以看成是每个小方块的变化,这里只涉及到了位置的变化,当然还可能存在大小、颜色等变化。
从整个动画过程提取几个最重要的信息:
- 开始状态信息:黄绿色、宽50px、高50px、位置坐标0,0
- 结束状态信息:橙色、宽50px、高50px、位置坐标100,0
- 发生变化的属性:
- 颜色:黄绿色=》橙色
- 位置:left 0 => 100
要实现上面的过渡效果,我们通常可以使用 animation
、transition
和 requestAnimationFrame
来实现。下面就用代码来实现上面的效果。
animation
@keyframes identifier {
from {
left:100px;
background: yellowgreen;
}
to {
left:400px;
background: rgb(255, 123, 0);
}
}
.animation-dom {
position: absolute;
display: inline-block;
height: 50px;
width: 50px;
background: yellowgreen;
animation: identifier 3s infinite;
-webkit-animation:identifier 3s infinite;
}
transition
使用 transition
设置属性变化时具有过渡效果,为开始和结束状态创建两个不同的类名,分别设置其状态的属性。通过改变类名来实现过渡效果。
.transition-dom {
transition: all 1s;
&.start {
left: 0;
background: yellowgreen;
}
&.end {
left: 100px;
background: rgb(255, 123, 0);
}
}
requestAnimationFrame
多用于受控属性的控制,如位置、大小等信息。这种方式不太适用于一般的动画效果开发,需要自己去计算每个时刻的状态,并且对颜色这种过度无能为力。
FLIP
FLIP
分别是 First
、Last
、Invert
、 Play
四个单词的缩写;
First
元素的起始状态,例如位置、大小、形状、颜色等信息
Last
元素运动后的终止状态
Invert
元素的变化过程,也就是最终状态相对于其实状态,有哪些属性发生了改变。例如:位置向右移动了100px,颜色从黄色变为了橙色。将元素所有发生了变化的属性全部统计出来。
Play
执行动画过程,将所有发生变化的属性,从其实状态过渡到结束状态。可以设置过渡的时间、过渡方式等。可以通过上述的几种方式来实现这个过程。
实现思路
为了使得我们的动画灵活性更高,开发成本更低,首先就排除掉使用 绝对定位
的方式。如果不通过计算的方式,我们如何知道动画结束时元素的属性呢?
这里就要提出一个很重要的知识点:DOM元素的属性发生变化时,会被集中收集到浏览器的下一帧进行统一渲染。也就是说会存在这么一个时间段,DOM的元素属性已经发生改变,而浏览器还没来得及渲染,此时我们依然是可以拿到DOM更新后的属性的。
知道了元素的终止状态,就可以来实现过渡动画了。最好是使用 animation
的方式来实现,好处是不会在DOM元素上添加任何的 CSS。
具体实现
以 React
为例
// 以此列表进行循环渲染的数据源
const [dataList, setDataList] = useState<any[]>([0, 1, 2, 3, 4, 5]);
// 容器元素
const wrapperRef = useRef<HTMLDivElement>(null);
return (
<div className='flip-demo'>
<Space>
<Button>
新增
</Button>
<Button>
乱序
</Button>
</Space>
<div className='list' ref={wrapperRef}>
{dataList.map(item => (
<div
key={item}
className="item"
>
{item}
</div>
))}
</div>
</div>
);
.list {
display: flex;
flex-wrap: wrap;
width: 550px;
}
.item {
display: flex;
align-items: center;
justify-content: center;
width: 80px;
height: 80px;
border: 1px solid #eee;
}
第一步:记录元素的起始状态
将每个元素的起始状态进行记录保存,这里建议使用 Map 来进行存储,有两个好处:
- 方便取值,不像数组那样必须要保证顺序。
- 可以使用 DOM 节点作为 KEY 值,即使 DOM 的属性发生了变化,DOM 的引用是不会变的。
// 用于存储最后一次状态
const lastRectRef = useRef<Map<HTMLElement, DOMRect>>(new Map());
// 将容器下面所有的子元素存储到MAP中
function createChildrenElementMap(wrapperNode: HTMLElement | null) {
if (!wrapperNode) {
return new Map();
}
// 获取到所有的子元素
const childNodes = Array.from(wrapperNode.childNodes) as HTMLElement[];
// 原元素作为KEY,其属性值作为VALUE
const result = new Map(childNodes.map(node => [node, node.getBoundingClientRect()]));
return result;
}
// 只要 dataList 发生了变化,就需要更新状态
useEffect(() => {
const currentRectMap = createChildrenElementMap(wrapperRef.current);
lastRectRef.current = currentRectMap;
}, [dataList]);
知识点: 一个n * 2 的二位数组,将其转为 Map 时,数组的 arr[n][0]
会作为Map 的key , arr[n][1]
为相应的值。
第二步:实现新增和乱序功能
新增和乱序比较简单,就是改变数据源而已。
import { shuffle } from 'lodash';
// 乱序,使用lodash的shuffle方法
function handleShuffle() {
setDataList(shuffle);
}
// 添加
function addItem() {
setDataList((list) => [list.length, ...list]);
}
第三步:获取最终状态,计算变化属性值,执行动画
本案例中我们明确知道只有元素的位置信息发生了变化。
useLayoutEffect(() => {
// 这里获取到的是最新的状态信息,也就是最终状态
const currentRectMap = createChildrenElementMap(wrapperRef.current);
// 对保存的上次状态的元素进行遍历
lastRectRef.current.forEach((prevNode, node) => {
// 由于DOM的属性变化后其引用是不会发生改变的,因此可以在currentRectMap中获取到其最终状态
const currentRect = currentRectMap.get(node);
// 计算位置信息的变化
const invert = {
left: prevNode.left - currentRect?.left,
top: prevNode.top - currentRect?.top,
};
// 设置动画过程
const keyframes = [
{
transform: `translate(${invert.left}px, ${invert.top}px)`
},
{
transform: `translate(0, 0)`
}
];
// 执行动画
node.animate(keyframes, {
duration: 800,
easing: 'cubic-bezier(0.25, 0.8, 0.25, 1)',
})
});
lastRectRef.current = currentRectMap;
}, [dataList]);
动画调试
我们在开发动画时,为了追求更好的过渡效果,就需要更加深入的分析动画的整个过程。但往往动画的过程都是比较快的,肉眼很难看到细节。
既然太快了,我们就把动画的时间调慢些,慢到我们可以看到为止。这种方式的确很直接很实用,但是需要手动的去修改代码,调试完成后还需要手动改回来。
浏览器的开发者工具支持动画的调试,具体位置如下图:
点击 10%
可以将动画的过程放慢10倍,并且可以在下方看到所有发生了动画的元素。
点击快照列表可以重复执行动画,在界面中也会重新执行。
你甚至可以拖动下面的时间线,来单独控制某个元素的执行时间:
下载此文:https://www.dengzhanyong.com/resource
个人网站:www.dengzhanyong.com
关注公众号【前端筱园】,不错过每一篇文章
边栏推荐
- Andersen global expands its business in northern Europe through cooperation agreements in Finland and Denmark
- Pytorch's leafnode understanding
- sort
- How to choose stocks? Which indicator strategy is reliable? Quantitative analysis and comparison of strategic returns of vrsi, bbiboll, WR, bias and RSI indicators
- MySQL transaction
- 五篇经典好文,值得一看(2)
- Leetcode-12- integer to Roman numeral (medium)
- Leetcode-78- subset (medium)
- Rest at home today
- Rotating camera
猜你喜欢
Leetcode-11- container with the most water (medium)
軟件測試的幾種分類,一看就明了
[Latex] 插入圖片
[JS component] custom paging
Unity calls alertdialog
408 true question - division sequence
Introduction to ROS from introduction to mastery (zero) tutorial
leetcode 142. Circular linked list II
数学知识整理:极值&最值,驻点,拉格朗日乘子
Alexnet实现Caltech101数据集图像分类(pytorch实现)
随机推荐
3623. Merge two ordered arrays
Five classic articles worth reading (2)
Status of the thread
(01). Net Maui actual construction project
Deep learning model pruning
[JS component] create a custom horizontal and vertical scroll bar following the steam style
What is the difference between pytorch and tensorflow?
五篇经典好文,值得一看
Unity extension
The seventh finals of the Blue Bridge Cup
Common skills of quantitative investment - index part 2: detailed explanation of BOL (Bollinger line) index, its code implementation and drawing
gpu加速pytorch能用吗?
[North Asia server data recovery] data recovery case of Hyper-V service paralysis caused by virtual machine file loss
Application advantages of 5g industrial gateway in coal industry
Aof persistence
Three column simple Typecho theme lanstar/ Blue Star Typecho theme
Undirected graph -- computing the degree of a node in compressed storage
Most elements leetcode
5G工业网关在煤矿行业的应用优势
单片机串口中断以及消息收发处理——对接受信息进行判断实现控制