【前端动画】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>

相关推荐
Moment3 小时前
牛逼,NextJs 从 16.3 开始全面拥抱 Agent Native 🥰🥰🥰
前端·后端·面试
沸点小助手4 小时前
6月沸点活动获奖名单公示|本周互动话题上新🎊
前端·后端
Csvn4 小时前
React 19 `use()` 来了:以后数据加载可以不用 useEffect?
前端·react.js
没落英雄4 小时前
从零开始搭建一个 AI Agent —— LangChain + TypeScript 实战手记
前端·人工智能·架构
远航_4 小时前
git submodule
前端·后端·github
摸着石头过河的石头4 小时前
从 Webpack 到 RSBuild:前端构建工具的进化之路
前端
疯狂的魔鬼4 小时前
告别 boolean 地狱:一个文件上传组件的状态机实践
前端·设计
竹林8184 小时前
Solana DApp 开发踩坑实录:从零用 @solana/web3.js 实现链上数据查询与交易签名
前端·javascript
狂师4 小时前
测试工程师的AI 技能库:推荐5个让你效率翻倍的Skills
前端·后端·测试
李明卫杭州4 小时前
Vue3 watch 与 watchEffect 深度解析
前端