当前位置: 首页 > Web前端 > CSS

使用FLIP技术优化你的动画

时间:2023-03-30 18:14:23 CSS

前言在项目中,我们会写很多小视图和大视图之间的过渡动??作,比如小图到大图的细节,如果不是动画,它会很僵硬由小变大的效果,用户体验不是很好。通常,我们会通过添加动画来缓解这些问题,但是css解决方案并不是通用的解决方案,不适合大范围的变化,偏移值也不好计算。在本文中,我们将探索一种称为“FLIP”的技术,它可用于以高效的方式为任何DOM元素的位置和尺寸设置动画,而不管其布局是如何计算或呈现的(例如,高度、宽度、浮动)),absolutepositioning,transform,flexbox,grid,etc)为什么要使用FLIP技术你有没有试过对高度、宽度、顶部、左侧或除变换和不透明度之外的任何其他属性设置动画?您可能已经注意到,动画看起来有点_破旧_,这是有原因的。当任何属性触发布局更改时,例如高度,浏览器必须递归检查其他元素的布局是否因此发生更改,这可能代价高昂。如果该计算花费的时间超过一个动画帧(大约16.7毫秒),则该动画帧将被跳过,因为它没有及时呈现,从而导致“干扰”。在PaulLewis的文章“PixelsAreExpensive”中,他进一步探讨了像素的渲染方式和各种性能成本。简而言之,我们的目标是简洁-我们希望尽快找出最少的必要样式更改。这里的关键只是动画变换和不透明度,FLIP解释了我们如何只能通过变换来模拟布局变化。什么是FLIP?FLIP是PaulLewis首创的助记符和技巧,代表FIRST、LAST、Invert、Play。他的文章很好地解释了这项技术,但我将在这里概述它:首先:在任何事情发生之前,记录将要转换的元素的当前(即第一个)位置和大小。您可以像下面这样使用element.getBoundingClientRect()。Last:执行使转换瞬间发生的代码,并记录元素的最终(即最后)位置和大小。Invert:由于元素在最后一个位置,我们想通过transform修改它的位置和大小,营造出它在第一个位置的错觉。这需要一点数学知识,但并不难。Play:元素被反转(并假装在第一个位置),我们可以通过将transform设置为none将其移回最后一个位置。以下是使用WebAnimationsAPI实现这些步骤的方法:getBoundingClientRectElement/animateconstelm=document.querySelector('.some-element');//首先:获取当前元素位置属性constfirst=elm.getBoundingClientRect();//执行导致布局改变的脚本changedoSomething();//Last:获取最终位置属性constlast=elm.getBoundingClientRect();//Reverse:计算起点和终点的差值//计算初始点之间的边界位置和最终位置constdeltaX=first.left-last.left;constdeltaY=first.top-last.top;constdeltaW=first.width/last.width;constdeltaH=first.height/last.height;//播放:使原图从初始位置移动到最终位置,${deltaH})`},{transformOrigin:'topleft',transform:'none'}],{duration:300,easing:'ease-in-out',fill:'botH'});在此处查看演示以直观地将动画时间增加到1秒注意:并非所有浏览器都支持WebAnimationsAPI。但是可以使用polyfill。共享元素转换在应用程序视图和状态之间转换元素的一个常见用例是最终元素可能与初始元素不是同一个DOM元素。我们使用简单的代码来实现点击方法的效果:constfirstElm=document.querySelector('.first-element');constfirst=firstElm.getBoundingClientRect();做一点事();constlastElm=document.querySelector('.last-element');constlast=lastElm.getBoundingClientRect();Imageclicktoenlargedemo下面是一个示例,说明如何使用共享元素转换将两个完全不同的元素显示为同一元素。点击其中一张图片查看效果demo父子过渡在之前的实现中,元素作用域是基于window的。这对于大多数用例来说都很好,但请考虑以下场景:一个元素改变了位置并需要进行转换。该元素包含一个子元素,该子元素本身需要在父元素内的其他地方进行转换。由于先前计算的范围是相对于窗口的,因此我们对子元素的计算将不可用。为了解决这个问题,我们需要确保相对于父元计算边界:constparentElm=document.querySelector('.parent');constchildElm=document.querySelector('.parent>.child');//First:parentandchildconstparentFirst=parentElm.getBoundingClientRect();constchildFirst=childElm.getBoundingClientRect();doSomething();//Last:parentandchildconstparentLast=parentElm.getBoundingClientRect();constchildLast=childElm.getBoundingClientRect();//反转:parentconstparentDeltaX=parentFirst.left-parentLast.left;constparentDeltaY=parentFirst.top-parentLast.top;//反转:childrelativetoparentconstchildDeltaX=(childFirst.left-parentFirst.left)-(childLast.left-parentLast.left);constchildDeltaY=(childFirst.top-parentFirst.top)-(childLast.top-parentLast.top);//播放:使用WAAPIparentElm.animate([{transform:`translate(${parentDeltaX}px,${parentDeltaY}px)`},{transform:'none'}],{duration:300,easing:'ease-进出'});childElm.animate([{transform:`translate(${childDeltaX}px,${childDeltaY}px)`},{transform:'none'}],{duration:300,easing:'ease-in-out'});这里还有几点要注意:父子(duration、easing等的时间选择)也_不必_一定要达到这个技巧才能随时发挥创意!此示例中有意省略了更改父项和/或子项中的尺寸(宽度、高度),因为这是一个高级且复杂的主题。让我们将其保存到另一个教程中。您可以将共享元素与父子技术结合起来以获得更大的灵活性。Flipping.js的完全灵活性以上技术可能看起来很简单,但是一旦您必须跟踪多个元素的转换,它们就会变得乏味。Android通过以下方式减轻了这种负担:将共享元素过渡到核心SDK允许开发人员通过使用通用android:transitionNameXML属性和一个名为Flipping.js的小型库来识别哪些元素是共享的。通过将data-flip-key="..."属性添加到HTML元素,可以预测且高效地跟踪位置和大小可能因状态而异的元素。例如,考虑以下初始视图:

