FLIP 动画初步

Posted by Nicodechal on 2020-09-06

FLIP 介绍

FLIP 是 First、Last、Invert、Play 四个单词的缩写。代表了该动画技术的 4 个主要的步骤:

  • First: 记录元素原本的位置
  • Last: 记录元素完成动画后的位置
  • Invert: 计算从 Last 位置回到 First 位置的参数 ( 例如 translatexy),并将处于 Last 状态的元素移回 First 位置
  • Play: 添加动画并取消 Invert 步骤的位移使元素移动到 Last 位置

FLIP 原理

假设我们要实现一个元素插入效果,元素从前方插入,原本的元素需要向后移,这里希望元素后移时有过渡效果,而不知直接闪现。

直接实现的问题在于,元素插入前,无法直接设定原本元素的动画,因为还不知道元素插入后原本的元素会出现在哪里。这时可以使用 FLIP 的方式实现过渡效果。下面说明一下实现的关键步骤:

  1. 在上图左侧状态时,计算浅蓝色方块的位置,这里利用 getBoundingClientRect API 得到元素的 topLeft
  2. 将深蓝色元素直接插入,此时,元素还没有真正渲染,但可以通过 getBoundingClientRect 得到深蓝色元素插入以后( 即右图状态时 ) 浅蓝色元素的位置。
  3. 计算浅蓝色元素从右图位置移回左图位置分别需要在 X 轴和 Y 轴上移动多少,关闭过渡 ( 例如 transition: none ) 并使用 transform: translate(X px, Y px) 的方式将元素移回原位,此时浏览器依旧还没有渲染。但是经过 transform 后,浅蓝色元素在之后的渲染后位置不变
  4. 在下一帧渲染时 ( 通过 requestAnimationFrame 等方式实现 ),打开过渡 ( 例如 transition: all .3s ),并将元素位置重置 ( 例如 transform: translate(0, 0) ),此时元素会从原始位置移回插入之后的位置。

下图说明一个元素从左上角移动到右下角通过 FLIP 实现的过程。

具体示例

下面说明使用 FLIP 如何实现上面提到的元素插入效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const container = document.querySelector('.container');
const box = create('div', { classes: ['new-box'] });

// 记录 First 位置 (F)
const brect = container.querySelector('.box').getBoundingClientRect();

prepend(container, box); // 插入元素

// 记录 Last 位置 (L)
const arect = container.querySelector('.box').getBoundingClientRect();

// 将元素从 Last 位置移回 First 位置 (I)
const oldBox = container.querySelector('.box');
const left = brect.left - arect.left;
const top = brect.top - arect.top;
oldBox.style.transition = 'none';
oldBox.style.transform = `translate(${left}px, ${top}px)`;

// 异步执行动画 (P)
setTimeout(() => {
oldBox.style.transform = `translate(0, 0)`;
oldBox.style.transition = 'transform .3s';
}, 0);