资讯专栏INFORMATION COLUMN

小动画大学问

Bryan / 2259人阅读

摘要:在样式代码添加录制性能日志如下可见,已经不存在绘制的步骤了。下面通过一段代码模拟页面进入的过程,来演示这个问题运行效果如下可以看到,固定定位的黄色元素是在动画结束后才突然出现的。

对于移动端的Web单页应用来说,为了达到媲美原生应用的效果,页面过渡动画是必不可少的。常用的页面过渡动画包括:

位移——当前页向左侧或右侧水平移出可视区,下一页由反方向移入可视区。

不透明度变化——当前页淡出,下一页淡入。

1和2同时进行。

(注意:以下讨论和实验均在 Chrome 68 浏览器环境下进行)

目前大多数设备的屏幕刷新率为60次/秒,算下来每个帧的预算时间约为16.66毫秒(1/60秒)。考虑到浏览器还有其他工作要执行,实际上预算时间只有10毫秒。跟此预算时间的差值越大,用户就会觉得动画过程越卡。那么,在这10毫秒内要完成什么事情呢?当使用JavaScript实现视觉交互效果时,一般要经过以下流程:

JavaScript的执行。例如修改元素的样式,或者给元素添加/删除样式类。

样式计算。根据样式规则计算出元素的最终样式。

布局(layout)。根据上一步的结果,计算元素占据的空间大小及其在屏幕的位置。注意,一个元素布局上的变化有可能会引发其他元素的联动变化。

绘制(paint)。填充像素的过程,包括元素的每个可视部分。一般来说,绘制是在多个层上进行的。

合成(composite)。把各层按正确顺序合并成一个层,显示到屏幕上。

值得注意的是,并非每一帧都会经过上述每一个步骤的处理。如果元素的几何属性(尺寸、位置)没有变化,就不需要进行布局;如果连元素的外观都没有改变,就不需要绘制。所以,实现流畅动画的关键就在于如何减少布局和绘制

位移

对于位移动画来说,最直接的实现方式,就是把元素设成绝对定位,然后去改变它的left样式值。例如:








使用Chrome开发者工具中的Performance面板录制动画过程的性能日志,如下图所示:

可见,元素在移动的过程中不断触发了布局和绘制。所以,这种实现方式的性能是极低的。网上诸多文献会推荐以transform的变化代替left的变化,而实际情况又是怎么样呢?把样式代码稍作修改:

.page {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    min-height: 100%;
    background: #ffffd;
    transition-duration: 2s;
    transition-property: transform;
}
.leave {
    transform: translateX(-100%);
}

录制性能日志如下图所示:

可见,仅仅是在动画开始和结束两个时间点触发了绘制,而布局则完全没有触发。这样一来,性能就有了很大的提升。但是,这里还有两个疑问:

为什么transform动画过程没有触发布局和绘制?

为什么动画开始前触发了两次绘制,动画结束之后触发了一次绘制?

要回答这两个问题,就得了解合成层。

合成层

当满足某些条件的时候,元素在渲染时会被分配到一个独立的层中进行渲染,只要该层的内容不发生改变,就不会触发绘制,浏览器会直接通过合成形成一个新的帧。常见的提升为合成层的条件包括:

对opacity或transform应用了animation或transition;

有 3D transform ;

will-change设置为opacity或transform。

很明显,上一节的transform位移动画满足了第一个条件。所以整个动画的渲染过程是这样的:

动画开始时,由于div.page被提升为独立的合成层,所以它要重新绘制;而document所在层相当于少了一块内容,也得重新绘制;

动画过程中,div.page没有其他变化,所以不触发布局和绘制;

动画结束后,div.page不再是独立的合成层,回到了document所在层,所以document又重新绘制了一遍。

如果让div.page一直在独立的合成层中渲染,则可以省掉上述过程中绘制的环节。在样式代码添加「will-change: transform」:

.page {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    min-height: 100%;
    background: #ffffd;
    transition-duration: 2s;
    transition-property: transform;
    will-change: transform;
}

录制性能日志如下:

可见,已经不存在绘制的步骤了。

顺带一提,Chrome开发者工具中有一个Layers面板,可以方便地查看页面上合成层以及成为合成层的原因。

(注意:由于低版本浏览器不支持will-change,所以实际应用中,如果想把元素提升到独立的合成层中渲染,可以用「transform: translateZ(0)」)

不透明度

众所周知,不透明度就是通过opacity样式来控制的。那么opacity的变化是否会触发布局和绘制呢?把样式代码修改如下:

.page {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    min-height: 100%;
    background: #ffffd;
    transition-duration: 2s;
    transition-property: opacity;
}
.leave {
    opacity: 0;
}

录制性能日志如下图所示:

在常规认知中,opacity的变化并不会导致元素位置和尺寸的变化,理应不会触发布局。但上述过程中确实触发了一次布局,表现较为诡异。接下来给div.page添加「will-change: opacity」使其一直在独立的合成层中渲染。录制性能日志如下:

可见,还是会触发一次绘制。而针对这「一次的布局」和「一次的绘制」,我进行了进一步的实验,得出的结论是:opacity从1(包括未设置的情况,下同)变更到小于1,以及从小于1变更到1,都会触发布局和绘制;即使在独立的合成层中渲染,也只能省掉布局,无法省掉绘制。

由于在opacity动画过程中从1到小于1的变更只会有一次,所以上述的布局和绘制都只触发一次。

位移和不透明度

同时使用两种动画,修改样式代码如下:

