FLIP动画

介绍

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

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(),即可对所有元素应用动画。

相关推荐
康小汪4 小时前
AI 写 Vue 总写成同一个味道:给它装 Skills,装的不是知识是边界
前端·vue.js·人工智能·ai编程·最佳实践
智能运维指南5 小时前
研制过程“两张皮“怎么破:研制一体化支撑平台的定位、能力边界与落地前提
java·前端·人工智能·嘉为蓝鲸
白山编程大哥6 小时前
Java 后端如何优雅地把 List 集合返回给前端
java·前端·list
Flynt7 小时前
我把热榜那篇"禁用 axios"逐条实测了一遍:结论大体对,坑在它没讲的地方
前端·javascript·axios
Csvn8 小时前
bundle优化
前端
北冥有鱼8 小时前
发版后动态路由点击报 `Failed to fetch dynamically imported module`」问题上的完整排查与修复过程
前端·vue.js
静默回滚8 小时前
视频压到 25 MB 以内,码率要算、音轨要扣、余量要留
前端
用户37203749260188 小时前
把 10MB 中文语义模型塞进浏览器:STSB 做到教师模型的 93%,但线上 top1 只有 42%
前端
光锥智能9 小时前
海康机器人举行2026移动机器人新品发布会:箱式存拣升级,「明智」系列发布
java·前端·机器人
计算机魔术师9 小时前
Anthropic 上市文件最吓人的一幕,不是亏损 807 亿
前端