【前端动画】FLIP 动画原则

FLIP 是前端实现高性能布局动画的核心原则,通过先计算、后反向、再播放,把昂贵的布局重排转为 GPU 友好的 transform 动画,解决列表重排、卡片展开、视图切换等场景的卡顿问题。


一、FLIP 是什么

FLIP 是 First / Last / Invert / Play 的缩写,是一套标准化的动画实现流程。

二、核心原理(一句话)

先让元素瞬间跳到最终布局 ,再用 transform 反向拉回起点,最后播放 transform 回到 0 的过渡------全程只触发合成(Composite),不触发重排(Layout)。

三、四步详解(代码逻辑)

1. First(记录初始态)

读取元素动画前的位置、宽高、偏移 等几何信息(getBoundingClientRect)。

javascript 复制代码
// 记录初始位置
const first = el.getBoundingClientRect();
2. Last(更新到最终态)

先修改 DOM/样式(如排序、展开、切换),让浏览器完成布局计算,再读取最终几何信息。

javascript 复制代码
// 触发布局变化(如列表重排、添加/删除元素)
list.appendChild(newItem);
// 记录最终位置
const last = el.getBoundingClientRect();
3. Invert(反向偏移)

计算初始与最终的差值,用 transform 把元素"拉回"初始位置,视觉上看起来没动

javascript 复制代码
// 计算偏移量
const deltaX = first.left - last.left;
const deltaY = first.top - last.top;
const deltaW = first.width / last.width;
const deltaH = first.height / last.height;

// 应用反向 transform
el.style.transform = `translate(${deltaX}px, ${deltaY}px) scale(${deltaW}, ${deltaH})`;
4. Play(播放动画)

在下一帧(requestAnimationFrame)移除反向 transform,开启 transition,让元素平滑过渡到最终态。

javascript 复制代码
// 启用过渡
el.style.transition = 'transform 0.3s ease';
// 下一帧清除反向 transform
requestAnimationFrame(() => {
  el.style.transform = 'none';
});

四、为什么 FLIP 性能更好

  • 避免重排(Layout) :直接修改 top/left/width/height 会触发全页面重排,成本极高。
  • 只走合成(Composite)transform/opacity 由 GPU 处理,不影响布局树,帧率更稳。
  • 批量处理:一次计算所有元素差值,统一动画,减少浏览器开销。

五、适用场景

  • 列表拖拽排序、增删动画
  • 卡片展开/折叠、模态框切换
  • 网格布局重排、视图切换
  • 任何需要位置/尺寸平滑过渡的场景

六、关键优化点

  • will-change: transform 提前提示浏览器优化。
  • 动画期间不触发重排 (避免读取 offsetTopclientWidth 等)。
  • 时长建议 200--500ms,兼顾流畅与感知。
  • 复杂场景可用 GSAP Flip 插件简化代码。

七、完整示例(列表重排)

html 复制代码
<ul class="list">
  <li class="item">1</li>
  <li class="item">2</li>
  <li class="item">3</li>
</ul>
<button onclick="shuffle()">打乱</button>

<script>
function shuffle() {
  const list = document.querySelector('.list');
  const items = Array.from(list.children);
  
  // 1. First:记录初始位置
  const firstRects = items.map(el => el.getBoundingClientRect());
  
  // 2. Last:打乱 DOM(触发布局)
  items.sort(() => Math.random() - 0.5);
  items.forEach(el => list.appendChild(el));
  
  // 3. Invert:反向偏移
  items.forEach((el, i) => {
    const first = firstRects[i];
    const last = el.getBoundingClientRect();
    const dx = first.left - last.left;
    const dy = first.top - last.top;
    el.style.transform = `translate(${dx}px, ${dy}px)`;
  });
  
  // 4. Play:播放动画
  requestAnimationFrame(() => {
    items.forEach(el => {
      el.style.transition = 'transform 0.3s ease';
      el.style.transform = 'none';
    });
  });
}
</script>

相关推荐
赵_叶紫2 小时前
Kubernetes 从入门到实践
前端
阿珊和她的猫2 小时前
深入解析浏览器的渲染过程
前端·javascript·vue.js
匠心网络科技2 小时前
JavaScript进阶-ES6 带来的高效编程新体验
开发语言·前端·javascript·学习·面试
Never_Satisfied2 小时前
在HTML & CSS中,nth-child、nth-of-type详解
前端·css·html
睡不着的可乐3 小时前
createElement → VNode 是怎么创建的
前端·javascript·vue.js
光影少年3 小时前
前端css如何实现水平垂直居中?
前端·javascript·css
C澒3 小时前
SLDS 自营物流系统:Pickup 揽收全流程
前端·架构·系统架构·教育电商·交通物流
摸鱼的春哥3 小时前
把白领吓破防的2028预言,究竟讲了什么?
前端·javascript·后端
infiniteWei3 小时前
SKILL.md 触发机制与设计规范:避免“写了不触发”
java·前端·设计规范