【前端动画】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 提前提示浏览器优化。
  • 动画期间不触发重排 (避免读取 offsetTop、clientWidth 等)。
  • 时长建议 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>

相关推荐
Lvan的前端笔记3 小时前
docker:每个前端项目一个 Nginx 容器还是只有一个Nginx容器
前端·nginx·docker
三天不学习3 小时前
Egg.js 4 突然爆火,原因是否归结于AI 原生落地需求爆发?
前端·javascript·全栈·egg.js
xcs194054 小时前
前端 vue 的前端页面debugger 进不去
前端·javascript·vue.js
明月_清风5 小时前
Deno 终局来了:从挑战 Node 到被 Cloudflare 收编
前端·后端·node.js
Csvn6 小时前
框架性能优化
前端
回眸&啤酒鸭7 小时前
【回眸】OpenSwarm 多智能体协作系统实战指南
大数据·前端·人工智能
用户69371750013848 小时前
2026,程序员的时代拐点到了
android·前端·后端
大龄秃头程序员8 小时前
一次 iBeacon + BLE 无感解锁方案的实现记录
前端
小兔子8 小时前
Python 的 GIL 与 free-threading:3.13 之后「去 GIL」走到哪一步了
前端
IT_陈寒8 小时前
SpringBoot自动配置差点让我加班到凌晨
前端·人工智能·后端