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

相关推荐
BigTopOne6 小时前
WebRTC Native / Android 开发学习资源整理
前端
excel7 小时前
nuxt 3 升级 nuxt 4 报错之 hasInjectionContext
前端
何以解忧,唯有..7 小时前
LangChain 工具调用(Tool Calling)实战指南
java·前端·langchain
软件聚导航8 小时前
「聚小软 AI 助手」技术升级:从数据库检索到 RAG 知识库问答
前端·数据库·mysql·微信小程序·小程序·ai编程·rag
恋猫de小郭9 小时前
看懂大模型架构术语,帮助你理解目前常见的大模型开源架构
前端·人工智能·ai编程
yma169 小时前
通过提示词实现GitHub Pages 和 Nginx 双部署竟然这么简单?
前端
前端 贾公子9 小时前
第09章:上下文与记忆 (2)
java·服务器·前端
可乐鸡翅yeah_9 小时前
第三方接口对接 M3U8 流媒体排坑实战,解决外部服务商流兼容难题
前端·javascript·python·django·html·m3u8·m3u8在线
GoppViper9 小时前
RDF资源描述框架深度解析:语义Web的数据基石与实战逻辑
前端·数据库
郑州光合科技余经理9 小时前
本地生活服务系统:模块边界与结算字段怎么拆
java·开发语言·前端·后端·系统架构·uni-app·php