让动画变得更简单之FLIP技术

栏目: Html · 发布时间: 5年前

内容简介:某次被问到如何实现以下动画效果:若干个元素卡片从上而下排列,当增加或删除某个卡片的时候,其余的卡片会以一种当时我恰好看过

某次被问到如何实现以下动画效果:

让动画变得更简单之FLIP技术

若干个元素卡片从上而下排列,当增加或删除某个卡片的时候,其余的卡片会以一种 transition 动画的形式移动到适当的位置上,而不是生硬地闪现

当时我恰好看过 Vue 中的内置组件 transition 的实现,意识到完全可以用 transition 组件的部分原理来完成这个效果,但是由于没有深入地探究过为什么是这样,只停留在表面,知其然而不知其所以然,所以尽管我知道如何实现这个效果,但很难解释为什么是这样,语言组织地比较困难

后来我无意间看到一篇文章 FLIP技术给Web布局带来的变化 ,立马恍然大悟,原来这个东西叫 FLIP

FLIP

FLIP是 FirstLastInvertPlay 四个单词首字母的缩写

First ,指的是在任何事情发生之前(过渡之前),记录当前元素的位置和尺寸,即动画开始之前那一刻元素的位置和尺寸信息,可以使用 getBoundingClientRect() 这个 API 来处理(大部分情况下其实 offsetLeftoffsetTop 也是可以的)

Last :执行一段代码,让元素发生相应的变化,并记录元素在动画最后状态的位置和尺寸,即动画结束之后那一刻元素的位置和尺寸信息

Invert :计算元素第一个位置( First )和最后一个位置( Last )之间的位置变化(如果需要,还可以计算两个状态之间的尺寸大小的变化),然后使用这些数字做一定的计算,让元素进行移动(通过 transform 来改变元素的位置和尺寸),从而创建它位于第一个位置(初始位置)的一个错觉

即,一上来直接让元素处于动画的结束状态,然后使用 transform 属性将元素反转回动画的开始状态(这个状态的信息在 First 步骤就拿到了)

Play :将元素反转(假装在 first 位置),我们可以把 transform 设置为 none ,因为失去了 transform 的约束,所以元素肯定会往本该在的位置(即动画结束时的那个状态)进行移动,也就是 last 的位置,如果给元素加上 transition 的属性,那么这个过程自然也就是以一种动画的形式发生了

按照我的理解,就是对动画元素起止状态的一个量化,量化成一个公式,绝大部分的连续动画都可以通过套用这个公式来完成,提升动画的开发效率,更加详细的请自行参见 FLIP技术给Web布局带来的变化

实现卡片 Card增删动画

了解了 FLIP 这个概念之后,再来实现开头提到的那个动画效果,其实就很简单了

First

记录在动画开始之前每个卡片的位置和尺寸信息,这里因为卡片的尺寸在动画过程中其实是不会发生任何变化的, 所以可以略过这一步,只记录卡片的位置信息

另外,如果所有卡片的尺寸都是相同的,那么也无需记录所有卡片的位置信息,因为无论是插入卡片还是删除卡片,都只有那些位于位置坐标在变化卡片坐标的后面的卡片才会受到影响的,前面的是不会变的

// First
activeList.forEach((itemEle, index) => {
  rectInfo = itemEle.getBoundingClientRect()
  transArr[index + stepIndex][0] = rectInfo.left
  transArr[index + stepIndex][1] = rectInfo.top
})
复制代码

Last

动画的结束状态,其实就是增加或者删除了卡片之后,其余卡片的状态:

if (updateStatus === 0) {
  // 增加卡片
  newListData = this.state.listData.slice(0, activeIndex).concat({
    index: cardIndex++
  }, this.state.listData.slice(activeIndex))
} else {
  // 删除卡片
  newListData = this.state.listData.filter((value, index) => index !== activeIndex)
}
复制代码

因为这个时候没给卡片加 transition 属性,所以卡片数量更新这个过程,其实就是一瞬间的事情,人眼是无法察觉到任何变化的,但是页面上的元素确实是发生了变化,然后此时测量卡片的位置信息,即 Last 所需要的数据

Invert

