FLIP实现animation动画

本文代码均放在git仓库

FLIP是什么

首先FLIP并不是一项新技术,可以把它理解为一种实现动画的新的理念或者新的方法。

FLIP是 First、Last、Invert和 Play四个单词首字母的缩写。

  • First,指的是在任何事情发生之前(过渡之前),记录当前元素的位置和尺寸,即动画开始之前那一刻元素的位置和尺寸信息,可以使用 getBoundingClientRect()这个 API来处理;
  • Last:执行一段代码,让元素发生相应的变化,并记录元素在动画最后状态的位置和尺寸,即动画结束之后那一刻元素的位置和尺寸信息;
  • Invert:计算元素第一个位置(First)和最后一个位置(Last)之间的位置或者尺寸变化,然后使用这些数字做一定的计算,让元素进行移动(通过 transform来改变元素的位置和尺寸),从而创建它位于初始位置的一个错觉。即让元素处于动画的结束状态,然后使用 transform属性将元素反转回动画的开始状态(这个状态的信息在 First步骤就拿到了);
  • Play:将元素反转(假装在first位置),我们可以把 transform设置为 none,因为失去了 transform的约束,所以元素肯定会往本该在的位置(即动画结束时的那个状态)进行移动,也就是last的位置,如果给元素加上 transition的属性,那么这个过程自然也就是以一种动画的形式发生了。

举个

var app = document.getElementById('app');
var first = app.getBoundingClientRect(); // 初始态
app.classList.add('app-to-end');
var last = app.getBoundingClientRect(); // 终态
var invert = first.top - last.top;
// 使元素看起来好像在起始点
app.style.transform = `translateY(${invert}px)`;
requestAnimationFrame(function() {
   // 启用tansition,并移除翻转的改变
  app.classList.add('animate-on-transforms');
  app.style.transform = '';
  // 此时,方块就会从假装在起始点开始transition
});
app.addEventListener('transitionend', () => {
  app.classList.remove('animate-on-transforms');
});

在React中是什么用的?

微信app里聊天界面点击预览图片时,图片从对话框到全屏预览的这个过程,用了一个过渡的动画,呈现出图片从小图到大图和从大图恢复到小图的全过程。代码仓库

First

    { listData.map((item, index) => (
  • )) }

在previewItem函数中的处理逻辑:计算初始态

previewItem (status, previewImgInfo = null, e) {
    previewVisibleStatus = status;
    if (previewVisibleStatus === 1) {
      // 计算初始态
      const currentPreviewEle = e.target;
      rectInfo = currentPreviewEle.getBoundingClientRect()
      previewFirstRect[0] = rectInfo.left
      previewFirstRect[1] = rectInfo.top
      this.setState({
        previewImgInfo,
        previewStatus: 1
      })
    } else {
      this.setState({
        previewStatus: 1
      })
    }
}

此时会触发一次render()函数

{
  (previewVisibleStatus === 1 || previewVisibleStatus === 2) ? (
    <>
      
) : null }

Last

接着会触发componentDidUpdate生命周期函数,在该生命周期函数中主要是执行updatePreviewStatus函数。

updatePreviewStatus () {
    if (this.state.previewStatus === 1) {
      // 计算终态
      if (previewVisibleStatus === 1) {
        const lastRectInfo = this.previewRef.current.getBoundingClientRect()
        previewLastRect[0] = lastRectInfo.left
        previewLastRect[1] = lastRectInfo.top
        scaleValue = rectInfo.width / lastRectInfo.width
      }
      this.setState({
        previewStatus: 2
      })
    } else if (this.state.previewStatus === 2) {
      // Play
      setTimeout(() => {
        this.setState({
          previewStatus: 3
        })
      }, 0)
    }
}

Invert

再次触发render()函数

{
  (previewVisibleStatus === 1 || previewVisibleStatus === 2) ? (
    <>
      
) : null }

此时,transform属性是

translate3d(${previewFirstRect[0] - previewLastRect[0]}px, ${previewFirstRect[1] - previewLastRect[1]}px, 0) scale(${scaleValue})

也就是说第二次执行render函数的时候,图片在(translate3d + scale)的作用下缩放回了初始位置。

Play

再次触发componentDidUpdate生命周期函数,在该生命周期函数中主要是执行updatePreviewStatus函数。

updatePreviewStatus () {
    if (this.state.previewStatus === 1) {
      // xxxxx
    } else if (this.state.previewStatus === 2) {
      // Play
      setTimeout(() => {
        this.setState({
          previewStatus: 3
        })
      }, 0)
    }
}

触发render()函数

<>
  

为图片添加 transition属性,并移除相关 transform属性,即可启动动画。

css:
.img.active {
  transition: all .36s ease-in-out;
}

至于放大后的图片恢复到小图这一个阶段,可以看成是另外一个 FLIP动画,继续套用即可,只不过这个动画就是上一个放大动画的逆向,所需的尺寸和位置信息都已经拿到了,可以省去调用 getBoundingClientRect的过程。

为什么要用FLIP?

有些人可能比较疑惑,如果想要实现动画的话,直接 transform不就好了,为什么要多此一举搞个 FLIP的概念出来?

对于一些动画,你明确的知道它的初始态(First)和结束态(Last),比如你就想让一个元素从 left:10px;移动到 left:100px;,那么你直接 transform就好了,根本没必要 FLIP,用了反而多此一举;

但除此之外,还有一部分你无法明确的初始态(First)或结束态(Last)的动画。这样情况下使用FLIP就会比较爽快了。但是你可能又会说,我即时不知道结束态,但是可以使用浏览器 API进行测量啊。

不好意思,这正是 FLIP要做的事情之一,你还是在无意识地情况下用到了这个东西,只不过相对于被前人总结并优化后的 FLIP来说,你的整体用法可能更零散更不规范一些。

你可能感兴趣的:(FLIP实现animation动画)