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

相关推荐
嘻嘻嘻嘻嘻嘻ys几秒前
《Spring Boot 3响应式架构实战:R2DBC驱动的高并发数据持久化革命》
前端·后端
滚石_stars1 分钟前
了解 CSS 的 display: inline-flex;
前端
程序员Bears1 分钟前
HTML5 新特性详解:语义化标签、表单与音视频嵌入
前端·html·html5·visual studio code
进取星辰1 分钟前
14、服务端组件:未来魔法预览——React 19 RSC实践
前端·react.js·前端框架
剽悍一小兔7 分钟前
小程序发布后,不能强更的情况下,怎么通知到用户需要去更新?
前端
115432031q7 分钟前
基于SpringBoot+Vue实现的旅游景点预约平台功能十三
java·前端·后端
JiangJiang8 分钟前
🧠 面试官:受控组件都分不清?还敢说自己写过 React?
前端·react.js·面试
tianchang8 分钟前
JS 中 Map 的概念与使用
前端·javascript
Jenlybein8 分钟前
[ Javascript 面试题 ]:提取对应的信息,并给其赋予一个颜色,保持幂等性
前端·javascript·面试
Carlos_sam9 分钟前
Opnelayers:向某个方向平移指定的距离
前端·javascript