获取了动画起始阶段受影响的卡片的位置信息后,就可以通过 transform 属性对元素的位置进行反转了

// Last + Invert
const stepIndex = updateStatus === 0 ? 1 : 0
activeList.forEach((itemEle, index) => {
  rectInfo = itemEle.getBoundingClientRect()
  transArr[index + stepIndex][0] = transArr[index + stepIndex][0] - rectInfo.left
  transArr[index + stepIndex][1] = transArr[index + stepIndex][1] - rectInfo.top
}
复制代码

Play

准备阶段就绪,就可以进行最后一步 Play 起来了,这一步的关键就是给元素加上 transition 属性,并移除 transform 给元素带来的位置变化:

// Play
// 重置
transArr = getArrByLen(this.state.listData.length)
setTimeout(() => {
  this.setState({
    animateStatus: 3
  })
}, 0)
复制代码

因为浏览器会对页面的 DOM 变化进行合并优化,所以为了能在视觉上呈现出想要的动画效果,这里必须要打断这种优化, setTimeout 是一个很常用的方式

到此为止,就完成了文章开头的那个动画效果,我做了个Live Demo,有兴趣的可以亲自试下,另外代码也可以上传到 Github

实现图片放大/恢复动画

微信app里聊天界面点击预览图片时,图片从对话框到全屏预览的这个过程,用了一个过渡的动画,呈现出图片从小图到大图和从大图恢复到小图的全过程,缩放过程类似于下面这种:

让动画变得更简单之FLIP技术

这种也属于连续动画,当然也可以通过 FLIP 来轻松实现

First

这里涉及到图片的位置和尺寸的变化,图片从 First 的小图原位置和小图尺寸,变成了 Last 状态下的大图位置和大图尺寸,所以需要同时获取这两个数据,其实都是可以通过一次调用 getBoundingClientRect 完成

Last

获取图片已经处于预览状态下的尺寸和位置信息,同样使用 getBoundingClientRect 完成

另外,为了更好地利用 transform 动画,我这里将图片两个状态下的尺寸变化转变为 scale 值的变化, FirstLast 状态下宽度或者高度的比例就是这个 scale 的应当取值(在没有改变图片宽高比例的前提下)

scaleValue = rectInfo.width / lastRectInfo.width
复制代码

Invert

使用 transform 进行位置和尺寸(即改变 scale 值)的反转

这里有一点需要注意的是,由于 transform 动画默认的 transform-origin 为元素的中心,即 50% 50% ,但是计算出来的 lefttop 却是相对于没有缩放的图片而言的,所以当 scale 取值不唯一时,图片动画的 First 状态就会发生偏差,需要将 transform 设为 0 0 以消除这种偏差

让动画变得更简单之FLIP技术

Play

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

可以看到,套用了 FLIP 之后,原本看起来比较棘手的一个动画,被轻易模式化实现了

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

同样做了个Live Demo,有兴趣的可以亲自试下,另外代码也可以上传到 Github

小结

很多前端同学似乎不太在意动画,认为这只是一个辅助能力,业务逻辑才是最重要的,其他的全都靠后站,就算是有时间也要看心情再决定搞不搞

我的看法是,业务逻辑当然是要放在首位的,但是同样也不要小看了其余的细枝末节,例如动画,一个体验良好的动效完全可以吸引用户的更多停留,以一种通用的方式从侧面提升业务的转化效果,某些特定场景下,其所能起到的作用甚至可以与业务的目标并驾齐驱


以上所述就是小编给大家介绍的《让动画变得更简单之FLIP技术》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Haskell函数式编程基础

Haskell函数式编程基础

Simon Thompson / 科学出版社 / 2013-7-1 / 129.00

《Haskell函数式编程基础(第3版)》是一本非常优秀的Haskell函数式程序设计的入门书,各章依次介绍函数式程序设计的基本概念、编译器和解释器、函数的各种定义方式、简单程序的构造、多态和高阶函数、诸如数组和列表的结构化数据、列表上的原始递归和推理、输入输出的控制处理、类型分类与检测方法、代数数据类型、抽象数据类型、惰性计算等内容。书中包含大量的实例和习题,注重程序测试、程序证明和问题求解,易......一起来看看 《Haskell函数式编程基础》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码