FLIP动画

介绍

FLIP(First last Invert Play) 是一种实现动画的思路。只利用transformopacity模拟布局变动的技巧,不触发Layout,相比使用topleft来做动画有较好的性能,当有多个元素触发动画时不会卡顿。

html 代码结构

html 复制代码
<div class="test">test</div>
<div class="list">
  <div class="item">1</div>
  <div class="item">2</div>
  <div class="item">3</div>
  <div class="item">4</div>
</div>

First 记录要监控的元素位置

这里要改变 item2 的位置,所以先记录下 item2 的位置信息,这里记录 item2 距离视口的位置:x, y

js 复制代码
const list = document.querySelector(".list");
const item2 = list.children[1];

function FLIP() {
  const { x: startX, y: startY } = item2.getBoundingClientRect();
  ...
}

Last 记录监控的元素变化后位置

如果此时在第二行打断点,可以发现 item2 还没有被插入到末尾,但是可以获取到其在末尾时的位置信息。这是因为 getBoundingClientRect 函数触发了浏览器的回流,使浏览器重新计算了元素位置,而此时主线程还被js代码占用,所以处于渲染线程重绘还没开始,就导致上述所说情况。

js 复制代码
list.insertBefore(item2, null); // 第二个参数为 null 时,元素会被插入到末尾
const { x: endX, y: endY } = item2.getBoundingClientRect();

Invert 移动元素到 First 的位置

js 复制代码
item2.style.transform = `translate(${startX - endX}px, ${startY - endY}px)`;

Play 使用动画还原元素到 Last 的位置

transform 属性被删除时,item2 会回到 Last 的位置

js 复制代码
requestAnimationFrame(() => {
  item2.style.transition = "transform 1s";
  item2.style.removeProperty("transform");
})

动画效果

js 复制代码
const list = document.querySelector(".list");
const item2 = list.children[1];

function FLIP() {
  const { x: startX, y: startY } = item2.getBoundingClientRect();
  list.insertBefore(item2, null);
  const { x: endX, y: endY } = item2.getBoundingClientRect();
  item2.style.transform = `translate(${startX - endX}px, ${startY - endY}px)`;

  requestAnimationFrame(() => {
    item2.style.transition = "transform 1s";
    item2.style.removeProperty("transform");
  });
}

const test = document.querySelector(".test");
test.addEventListener("click", FLIP);

可以看到只有 item2 元素应用了动画效果,其他元素也想应用动画的话,就得一个个设置,所以得改造下 FLIP 函数。

完整代码

ts 复制代码
class FLIP {
  constructor(list, duration = 0.5) {
    this.duration = duration;
    this.list = [...list];
    this.positionMap = new WeakMap();
    this.list.forEach((item) => {
      const { x, y } = item.getBoundingClientRect();
      this.positionMap.set(item, [x, y]);
    });
  }

  play() {
    this.list.forEach((item, index) => {
      item.style.removeProperty("transition");
      const position = this.positionMap.get(item);
      if (!position) return;
      const [startX = 0, startY = 0] = position;
      const { x: endX, y: endY } = item.getBoundingClientRect();
      item.style.transform = `translate(${startX - endX}px, ${startY - endY}px)`;
      this.positionMap.set(item, [endX, endY]);
      this.raf(() => {
        item.style.transition = `transform ${this.duration}s ease-out`;
        item.style.removeProperty("transform");
      });
    });
  }

  raf(fn) {
    requestAnimationFrame(() => {
      requestAnimationFrame(fn.bind(this));
    });
  }
}


const list = document.querySelector(".list");
const flip = new FLIP(list.children);
const test = document.querySelector(".test");
test.addEventListener("click", move);

function move() {
  const item2 = list.children[1];
  list.insertBefore(item2, null);
  flip.play();
}

改造完后只需要在 new FLIP 时传入要监控的元素列表即可,在元素位置发生变化后调用 flip.play(),即可对所有元素应用动画。

相关推荐
程序员黑豆3 小时前
Java类型推断完全指南:从var到菱形运算符,掌握使用限制与最佳实践
java·前端·ai编程
To_OC4 小时前
踩了个 TS 的坑之后,我终于把 type 和 interface 掰明白了
前端·react.js·typescript
GreenTea4 小时前
深度解读 Anthropic 多智能体报告:更强的模型 ≠ 更好的协调
前端·后端·算法
浮生望4 小时前
前端API工程化:用 Mock 数据与 Axios 配置实现独立于后端的并行开发
前端
万少5 小时前
给 DeepSeek Harness 装个"应用商店":一条命令,595 个插件随你逛
前端·javascript·后端
波波0075 小时前
C# 15 重磅新特性: 带标签 break 与 continue:重新定义嵌套循环控制流
服务器·前端·c#
Brown.alexis6 小时前
es6知识点1-自备使用
前端·ecmascript·es6
小爬的老粉丝7 小时前
JavaScript 纯前端预览 WPS:先识别容器,再路由解析器
前端·javascript·wps
计算机魔术师7 小时前
新兴多智能体系统的模式与问题
前端
用户059540174468 小时前
AI Agent 上下文污染踩坑实录:用 pytest + Redis 揪出 3 类记忆串号 bug,排查 6 小时
前端·css