前言
最近在vue官网上看transition-group组件时发现了一个“新名词”FLIP技术,这这这是什么呀,我竟然没听过这个技术,于是乎掘金走起,哎呀,就这啊,不就一个动画技术嘛,小case啦。
<transition-group>支持通过 CSS transform 过渡移动。当一个子节点被更新,从屏幕上的位置发生变化,它会被应用一个移动中的 CSS 类 (通过nameattribute 或配置move-classattribute 自动生成)。如果 CSStransformproperty 是“可过渡”property,当应用移动类时,将会使用 FLIP 技术使元素流畅地到达动画终点。
什么是FLIP
FLIP可以理解为对应四个单词:first、last、invert、play,四个状态。
first
动画的初始状态,可以理解为记录当前的元素的位置、尺寸等。
last
动画的结束状态,元素发生了变化,记录元素在最后状态的位置、尺寸等。
invert
计算初始状态和结束状态的差值。这里需要理解一个知识点:DOM 元素属性的改变(比如 left、right、 transform 等等),会被集中起来延迟到浏览器的下一帧统一渲染,这样我们就可以在结束状态利用transform属性将元素移动到初始状态的位置。
play
播放动画,再利用transform属性将元素移动到结束状态。
实现
<template>
<div class="flip-wrap">
<button @click="add">add</button>
<button @click="deleteItem">delete</button>
<ul class="box" ref="box">
<transition-group name="fade" mode="out-in">
<li class="item" v-for="item in list" :key="item.title">
{{item.title}}
</li>
</transition-group>
</ul>
</div>
</template>
data(){
return{
list: [{
title: 1,
}]
}
},
<style>
.flip-wrap {
margin-top: 100px;
}
.item {
display: inline-block;
width: 200px;
height: 260px;
border-radius: 4px;
box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.2);
text-align: center;
font-size: 16px;
margin: 5px;
background-color: #fff;
}
</style>
获取元素的初始状态
first(){
for(let i=0; i<this.$refs.box.children.length; i++){
const dom = this.$refs.box.children[i];
const bcr = dom.getBoundingClientRect();
dom.startX = bcr.left;
dom.startY = bcr.top;
}
},
获取元素的结束状态
last(){
for(let i=0; i<this.$refs.box.children.length; i++){
const dom = this.$refs.box.children[i];
const bcr = dom.getBoundingClientRect();
const currentX = bcr.left;
const currentY = bcr.top;
this.invert(dom, currentX, currentY);
}
},
计算状态差值
invert(dom, currentX, currentY){
const translateX = dom.startX - currentX;
const translateY = dom.startY - currentY;
this.play(dom, translateX, translateY);
},
播放动画
play(dom, translateX, translateY){
dom.animate([{
transform: `translate(${translateX}px, ${translateY}px)`,
}, {
transform: `translate(0px, 0px)`,
}], {
duration: 400,
})
},
其他逻辑
change(type){
switch (type){
case "delete":
this.delete();
break
default:
this.add();
}
},
add(){
this.list.unshift({
title: Math.ceil(Math.random()*1000),
})
},
delete(){
this.list.splice(Math.floor(Math.random()*this.list.length), 1)
},
// 调用顺序
clickHandler(type){
this.first();
this.change(type);
this.$nextTick(() => {
this.last();
})
}
效果
使用transition-group组件来实现
既然vue官网也说了,transition-group组件可以使用FLIP技术,那为啥不使用transition-group呢,接下来就使用transition-group组件实现相同的功能,看看和上述写法有啥区别。
<template>
<div class="flip-wrap">
<button @click="add">add</button>
<button @click="deleteItem">delete</button>
<ul class="box" ref="box">
<transition-group name="fade" mode="out-in">
<li class="item" v-for="item in list" :key="item.title">
{{item.title}}
</li>
</transition-group>
</ul>
</div>
</template>
data(){
return{
list: [{
title: 1,
}]
}
},
<style>
.flip-wrap {
margin-top: 100px;
}
.item {
display: inline-block;
width: 200px;
height: 260px;
border-radius: 4px;
box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.2);
text-align: center;
font-size: 16px;
margin: 5px;
background-color: #fff;
transition: all 1s;
}
.fade-enter {
opacity: 0;
transform: translateX(-100px);
}
.fade-leave {
opacity: 0;
}
.fade-leave-active {
position: absolute;
}
</style>
操作逻辑
add(){
this.list.unshift({
title: Math.ceil(Math.random()*1000),
})
},
deleteItem(){
this.list.splice(Math.floor(Math.random()*this.list.length), 1)
},
好了,没了,使用transition-group组件就是这么简单,其实都是人家已经帮我们封装好了,这里我们主要写一下动画的css就完事了。
效果
总结
现在是不是对FlIP有种似曾相识的感觉,或许你以前没听过这个专业名词,但是在实际开发中或多或少都使用过了这相关的技术,FLIP只是把一个动画进行了规范化、标准化,我们只需要按照FLIP的开发模式就可以轻松实现一个标准动画了。