而这个单独的详细视图:
Loremipsumdolorsitamet...请注意,在上面的示例中,有2个元素使用相同的data-flip-key="photo-1"。Flipping.js通过选择满足以下条件的第一个元素来跟踪“活动”元素:元素存在于DOM中(即,它尚未被删除或分离)元素未隐藏(提示:对于隐藏元素elm.getBoundingClientRect()将具有{width:0,height:0})selectActive选项中指定的任何自定义逻辑。Flipping.js入门根据您的需要,有几种不同的Flipping包:flipping.js:微型和低级;仅在元素范围更改时发出事件flipping.web.js:使用WAAPI为转换设置动画flipping.gsap。js:使用GSAP动画转换更多适配器即将推出!您可以直接从unpkg获取压缩代码:https://unpkg.com/flipping@latest/dist/flipping.jshttps://unpkg.com/flipping@latest/dist/flipping.web.jshttps://unpkg。com/flipping@latest/dist/flipping.gsap.js或者,您可以npminstallflipping--save将其导入到您的项目中://importnotnecessarywhenincludingtheunpkgscriptsinatagimportFlippingfrom'flipping/adapters/web';constflipping=newFlipping();//首先:让Flipping读取所有初始boundsflipping.read();//执行导致任何元素改变的改变boundsdoSomething();//Last,Invert,Play:flip()方法完成allflipping.flip();处理由于函数调用引起的FLIP转换是一种常见的模式,.wrap(fn)方法首先调用.read(),然后获取函数的返回值,然后调用.flip(),然后返回return到透明地包装(或“装饰”)给定的功能。价值。这导致更少的代码:constflipping=newFlipping();constflippingDoSomething=flipping.wrap(doSomething);//任何时候调用它,FLIP都会为改变的元素设置动画flippingDoSomething();这里有一个flipping.wrap()的简单字母过渡效果的例子。单击任意位置以查看效果。演示添加Flipping.js到现有项目在另一篇文章中,我们使用有限状态机创建了一个简单的ReactGallery应用程序。它按预期工作,但UI可以在状态之间使用一些平滑的过渡来防止“跳跃”并改善用户体验。让我们将Flipping.js添加到我们的React应用程序中以完成此操作。(请记住,Flipping.js与框架无关。)第1步:初始化Flipping.jsFlipping实例将驻留在React组件本身,因此它仅与该组件内发生的更改隔离。通过在componentDidMount生命周期钩子中设置它来初始化Flipping.js:componentDidMount(){const{node}=this;如果(!节点)返回;this.flipping=newFlipping({parentElement:node});//使用初始边界初始化翻转this.flipping.read();通过指定parentElement:node,我们告诉Flipping仅在渲染中查找具有data-flip-key的元素App,而不是整个文档。然后,使用data-flip-key属性(类似于React的keyprop)修改HTML元素以识别唯一和“共享”元素:renderGallery(state){return({this.state.items.map((item,i)=>this.transition({type:'SELECT_PHOTO',item})}data-flip-key={item.link}/>)});}renderPhoto(state){if(state!=='photo')返回;return(this.transition({type:'EXIT_PHOTO'})}>)}通知img.ui-item和img.ui-photo如何由data-flip-key表示={item.link},而data-flip-key={this.state.photo.link}分别是:当当前用户点击img.ui-item时,即把item设置为this.state.photo,这样的值。链接将相等,并且由于它们相等,翻转将从img.ui-item缩略图平滑过渡到更大的img.ui-photo。现在,我们需要做两件事:this.flipping.read()每当组件_will_update和this.flipping.flip()每当组件_does_update正如你们中的一些人可能已经猜到的,这些方法调用发生在哪里:componentWillUpdate是componentDidUpdate并分别:componentWillUpdate(){this.flipping.read();}componentDidUpdate(){this.flipping.flip();并且,像这样,如果您正在使用Flipping适配器(例如flipping.web.js或flipping.gsap.js),那么Flipping将使用[data-flip-key]跟踪所有元素并平滑地转换它们当他们改变时,到新的界限。这是最终结果:demo如果您想自己实现自定义动画,您可以使用flipping.js作为一个简单的事件发射器。阅读更多高级用例的文档。Flipping.js及其适配器默认处理共享元素和父子转换,并且:transitionsbreak(inadapters)enter/move/leavestate支持插件,例如mirror,它允许新输入的元素“镜像”另一个元素运动和更多的未来计划!资源类似的库包括:PaulLewis自己的FlipJS,它处理一个简单的单元素FLIP转换React-Flip-Move,JoshComeau的有用的React库BarbaJS,不一定是FLIP库,而是一个允许你在一个库之间移动的库这增加了URL之间的平滑过渡而无需页面跳转。更多资源:没有动画的动画–JoshuaComo翻转动画–PaulLewis像素很昂贵–PaulLewis通过页面转换改善用户流程–LuigideRosaUX设计的智能转换–AdrianZumbrunnen如何进行良好的转换?–NickBabichGoogle的MaterialDesign运动指南ShareElementTransitionswithReactNative