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

相关推荐
zwjapple4 小时前
docker-compose一键部署全栈项目。springboot后端,react前端
前端·spring boot·docker
像风一样自由20206 小时前
HTML与JavaScript:构建动态交互式Web页面的基石
前端·javascript·html
aiprtem7 小时前
基于Flutter的web登录设计
前端·flutter
浪裡遊7 小时前
React Hooks全面解析:从基础到高级的实用指南
开发语言·前端·javascript·react.js·node.js·ecmascript·php
why技术7 小时前
Stack Overflow,轰然倒下!
前端·人工智能·后端
GISer_Jing7 小时前
0704-0706上海,又聚上了
前端·新浪微博
止观止7 小时前
深入探索 pnpm:高效磁盘利用与灵活的包管理解决方案
前端·pnpm·前端工程化·包管理器
whale fall8 小时前
npm install安装的node_modules是什么
前端·npm·node.js
烛阴8 小时前
简单入门Python装饰器
前端·python
袁煦丞8 小时前
数据库设计神器DrawDB:cpolar内网穿透实验室第595个成功挑战
前端·程序员·远程工作