使用 FLIP 技术实现流畅的动画

FLIP 是一个用于创建高性能、流畅的动画的技术。它代表 First, Last, Invert, Play,这四个步骤是创建动画的关键。在本文中,我们将探讨如何使用 FLIP 技术为一个元素实现宽度变化的动画。

1. FLIP 是什么?

FLIP 是一种动画优化技术,它的核心思想是利用浏览器的硬件加速特性来实现流畅的动画效果。FLIP 的四个步骤如下:

  • First: 获取元素的初始位置和尺寸。
  • Last: 在进行任何动画之前,获取元素的最终位置和尺寸。
  • Invert: 计算初始和最终状态之间的差异,并设置一个“反转”变换,使元素看起来像它还在原始位置。
  • Play: 移除“反转”变换,使元素动画地移动到其最终位置。

2. 示例代码

我们有一个红色的方块,当点击它时,我们希望它的宽度从100px增加到400px。为了实现这一效果,我们使用了 FLIP 技术:

function flipAnimation(element) {
    // First
    const first = element.getBoundingClientRect();

    // Manipulate the element
    element.style.width = '400px';
    element.style.transformOrigin = `left`;

    // Last
    const last = element.getBoundingClientRect();

    // Invert
    const deltaX = first.left - last.left;
    const deltaY = first.top - last.top;
    const deltaW = first.width / last.width;
    const deltaH = first.height / last.height;

    element.style.transform = `
        translate(${deltaX}px, ${deltaY}px) 
        scale(${deltaW}, ${deltaH})
    `;
    // 以上都是设置前一帧的状态
    // Play (给下一帧应用样式)
    requestAnimationFrame(() => {
        element.style.transition = 'transform 3s';
        element.style.transform = '';
    });
}

3. 解释

  • First: 我们首先获取元素的初始位置和尺寸。

  • Last: 在更改元素的宽度后,我们再次获取其位置和尺寸。这是我们希望元素在动画结束时所在的位置。

  • Invert: 我们计算了初始和最终状态之间的差异,并设置了一个“反转”变换。这使得元素看起来仍然在其原始位置。

  • Play: 使用 requestAnimationFrame 确保在下一个渲染周期中应用动画。我们移除了“反转”变换,使元素动画地移动到其最终位置。

4. 结论

FLIP 是一种强大的动画技术,它可以帮助我们创建流畅、高性能的动画效果。通过简单地计算初始和最终状态之间的差异,并使用 CSS 变换来实现动画,我们可以避免导致页面重绘的昂贵操作,从而实现流畅的用户体验。

举个栗子

代码

Flip.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="box" style="width: 100px; height: 100px; background-color: red;"></div>
</body>
<script src="./Flip.js"></script>
</html>

Flip.js

function flipAnimation(element) {
    // First
    const first = element.getBoundingClientRect();

    // Manipulate the element, e.g., change its position, scale, etc.
    element.style.width = '400px';
    element.style.transformOrigin = `left`;
    // ...

    // Last
    const last = element.getBoundingClientRect();

    // Invert
    const deltaX = first.left - last.left;
    const deltaY = first.top - last.top;
    const deltaW = first.width / last.width;
    const deltaH = first.height / last.height;

    element.style.transform = `
        translate(${deltaX}px, ${deltaY}px) 
        scale(${deltaW}, ${deltaH})
    `;

    // Play
    requestAnimationFrame(() => {
        element.style.transition = 'transform 3s';
        element.style.transform = '';
    });
}

const box = document.getElementById('box');
box.addEventListener('click', () => {
    flipAnimation(box);
});

如何理解

元素的初始状态是A,我们想让他以动画的形式过渡到状态B。则我们在First这一步给元素设置使其变为B的样式(最好不要用transform来改变),然后在Last这一步获取到已经变为B的状态的元素(因为浏览器的渲染机制,目前还未绘制到屏幕上),之后在Invert这一步给其添加transform样式使其恢复到初始状态A(到此为止,都是同一渲染帧的样子,看起来元素一直是在初始状态,我们记为第一帧)。

之后,在最后的Play这一步中,调用requestAnimationFrame 方法,使我们能够在下一帧应用新的样式,来给元素设置我们想要展示出来的过渡效果(如transition: transform 3s ease),并把Invert中添加的transform效果移除(比如设置transform: none)。这样,就能使元素从A状态过渡到B状态,在这期间不会导致新的重排或重新渲染,并且能够通过transform的过渡效果充分利用GPU的性能来渲染动画,使动画表现足够流畅。

效果

> 本地查看演示视频 <


A Student on the way to full stack of Web3.