FLIP技术让动画更流畅

前言

此篇文章实际上是《H5页面性能优化之加载篇》的姊妹篇。主要想从CSS 动画的角度给大家介绍下 FLIP 技术,可以帮助大家创建真正轻量级的动画,以此来提高 Web 动画的性能。

很多同学平时并不重视动画,认为动画相比较业务逻辑画蛇添足,有时还影响页面性能,所以开发时则是能省就省,其实不然,开发中业务逻辑当然是要放在首位的,但是同样也不要小看了动画,在某些特定场景下,其所能起到的作用甚至可以与业务的目标并驾齐驱。但是这有一个大前提,那就是需要流畅的动画,FLIP 技术应运而生,可以帮助你创建真正轻量级的动画。

FLIP 技术

FLIP是一种记忆设备和技术,最早是由@Paul Lewis提出的,FLIP是First、Last、Invert和Play四个单词首字母的缩写。

FLIP 将一些性能低下的动画映射为 transform 动画。通过记录元素的两个快照,一个是元素的初始位置(First – F),另一个是元素的最终位置(Last – L),然后对元素使用一个 transform 变换来反转(Invert – I),让元素看起来还在初始位置,最后移除元素上的 transform 使元素由初始位置运动(Play – P)到最终位置。

它就是通过这样一种高性能的方式来动态的改变DOM元素的位置和尺寸,而不需要管它的布局是如何计算或渲染的(比如,height、width、float、绝对定位、Flexbox和Grid等)。

上面这个过程可以拆解为以下四个步骤:

1. First

元素的初始状态,记录当前元素的位置、尺寸、透明度等等的样式信息

2. Last

元素的最终状态,即动画后元素的位置、尺寸、透明度等等的样式信息

3. Invert

将元素恢复至动画前状态,即相反操作,先计算出从初始状态到最终状态元素发生的改变,比如宽度、高度、透明度等,然后在元素上应用 transform 或 opacity 使这些改变反转,给人一种错觉,即它原来就在初始位置。

这一步比较关键,是此技术的核心,我们举例说明一下:假如现在有一个图片列表,初始有两个图片 img1,img2,然后在列表开头加入新图片 img3,img4,此时 img1 和 img2 就会被挤到后面去。

假设 img1 的初始位置是 (0, 0),被挤下去后的位置是 (100, 100),那么此时浏览器还没有渲染,我们可以在这个时间点通过设置 img1.style.transform = translate(-100px, -100px),让它先 Invert 倒置回位移前的位置。

4. Play

执行动画,前面的准备工作都做好了,最后就是 Play 了,移除元素上的 transform(将transform置为0或none) 并启用 tansition 相关的动画。

为了便于大家更好的理解FLIP技术的原理,借用davidkpiano 曾在分享中使用的流程图向大家展示,或许更易于理解:

13c77273c4a440f1a9d94fc30717aca5.jpeg

004273d0791e4cffb339c19e09fffe32.gif

实现

了解了 PLIP 技术的原理后,我们通过实现一个简单的小例子来更好的理解这个概念,实现卡片的增删动画。

首先我们先初始化一个列表如图所示:

企业咚咚20200929133416.jpg

然后按照上面四个步骤来开发增删动画。

First

此处因为卡片的尺寸在动画过程中不发生任何变化,所以我们只需记录卡片的位置信息即可

let cardIndex = 4
let activeList = null

// 生成初始测试数据
let listData = Array(cardIndex).fill().map((item, index) => ({
  index
}))

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

Last

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

// 0标识增加,1标识删除
let updateType = 0
// 定义新的列表数组
let newListData = null
activeIndex = currentIndex

if (updateType === 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)
}

Invert

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

