什么?你竟然不知道FLIP动画!

1,739 阅读2分钟

前言

最近在vue官网上看transition-group组件时发现了一个“新名词”FLIP技术,这这这是什么呀,我竟然没听过这个技术,于是乎掘金走起,哎呀,就这啊,不就一个动画技术嘛,小case啦。

<transition-group> 支持通过 CSS transform 过渡移动。当一个子节点被更新,从屏幕上的位置发生变化,它会被应用一个移动中的 CSS 类 (通过 name attribute 或配置 move-class attribute 自动生成)。如果 CSS transform property 是“可过渡”property,当应用移动类时,将会使用 FLIP 技术使元素流畅地到达动画终点。

什么是FLIP

FLIP可以理解为对应四个单词:first、last、invert、play,四个状态。

first

动画的初始状态,可以理解为记录当前的元素的位置、尺寸等。

last

动画的结束状态,元素发生了变化,记录元素在最后状态的位置、尺寸等。

invert

计算初始状态和结束状态的差值。这里需要理解一个知识点:DOM 元素属性的改变(比如 leftrighttransform 等等),会被集中起来延迟到浏览器的下一帧统一渲染,这样我们就可以在结束状态利用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();
    })
}

效果

flip1.gif

使用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就完事了。

效果 flip2.gif

总结

现在是不是对FlIP有种似曾相识的感觉,或许你以前没听过这个专业名词,但是在实际开发中或多或少都使用过了这相关的技术,FLIP只是把一个动画进行了规范化、标准化,我们只需要按照FLIP的开发模式就可以轻松实现一个标准动画了。