本文做者:任家乐html
原创声明:本文为阅文前端团队 YFE 成员出品,请尊重原创,转载请联系公众号 ( id: yuewen_YFE ) 获取受权,并注明做者、出处和连接。前端
曾写过一篇性能优化 “ 长篇报告 ” 「 checkbox 美化引起的蝴蝶效应 」 ,也曾感叹 CSS 对渲染的影响是如此大,也许深化记忆点的代价就是被同一块石头绊倒2次 ?是的,性能优化“报告”第二弹来了,但愿本篇文章能够在优化页面性能上给你们提供一些思路。web
某些 CSS 属性或 JS 操做,看似简单却易隐藏陷阱?chrome
磨刀不误砍柴工,性能工具的亲密接触老是必不可少的。浏览器
同是浏览器,渲染层面的表现却如此不一样?性能优化
问题始于一个 “ 红色性能炸弹 ” ,瞄准的是 Webnovel「起点海外版」 PC 站点阅读页,关键字:Edge 浏览器、CPU、RAM。Webnovel 的国外用户指出,在 Edge 浏览器下,阅读页 CPU 消耗的很是大,他的腿甚至感觉到了笔记本电脑的灼烧。异步
Webnovel 阅读页,为了更趋近于完美的阅读体验,咱们作了:预加载上下章、支持上下键切换章节、无刷新跳章节等优化,在阅读过程当中,你甚至不会看到内容加载过程当中的 loading 。然而面对此次的性能突击,这些优化在 Edge 浏览器下显得如此眇小...而对因而否值得作这次性能优化,Google Analytics 则给出了答案:chrome-devtools
「 Webnovel 浏览器占比一览」工具
Edge 浏览器 TOP4 (2.53%)的占比状况已不容忽略!看来注定免不了和 Edge 的一场针锋相对了,立马去 Edge 浏览器上一探究竟~布局
定位问题以前,心里有个声音在提醒我:是 Google 广告脚本的问题!由于前不久阅读页引入了 Google 广告,而且针对广告的展现及隐藏作过一些逻辑的调整,带着目的首先从 JS 开始分析。
我是用 Edge 浏览器自带的性能工具来进行分析的,因为 Edge 只能在 win10 系统上运行,我工做中用的是 iMac 因此几乎没有使用过 ,也只能回家 “踢走” 老公倒腾他专门打游戏的台式机了,其 Edge 浏览器版本:42.17134.1.0。
打开 Edge 开发者工具,性能工具的入口映入眼帘。
「 Edge 开发者工具 」
切入正题:「 访问 Webnovel 阅读页 - 打开“ 性能 ” 标签栏 - 按下运行按钮 」。为了还原用户的使用场景,我也认真的滚动浏览了十几章。运行的结果:
「 首次性能工具的结果图 」
这满屏的原谅色,看到这张图,答应我请第一眼就确认是 “渲染” 的问题好吗?请立马注释 CSS 一探究竟好吗?但我并无作到。
继续观察,发现性能工具中有2个子标签栏: 时间线、JavaScript 调用堆栈。调用堆栈里很清楚的指明了 CPU 占用率最高的2项(下图),果真出自于 Google 广告的 osd.js 脚本。
「 首次性能工具结果图之 JavaScript 调用堆栈 」
不能慌,证据还不够确凿。Edge 的性能工具其实能够精肯定位到 JS 中的问题代码片断,在红框区域点击鼠标右键,也能够定位到问题源码,这一点 very good!感受离事实的真相更近了一步。
「 追溯代码问题细节图 」
「 问题代码片断1 」
「 问题代码片断2 」
显而易见,Google osd.js 脚本设置了计时器,不停地获取 HTML 节点的 clientWidth 和 clientHeight,因此祸根源于此?
Facebook 的工程师 Stoyan 在他的博客中曾提过, 浏览器是聪明的,为了节约资源、不频繁触发 Repaint(重绘)、Reflow(重排),它会将一系列脚本中须要执行的 UI 改动放在一个队列中一块儿执行,而不是每一个改动都单独执行从而引起无数次的 Repaint、Reflow。BUT!有一些操做会打乱它原有的 plan 和秩序:
浏览器对于这些操做,会“ 很是及时地 ”给予最准确实时的答案,所以会触发 Reflow,例如从新计算样式、更新图层等。要知道 Reflow 相比 Repaint 对渲染的影响更大、损耗更多。
demo time - 获取 clientWidth 是否会触发没必要要的渲染?
功能简述:
「 demo 动图 」
过程 |
结果 |
阶段一:before “ click me ” |
未触发 Reflow 等渲染事件 |
阶段二:after “ click me ” |
触发了额外的渲染(recalculate style、update layers) |
阶段三:after “ no timer ” |
没有触发 Reflow 等渲染事件 |
Chrome Performance 工具则更直观地描述了表格中的结果(红框中处于阶段二,清楚表现了额外的渲染):
为了更突出差别,我将水平移动的 div 数量增长到了 20 个,Recalculate Style 的数据相差了 70ms:
「 渲染数据的差别:动画期间获取 clientWidth(上),动画期间不作任何操做(下) 」
demo 的结果验证了在 Chrome 浏览器中,动画期间获取 clientWidth / clientHeight 确实会引起浏览器的 Reflow,随着动画复杂度的增长以及元素的增长,该影响只会更大,进而增长了没必要要的渲染消耗。
既然阅读页 CPU 飙升极有多是由于 Google 的 osd.js 不断获取 HTML 节点的 clientWidth / clientHeight 意外触发了屡次 Reflow ,那么解决问题的第一步便是删除 Google 广告。我利用 fiddler 代理 JS 脚本到本地,删除了广告相关的代码。
但其结果尽和个人猜想彻底不一样,删除广告并无使页面性能变得更好,有点没有头绪...真的不是 Google 广告的问题?
继续用 Edge 性能工具对一相似网站进行测试,由于这个网站一样使用了 Google 广告,而且相比于 Webnovel 阅读页首次初始化1枚广告,它的首页尽然同时加载了 4 枚广告。然而结果却没有渲染上的问题(以下图),网站 CPU 利用率一栏甚至彻底没有飚绿的痕迹。
太过坚信设想反而偏离正轨?前期我太坚信是 Google 广告脚本的问题,既然以上 2 次的尝试都证实不是 Google 广告脚本的锅,那么是时候转换方向了。
曾经写的「 checkbox 美化引起的蝴蝶效应 」带给了我一些灵感:CSS 在渲染性能上有着相比 JS 更不容忽视的影响力。结合前面提到的 demo 结论,不禁得猜想也许阅读页的性能问题是因为某些 CSS 属性或动画与 Google 广告脚本中的 clientHeight、clientWidth 产生了相互做用,引起了额外的渲染。此时尝试注释全部样式,最终绿色居然消失了,这说明极有多是 CSS 的锅。
「 CPU 占用率中消失的渲染 」
继续从性能工具中寻找更精确的答案,意外发现 Edge 性能工具里的 “ 时间线 ” 标签栏里能够捕捉到不少有用的信息,甚至能够找到样式相关的条目?展开便可定位到目前引起渲染的 DOM,这一点太赞了!
这对问题的定位有了很大的帮助,由于我很清楚地看到了有问题的 DOM ,也就是咱们写的 loading 组件。
Webnovel 阅读页确实存在几枚隐藏的 loading,分别存在于章评弹窗中、目录弹窗中、各章节内容之间。
「 章评弹窗、目录弹窗 」
「 章节之间的 loading 」
隐藏 loading ,是为了简化逻辑,将其“ 埋伏隐藏 ” 在各自须要的地方,在获取所需的异步数据以前,能够第一时间将 loading 展现给用户,获取到须要的数据后再将 loading 隐藏,这样的交互逻辑是合理的,那么隐藏的 loading 为何会对渲染产生影响?Webnovel 阅读页弹窗中的 loading 是用 visibility: hidden 来隐藏的,这样的方式不对?
看不见 loading 的踪影,它却依然能对渲染产生这么大的影响?你认为正确地隐藏了其中的 loading,而 loading 却实际地存在于文档流中,它的每个动画,都会影响同在文档流中的其余节点。
如此来看,性能更优的应该是 display: none,不在文档流中就不会影响其余的元素。但每一个被创造出来的 CSS 属性必然有创造它的道理,若是 display: none 是最推荐的方式,为何还会有 visibility : hidden 的存在?任何事物都存在两面性,例如如下场景中,visibility: hidden 竟协助某大神解决了性能问题。
Google 的工程师 Jake Archibald 在他的博客「Solving rendering performance puzzles」中,用一个有趣的 SVG 动画验证了 Layout (布局)在网页性能中举足轻重的地位。随着动画中 SVG 文字的不断变化,动画的帧率由原来最优的 60fps 最终下降到不足于 10fps。Chrome 的 Performance 工具揭示了其主要因素是产生了大量的 Layout 消耗(称为 Layout Thrashing or Reflow )。
此时 Jake 利用 visibility: hidden 解决了 Layout 消耗过多的问题,他提到,visibility: hidden 的元素,由于其在文档流中占据一席之地,于是将其设置为 visibility: visible 并不会产生 Layout 上的消耗,只会产生额外的 paint,借助此想法,他事先将 SVG 中的全部字符分别放置于 <textpath> 节点中的子节点 <tspan> 中,并利用 visibility: hidden 进行隐藏:
「 DOM 中隐藏的 SVG 文字 」
随后逐次将 <tspan> 的 visibility 属性值变动为 visible ,字符就依次展现了出来,对比下,他原来的作法就真的是很是粗暴了,即:将全部字符存于单独的一个节点中,每次截取固定长度的文字设置为 <textPath> 的值。大量的重置 textpath 节点值最终引起了浏览器每一个字符的从新布局( layout )。
所以并不能笼统地下定论 visibility: hidden 不如、或优于 display: none ,只能说在不一样的场景下他们都有各自的优点。
显然,loading 动画用 visibility: hidden 进行隐藏确实产生了不少额外的渲染消耗。修复这个问题的方案不少,例如,能够将 loading 的隐藏方式改成 display: none,或者是在打开弹窗的时候将 loading 实时添加到 DOM 中,最终为避免改动弹窗组件的 CSS 产生的全局影响,咱们在打开弹窗的时候添加了 loading 的 DOM 节点,关闭弹窗时则将其移除。一周后用户的反馈也证实了此方案是有效的(下图)。
「 用户的反馈 」
Webnovel 阅读页的问题虽然得以解决,但可能咱们都会感到疑惑,为何这样的性能问题在 Chrome 浏览器上并无发生,或者说表现不明显?这里我借助「 visibility: hidden 有利有弊」一节中提到的 Jake 的 SVG 动画作了一波测试。性能工具简单探测一下,发现未优化前的动画其性能和 Chrome 同样没法直视:
「 Edge 浏览器性能结果 」
优化后的动画其表现和 Chrome 也相差甚远,直观上的对好比动图所示,Edge 浏览器(第二张图)上的动画比 Chrome(第一张图) 缓慢许多。
「 上 Chrome, 下 Edge 」
Chrome Performance 工具中能够证实 Jake 的优化策略确实将 Layout 产生的消耗下降了不少,由原来的 TOP3 变为最后一位 :
「 优化前,优化后 」
但 Edge 性能工具的结果甚至比不上优化以前,渲染上未下降,而且出现了不少 JS 上的 CPU 冲击(图中橘色区域):
「 优化前 」
「 优化后 」
优化后渲染层面的开销甚至大于优化前的开销(在动画运行差很少时长的状况下):
「 优化前、后 UI线程数据 」
在此 demo 的表现上,Edge 浏览器对 SVG 动画的渲染状况与 Chrome 截然相反,更重要的是,Chrome 对于 Layout(布局)和 Paint (重绘) 在渲染性能方面的 “ 权衡 ”彷佛和 Edge 是不同的,下降大量的 Layout 开销确实可使 Chrome 中的动画更为流畅,但这在 Edge 中却行不通,甚至表现相反。
Chrome 和 Edge 浏览器在 SVG 动画上的表现差别提供了我一些新的思路,再次回到 Webnovel 阅读页,咱们的页面中确实使用了数量很多的 SVG,我尝试着将 SVG 的 display: inline-block 改成 display: none 后,CPU 居然迅速降了下来,性能上也获得了缓解(以下图),感到很是惊喜。
「 注释 SVG 样式以前 」
「 注释 SVG 样式以后 」
但目前性能问题已然解决,而且 Webnovel 全站都使用的了 SVG ,此时若全局替换掉 SVG 未免有些粗暴,加之 Jake 的 demo 还不足以说明过多的 SVG 影响了页面的性能,所以该方案并无实施,但这确实能够做为 Webnovel 阅读页性能问题的另外一个突破点,后续还须要花更多的时间来探索这其中的奥秘。
固然,问题的修复老是须要总结一下:
性能优化本就是积极尝试和勇于试错的过程,正所谓积跬步以致千里,点滴的积累和探索不是为了产出 “ 最佳性能优化方案 n 条 ” ,而是为了能更好地提炼定位问题的思路、扩展认知范围、同时造成一种对待性能瓶颈不害怕的态度,但愿咱们均可以作到微笑面对性能瓶颈。