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

相关推荐
福兮说1 小时前
canvas 旋转图片的六个坑:四角被裁、Math.ceil 多出 1px 黑边、JPG 角发黑、翻转方向反了
前端·javascript·图像处理·canvas
传人once3 小时前
多图片上传预览功能如何写
前端·css·html·jquery·html5
Eric.463 小时前
Wan‑Animate+MiniMax-H3 本地部署 AI 漫剧流水线:8G 显存动作迁移与动态分镜工程实战
前端·人工智能·comfyui·ai漫剧
库拉镜像AI牛牛4 小时前
短剧内容自动化生产:知漫剧工作室落地教程
大数据·服务器·前端·人工智能·语音识别
律宏阔5 小时前
Flutter 列表图片内存优化:CachedNetworkImage 与 ClipRRect 的取舍
前端·flutter
行者全栈架构师5 小时前
【鸿蒙心迹】相册文搜图工程化落地:HarmonyOS 7.0 端侧 AI 检索全链路实战与 5 个踩坑解法
前端
律宏阔5 小时前
Dart 3 Record:解决 Future.wait 异构返回值的类型问题
前端·flutter
Seraphina365 小时前
DVWA(XSS Reflected/Stored High,CSRF Low)
前端·xss·csrf
默_笙6 小时前
🍔 中间件不只是打日志:四个钩子、一次短路,和自带的工具
前端·javascript
程序员老赵6 小时前
Docker 部署 Dolibarr:轻松搭建开源 ERP/CRM 平台
运维·前端·后端