-
Notifications
You must be signed in to change notification settings - Fork 0
FLIP Animation
menglingyu.213 edited this page Mar 15, 2026
·
1 revision
FLIP 是这个库实现平滑过渡动画的核心技术。
你想让一个元素从位置 A 平滑移动到位置 B:
位置 A 位置 B
┌─────────┐ ┌─────────────────┐
│ 300x200 │ ───── 动画 ─────► │ 600x400 │
│ (100,200)│ │ (400,50) │
└─────────┘ └─────────────────┘
直觉做法是用 JavaScript 算每一帧的中间值。但这样性能差——JS 计算 + 布局重排,每帧都在主线程上跑。
FLIP 的思路完全反过来:让浏览器用 GPU 加速的 CSS 来做动画。
const first = element.getBoundingClientRect();
// { left: 100, top: 200, width: 300, height: 200 }直接把元素放到最终位置(不需要动画,瞬间完成):
targetContainer.appendChild(element);
const last = element.getBoundingClientRect();
// { left: 400, top: 50, width: 600, height: 400 }计算差值,用 CSS transform 把元素"拉回"起始位置:
const deltaX = first.left - last.left; // 100 - 400 = -300
const deltaY = first.top - last.top; // 200 - 50 = 150
const deltaW = first.width / last.width; // 300 / 600 = 0.5
element.style.transform = `translate(${deltaX}px, ${deltaY}px) scale(${deltaW})`;
// 视觉上元素还在原位element.style.transition = 'transform 0.3s ease';
element.style.transform = ''; // 回到真实位置
// 浏览器自动做 GPU 加速的补间动画!标准 FLIP 用 transform,但这个库用的是 position: fixed + left/top/width/height 过渡。原因是:
-
元素需要脱离原容器 — 动画期间元素不属于任何容器,
fixed定位最合适 -
尺寸要真的变 —
transform: scale只是视觉缩放,不会改变元素实际布局尺寸,而播放器的控件需要按真实尺寸排列
实际流程:
1. 记录元素当前 rect { left:100, top:200, w:300, h:200 }
2. 从容器中取出,放到 <body>
3. position: fixed 在原位 left:100px, top:200px, w:300px, h:200px
4. 算出目标容器的 rect { left:400, top:50, w:600, h:400 }
5. 加 CSS transition
6. 改 left/top/width/height 到目标值 浏览器自动动画
7. transitionend 事件触发后
8. 去掉 fixed,appendChild 到目标容器
代码里有一行看起来奇怪的:
element.offsetHeight; // 强制重排这是必需的。浏览器会把多次样式修改合并成一次重排。如果你先设 left: 100px 再立即改成 left: 400px,浏览器只会看到最终值 400px,不会有动画。
element.offsetHeight 强制浏览器在这个点做一次布局计算,相当于告诉它"把当前的 left: 100px 先画出来",然后再改成 400px 才会触发 transition。
transitionend 事件有时不触发(比如元素不可见、transition 属性无效、或者被其他样式覆盖)。所以有一个超时兜底:
const fallbackTimer = setTimeout(settle, 3000);
element.addEventListener('transitionend', () => {
clearTimeout(fallbackTimer);
settle();
}, { once: true });3 秒内如果 transitionend 没触发,强制完成。不至于让元素永远浮在页面上。