前端界面性能优化指南

界面渲染基本流程

前景知识,了解页面是如何渲染出来的,才知道从何处下手优化渲染性能。

像素管道

  • JavaScript 。一般来说,我们会使用 JavaScript 来实现一些视觉变化的效果。比如用 jQuery 的 animate 函数做一个动画、对一个数据集进行排序或者往页面里添加一些 DOM 元素等。当然,除了 JavaScript,还有其他一些常用方法也可以实现视觉变化效果,比如: CSS Animations、Transitions 和 Web Animation API。
  • 样式计算 (Style)。此过程是根据匹配选择器(例如 .headline.nav > .nav__item)计算出哪些元素应用哪些 CSS 规则的过程。从中知道规则之后,将应用规则并计算每个元素的最终样式。
  • 布局 (Layout)。在知道对一个元素应用哪些规则之后,浏览器即可开始计算它要占据的空间大小及其在屏幕的位置。网页的布局模式意味着一个元素可能影响其他元素,例如 <body> 元素的宽度一般会影响其子元素的宽度以及树中各处的节点,因此对于浏览器来说,布局过程是经常发生的。
  • 绘制(Paint)。绘制是填充像素的过程。它涉及绘出文本、颜色、图像、边框和阴影,基本上包括元素的每个可视部分。绘制一般是在多个表面(通常称为层)上完成的。
  • 合成(Composite)。由于页面的各部分可能被绘制到多层,由此它们需要按正确顺序绘制到屏幕上,以便正确渲染页面。对于与另一元素重叠的元素来说,这点特别重要,因为一个错误可能使一个元素错误地出现在另一个元素的上层。

上面每一个步骤都可能会产生渲染性能而造成卡顿,所以,确定代码触发了管道哪一部分十分重要。

页面如何从文件到用户看到的画面

  • 下载html文件以及相关的css文件并解析成Dom Tree
  • Dom+CSS生成Render Tree,也就是Compputed style里的内容;那么干了什么呢?1)解析不显示的dom-node,将其从tree里删除,2)解析伪类的content,插入新的dom-node。注意里面去掉了与页面样式无关的<header><script>标签,因为这些在获取样式之后和dom并没有任何关联。
  • 根据Render Tree + CSS解析出Layout,也就是几何内容、盒子关系。当其中一个环节更改,影响相关元素,并且将影响从上(根)至下(叶子)传递而形成布局变化的过程也被为Reflow ,也就是回流
  • Painting ,这时将Render Tree 光栅化绘制,以便填充为图层

    • 光栅化就是将矢量图形 (左)通过光栅器填充为单个像素,形成位图(右)
    • 那么回到刚才的简易代码,光栅器是如何执行的呢?

    • 最后,绘制出一个图层

  1. Composite,将解析出的图层进行增量渲染。同一图层,即便是微小的改动,也会整个渲染,而组合这些图层便是性能优化中的关键。

帧率优化-Composite

那么影响帧率的关键就在于"改动"都影响了哪些步骤并引起了连锁反应。

像素管道阶段触发表

CSS Triggers

让我们在浏览器拉爆网页性能吧

下面是两个例子介绍如何使用性能检查工具(Performance)调试和修改引起性能问题的代码

我们直接深入代码优化,以github的一个仓库为例子[仓库地址](udacity/news-aggregator at gh-pages (github.com))

跑起代码之后,我们看到的是这样一个界面:

减少无意义的几何改变

  1. 打开F12 ,选择Performance 标签,按Ecs 打开控制台,添加Rendering标签
  1. 滚动录制一段时间线(以前叫TimeLine现在叫Performance),可以看到帧率及其糟糕,有大量的红色标记
  1. 展开渲染细节我们发现有大量的强制布局同步引起的回流(Force synchronous layout -> Force Reflow),而调用堆栈显示是colorizeAndScaleStories 引起的
  1. 进入到colorizeAndScaleStories内部,我们发现有js在实时的更改这dom的宽高

而从上面的触发对比表格里我们知道Width,Height是全触发

这个函数的功能是计算每一条新闻的位置,将屏幕下半截的左边计数圆盘改变颜色、大小并且在改变之后保持行内上下居中。从需求角度讲根本没卵用,但是对页面的性能有极大的伤害,那就直接干掉,然后看效果。

对比刚才每帧一个回流操作简直赏心悦目,帧率也明显提升。

动画尽量使用CSS实现

点击一条消息,查看详情(消息详情像menu一样滑入滑出)

我们看到这里有异常多的Layout引起的回流。

展开细节我们可以看到这里是由一个叫做animate方法引起的。

不看不知道,一看吓一跳,里面竟然用js递减确定位置,还用的是setTimeout,即便是如此也应该用requestAnimationFrame是吧。

  • 过一遍完整逻辑,通过点击消息调用showStory 来调用showStory,点击 图标X调用hideStory,两个都调用animate对left进行加减控制滑块位置。
  • onStoryClick时,bing对应的id到showStory和hideStory,并setTimeout执行,在执行期间,创建一个滑块的Dom插入body(但是没有删除,这会浪费性能和内存)。

解决办法就很简单了:

  • 直接创建一个节点以供选择新闻内容后进行内容填充。
  • 动画改用css实现。

最终结果表示,滑入滑出的动画也能60帧运行了,丝滑无比。

滚动陷阱

元素超出边界是一个很严肃的问题,超出时,滚动会因为未知高度(子>父高度,但是父没计算滚动条,滚动条在别的元素上,所以每次滚动都要repatin)强制重绘(虽然可能仅是composite)

当子级高度大于父级时,通过overflow-y: auto使子级滚动条正常显示在父级;或者用魔法,transform: translateZ(0) 将子级置于独立layer渲染

使用 最底部的元素 bottomElement.scrollIntoView({ behavior: "smooth" }) 尽力规范控制变量,用Performance监听渲染,并选取帧率变化的 825ms区间,选取中间位置的Task最低值

不优化前

优化后

因为不需要重新计算子级高度并进入composite,每次task平均时间降低到1.3毫秒(较深的2毫秒左右时gif和video的影响),Paint总耗时大大降低

总结

在编写交互和动画时,很容易就能触发重排、回流和重绘操作,以造成糟糕的性能体验。熟悉渲染的各个阶段,并且知道如何在各阶段避开错误的写法就能写出流畅丝滑的页面。

资料汇总

相关推荐
careybobo11 分钟前
海康摄像头通过Web插件进行预览播放和控制
前端
TDengine (老段)36 分钟前
TDengine 中的关联查询
大数据·javascript·网络·物联网·时序数据库·tdengine·iotdb
杉之2 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue
喝拿铁写前端2 小时前
字段聚类,到底有什么用?——从系统混乱到结构认知的第一步
前端
再学一点就睡2 小时前
大文件上传之切片上传以及开发全流程之前端篇
前端·javascript
木木黄木木3 小时前
html5炫酷图片悬停效果实现详解
前端·html·html5
请来次降维打击!!!3 小时前
优选算法系列(5.位运算)
java·前端·c++·算法
Alo3653 小时前
面试考点复盘(二)
面试
難釋懷4 小时前
JavaScript基础-移动端常见特效
开发语言·前端·javascript
还是鼠鼠4 小时前
Node.js全局生效的中间件
javascript·vscode·中间件·node.js·json·express