原文地址
https://css-tricks.com/animating-layouts-with-the-flip-technique/
为什么使用FLIP
作为一名前端开发者,项目中我们会经常实现一些动画效果,这些动画通过设置height,width,top,left或者除了transform,opacity其他的属性值来完成。您可能已经注意到,这些动画技术已经有些过时,这其实是有原因的。当任何触发布局更改的属性(比如height)变化时,浏览器都必须检查其他元素的布局是否发生了改变,这代价是很昂贵的(浏览器的回流与重绘)。如果计算所花费的时间超过了一个动画帧(大约16.7毫秒),则该动画帧将被跳过,因为该帧没有及时渲染,所以页面有时会出现卡顿的情况。在保罗·刘易斯(Paul Lewis)的文章Pixels are Expensive,他深入探讨了如何渲染像素以及各种性能支出。
简而言之,项目开发过程中,我们要尽量减少一些必要样式修改所导致的浏览器回流操作。FLIP技术可以很好的帮助我们如何仅使用transform和opacity来模拟一些常见的布局修改动画。
什么是FLIP
FLIP是一个简写词,最早是由Paul Lewis提出的,分别代表了First,Last,Invert,Play,他的文章很好的解释了这项技术。
- First: 在动画开始之前,请记录过度元素的初始形态,比如位置和尺寸信息,我们可以使用element.getBoundingClientRect().
- Last: 记录过渡元素的最终形态,方法同上
- Invert: 我们已经知道了过渡元素的初始和最终形态,所以我们可以很清楚的了解元素的变换方式,比如高度,宽度和不透明度的变化,接下来,我们通过修改transform和opacity使过渡元素任然保持初始形态,比如过渡元素的最终形态是向下移动了90px,那么我们可以设置translateY:-90px来使元素看起来仍然是在初始位置。
- Play: 上一个操作中,元素虽然已经处于最终位置,但是看起来任然是在初始位置,这时候我们可以赋予元素transition动画,并初始化已经应用的transform和opacity样式,我们可以看到过渡元素会自然的过度到最终位置。
以下代码是如何使用Web Animation API来实现这些步骤:
const elm = document.querySelector('.some-element');
// First: 记录元素的初始形态
const first = elm.getBoundingClientRect();
// 执行一些操作使元素形态发生改变
doSomething();
// Last: 获取元素的最终形态
const last = elm.getBoundingClientRect();
// Invert: 反转元素的最终和初始形态
const deltaX = first.left - last.left;
const deltaY = first.top - last.top;
const deltaW = first.width / last.width;
const deltaH = first.height / last.height;
// Play: 应用动画,使元素从初始形态过度到最终形态
elm.animate([{
transformOrigin: 'top left',
transform: `
translate(${deltaX}px, ${deltaY}px)
scale(${deltaW}, ${deltaH})
`
}, {
transformOrigin: 'top left',
transform: 'none'
}], {
duration: 300,
easing: 'ease-in-out',
fill: 'both'
});
注意:在撰写本文时,并非所有浏览器都支持Web Animation API,您可以使用polyfill
共享元素过度
最终元素和初始元素可能不是同一DOM元素,这与Android中的共享元素过度类似。尽管如此,我们仍然可以通过一些特殊操作来实现FLIP过度。
const firstElm = document.querySelector('.first-element');
// First: 记录元素(记为firstEle)的初始形态并隐藏它
const first = firstElm.getBoundingClientRect();
firstElm.style.setProperty('visibility', 'hidden');
// 执行一些操作使视图更改
doSomething();
// Last: 获取共享元素(记为lastEle)的最终形态
const lastElm = document.querySelector('.last-element');
const last = lastElm.getBoundingClientRect();
// 像上边那样继续其他步骤
// 请注意:您应该为lastEle执行动画,而不是firstEle
以下是使用共享元素过度实现将两个不同元素看起来像同一元素动画的例子,单击其中一张图片查看效果