// 0 增加 1 删除
const updateIndex = updateType === 0 ? 1 : 0
activeList.forEach((item, index) => {
  rect = item.getBoundingClientRect()
  invertArr[index + updateIndex][0] = invertArr[index + updateIndex][0] - rect.left
   invertArr[index + updateIndex][1] = invertArr[index + updateIndex][1] - rect.top

Play

反转了以后,想让他做动画就简单了

// 生成一个二维数组
function getArrByLen(len) {
  return Array(len).fill().map(() => [0, 0])
}

invertArr = getArrByLen(this.state.listData.length)

<li lassName={`card-item${index >= activeIndex ? ' active' : ''}`} style={index >= activeIndex ? {transform: `translate(${invertArr[index - activeIndex][0]}px, ${invertArr[index - activeIndex][1]}px)`} : null) }>
...主体区域
</li>

另外由于card在排列过程中可能出现遮挡问题,此处可以将 z-index 初始化为1,之后横排的层级变化,则进行 z-index的提升以获得更好的视觉体验。

if (transArr[index + stepIndex][1] !== 0) {
  this.state.listData.some((v, k) => {
    if (index + stepIndex + activeIndex === k) {
      v.zIndex = zIndex++
      return true
    }
    return false
  })
}

最后让我们来看一下效果吧~

2.gif

FLIP 动画应用场景

FLIP技术适用于动画的初始态或最终态不明确的情况,这时候,使用FLIP非常容易做出动画效果。

例如:渲染一个图片列表,当插入或者删除其中某一个元素项时,它会突然出现或者消失,同时其他兄弟元素项也会瞬移,这对于用户体验非常不好,此时除非你限定死了每个元素项的尺寸以及整体页面的尺寸,否则你无法明确当你任意插入或者删除了某个元素之后,其他元素项应当在什么位置。这里就可以使用FLIP 技术给列表的操作添加过渡动画。

WX20200929-080630@2x.png

在创建UI时,添加合理的UI过渡动效,避免跳转和瞬间移动。如果将生活中的一些自然运动用到UI动效中来,将会给你的用户带来眼前一亮的感觉。毕竟,所有与你互动的东西都源自于生活中自然的运动。

但是对于一些你明确的知道它的初始态和结束态的动画,其实你可以直接使用 transform,根本没必要 FLIP,用了反而多此一举。

经总结,FLIP 技术主要应用于以下几个场景:

  • 视图之间的过渡
  • 图片展开和收缩效果
  • 项目删除和添加时填充空白区域的效果
  • 网格项的重新排序

FLIP 能提高动画流畅度

FLIP 最大的优点就是提高动画的流畅度,Google 的 Paul Kinlan 曾做过一个调研 关于用户对新闻类 APP 最期待什么样的功能。回答令人惊讶,最多的声音不是离线支持,不是平台间同步,不是更好的通知方式,而是希望使用起来更加平滑。平滑就意味着没有屏幕闪烁,没有卡顿,没有抖动。

用户在网页上进行交互时,比如click,touch等,从交互结束到感知到程序的响应大约需要100ms的生理反应时间。如果你的网站在100ms内作出了相应的响应,那么在用户看来,就是得到了立即的响应。否则用户就会觉得卡顿,反应延迟。

我们可以充分利用用户 100ms 生理反应时间来进行相关的计算:getBoundingClientRectgetComputedStyle,并通过 FLIP 技术使动画尽快开始,最后通过 transform 和 opacity 的动画来保证动画的平滑运行。

636026-20200917152836815-436837780.jpg

这是一个用户交互体验的原理图,基于100ms的生理反应时间,我们需要在用户交互后100ms内准备好动画,然后以60fps的帧速进行动画的运行。为什么是60fps,此处不做深入探讨,感兴趣的同学可以去看《为什么帧率达到60fps页面就流畅?》,这些动画准备计算就是getBoundingClientRect(或getComputedStyle)等的计算,而60fps的真素,则需要通过transform或者opacity的反转动画来实现。我们先将元素设置到了结束位置,通过transform来模拟开始位置,然后去除transform这个反向的过程,使得浏览器知道了动画的开始位置和结束位置,计算量锐减,流畅度自然就提升了。

tips

使用 FLIP 技术时有几点需要牢记:

  1. 不要超出用户反应时间 100ms。否则用户会觉得你的应用没有立即响应,开发中通过 DevTools 注意这一点。
  2. 精心组织你的动画。设想一下,如果一个 FLIP 动画正在运行,同时你接着想执行下一个 FLIP 动画,这时就要确保下一个动画的预计算工作是在闲置或用户的反应时间内进行,这样就可以保证两个动画互不影响。
  3. 内容可能被扭曲。当进行某些缩放动画时可能导致内容扭曲,毕竟这是一种 Hack 技术。

总结

FLIP动画,是一种动画机制,策略,通过反转动画来提高流畅度,其实你在写一些过渡效果的时候,在无意识的情况下用到过,该文章只是将其总结并优化后的产物。另外,强大的 Vue 贴心地为我们提供了两个内置组件 transition & transition-group,其中 transition-group 内部已经实现 FLIP 的简单动画队列,方便我们使用,感兴趣的小伙伴可以直接去官网学习使用。

参考

Vue transition-group
flip-your-animations
FLIP技术给Web布局带来的变化

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,658评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,482评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,213评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,395评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,487评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,523评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,525评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,300评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,753评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,048评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,223评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,905评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,541评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,168评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,417评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,094评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,088评论 2 352