.page {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    min-height: 100%;
    background: #ffffd;
    transition-duration: 2s;
    transition-property: transform, opacity;
}
.leave {
    transform: translateX(-100%);
    opacity: 0;
}

按照前文的描述,动画过程会触发:

一次布局,在动画开始时触发,由opacity引起;

两次绘制,在动画开始时触发,因opacity以及提升为独立合成层引起;

由独立合成层回到document所在层时引起。

倘若加上「will-change: transform, opacity」,使div.page一直在独立的合成层中渲染,则只触发一次绘制,由opacity引起。

然而,创建一个新的合成层并不是免费的,它会导致额外的内存开销。在单页应用中,应用页面过渡动画的元素是页面的最外层容器,包含了该页面所有内容结构。如果让其长期在独立的合成层中渲染,那内存的消耗是非常大的。

所以,可以仅在动画过程中让其在独立的合成层中渲染,而在其他情况下则维持常规状态。

transform和fixed的冲突

如果用transform实现页面过渡动画,想必大家都遇到过一个问题:页面上固定定位的元素,其位置变得不太正常了。

下面通过一段代码模拟页面进入的过程,来演示这个问题:








运行效果如下:

可以看到,固定定位的黄色元素是在动画结束后才突然出现的。那在这之前它跑到哪去了呢?

如果给一个固定定位元素的任意一个祖先元素设置样式「transform」或者「will-change: transform」,那么该元素就会相对于最近的设置了上述样式的祖先元素定位。

因为div.page的高度设成了150%,所以,在动画过程中,黄色元素实际上是跑到了页面的最底下(超出了浏览器可视范围)去了。而在某些比较旧(如 iOS 9 的Safari)的移动端浏览器中,问题更为严重,固定定位的元素可能会消失掉再也不出现。

网上能查到的解决方案有两种:

通过绝对定位模拟固定定位。虽然是可行的,但是在移动端浏览器内,交互上会有一些细节问题,而且元素内部的滚动很容易与页面滚动冲突。

把固定定位的元素放到应用transform动画的元素外。但这对使用「Vue.js」这类框架开发的单页应用来说可行性较低,因为在这类框架中,一个页面就是一个组件,多带带把页面中的某个元素抽离出来是比较麻烦的。

所以,这里介绍第三种方案——在页面过渡动画结束之后(此时transform样式已被移除,不再影响fixed),再让固定定位的元素插入到页面容器。并且,为了让它的出现显得不那么突然,增加缓动动画。代码主要修改点如下:

@keyframes kf-move-in {
    0% { transform: translateY(100%); }
    100% { transform: translateY(0); }
}
.move-in {
    animation-name: kf-move-in;
    animation-duration: 0.45s;
}

运行效果如下:

这样一来,整个交互就较为友好了。这同时也说明:技术上的问题,不一定只能通过技术去解决,也可以从交互上去寻求解决方案。

参考文献

《渲染性能》

《坚持仅合成器的属性和管理层计数》

《无线性能优化:Composite》

本文同时发布于作者个人博客: https://mrluo.life/article/de...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/114317.html

相关文章

  • 动画大学

    摘要:在样式代码添加录制性能日志如下可见,已经不存在绘制的步骤了。下面通过一段代码模拟页面进入的过程,来演示这个问题运行效果如下可以看到,固定定位的黄色元素是在动画结束后才突然出现的。 对于移动端的Web单页应用来说,为了达到媲美原生应用的效果,页面过渡动画是必不可少的。常用的页面过渡动画包括: 位移——当前页向左侧或右侧水平移出可视区,下一页由反方向移入可视区。 不透明度变化——当前页淡...

    trilever 评论0 收藏0
  • CSS创始人之一Bert Bos:CSS只是进化的一部分

    摘要:在的发展过程中,是最早与之父合作的人之一。问您认为中国的开发者虽然起步晚,但是现在已经赶上了是的。但是我知道,它们只是进化的一部分。第一个最主要的原因就是要保护。 非商业转载请注明作译者、出处,并保留本文的原始链接:http://www.ituring.com.cn/article/194473 Bert Bos是一位计算机科学家,他也是CSS的创始人之一。在CSS的发展过程...

    tinyq 评论0 收藏0
  • 记录一下自己的春招,唯品会、360、京东offer已收、腾讯offer_call已达!!!

    摘要:春招结果五月份了,春招已经接近尾声,因为到了周五晚上刚好有空,所以简单地记录一下自己的春招过程。我的春招从二月初一直持续到四月底,截止今天,已经斩获唯品会电商前端研发部大数据与威胁分析事业部京东精锐暑假实习生的腾讯的是早上打过来的。 春招结果 五月份了,春招已经接近尾声,因为到了周五晚上刚好有空,所以简单地记录一下自己的春招过程。我的春招从二月初一直持续到四月底,截止今天,已经斩获唯品...

    freewolf 评论0 收藏1
  • 一个白的四次前端面试经历

    摘要:下面具体说一说四次面试经历,已经问到的问题,现在就做一次总结。第四次面试第四家公司真的就是高大上了,在腾讯的旁边,先不说面试,先说腾讯,真的就是当时内心挺害怕的。有点不好意思的说就是当时站在腾讯大楼面前腿是有些瑟瑟发抖的。 前言 做一个自我介绍,本人男,爱好女。曾以为自己可以改变世界,没想到被世界无情的摧残。来深圳之前那种找工作少于 1W 少跟我谈,变成了收到 offer 了 4000...

    陈伟 评论0 收藏0

发表评论

0条评论

Bryan

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<