记一次惊心动魄的前端性能优化之旅

15

欢迎一起交流

欢迎关注我的个人公众号,不定期更新自己的工作心得。
图片描述

正文从这里开始

题记:可以把这篇文章看成一次事故记录,一次线上事故。此刻我能坐在这里写下这篇文章,要感谢我司没有开除我,或者说我解决了这次性能问题,所以我有机会写下这篇文章。

起因

我司全球购业务改版,将原生的全球购页面采用Hybird模式开发,这是我司H5页面第一次出现在以及入口位置,作为开发者显得很是兴奋。

第一阶段 - 问题初显

问题

根据设计稿,如期完成工作,测试也已准备好,部分Android低端机中显示出卡顿现象,出于对自身技能的肯定,误以为是硬件性能问题,并未引起过多关注。

方案

未引起重视,并没有做出相应处理。

第二阶段 - 引起重视

问题

上到pre环境后,发现部分iPhone机型出现崩溃,初步排查在iOS 8.4系统上DOM节点高度达到10000px以上奔溃率100%,其他8-9之间的系统版本当分页数据使得DOM达到10000px以上快速滚动会出现卡顿,9以上iOS系统与Android4.4以上系统均表现良好。定位问题后,迅速进行更多特殊机型与相应系统的测试,期间借遍了公司几百位小伙伴的手机,在此向他们表示感谢。

你问我为什么,我只能说测试环境没有这么多数据,至于为什么没有?呵呵,你懂得!

方案

  1. iOS将UIWebview切换为WKWebview,WKWebview为iOS在8以上系统中新加入的一个高性能Webview,具体哪里好,可以点这里 这里 还有这里 ,由于iOS已经发版(对的,在H5页面还在pre环境测试的时候,iOS已经发版了)这个方案只能作为下一个版本了

  2. 临时降低数据显示总量,同时寻找性能瓶颈(这里要和运营的同学说声sorry)

实施

针对iOS 8以下以及8.4系统 Android 4.4以下系统做自动降级处理。

一些经验

  1. 测试不能仅仅做功能测试,还要做性能测试

  2. 开发人员常使用chrome、Firefox等浏览器的开发工具进行开发,这里要注意的是:浏览器开发工具只能模拟手机UI与交互,其性能要远远强于手机环境

  3. 开发过程中常用微信、QQ、手机浏览器进行测试,这是不可取的,因为最终环境不一,容易给自己造成错觉,觉得在微信 QQ的Webview中没问题,就OK了

  4. deadline 真的要好好定。很多人认为H5很灵活,并不依赖于iOS发版,可以将测试定在iOS发版的那几天进行。其实这真的是巨大的错误,后面我会说明。

第三阶段 - 全面升级

经过降级后,仍然存在很多问题,同时我们也在抓紧研究后续升级方案。经过协调,将系统切回了老版本暂时使用原生替代H5(感觉很受打击~)。

问题

  1. 数据量不足,对运营影响很大

  2. 对降级机型的体验很差,数据统计显示,受影响的机型还是有一定份额

  3. 由于此次H5是存在于以及目录,所以设计上是常驻内存以保证体验,UIWebview的内存一直得不到释放,同事系统中其他页面也使用到了UIWebview,长时间使用仍然会出现崩溃。

  4. 距离iOS和Android下一次发版还有一段时间,不可能一直降级

无论如何解决性能问题,这是唯一目标!

经过一个星期枯燥而漫长的努力与实验,从以下几个方面彻底解决了性能瓶颈;

方案

1. 去除iScroll

iScroll是我们内部框架中引入的一个第三方组件,官方的介绍是「Smooth scrolling for the web」,奈何在大数据量面前性能确实令人堪忧。如果数据量少,还是推荐使用的。

2. 高性能DOM渲染

在对比了竞品,和天猫、JD等知名厂商H5实现后,确实郁闷了一段时间,同样的数据量为什么别人可以做到?最后得到两个结论:

  1. 别人DOM节点没我们复杂,由于我们采用了内部研发的m-fast框架,框架顶层就确定了DOM结构的复杂性,例如:框架预设了整个布局为上、下、左、右、中的布局,即使我的页面只是一个流式布局。

  2. 别人H5页面没有图片轮播!对,你没看错,一个图片轮播居然对性能影响如此之大

居于上面这两点,我开始了DOM性能研究

像素渲染流水线

m-fast框架采用模板预编译技术,将页面模板编译为js文件,在通过异步加载的方式载入,所以,网页的渲染大致符合上面这五个步骤

  • JavaScript。一般来说,我们会使用JavaScript来实现一些视觉变化的效果。比如用jQuery的animate函数做一个动画、对一个数据集进行排序、或者往页面里添加一些DOM元素等。当然,除了JavaScript,还有其他一些常用方法也可以实现视觉变化效果,比如:CSS Animations, Transitions和Web Animation API。

  • 计算样式。这个过程是根据CSS选择器,比如.headline.nav > .nav_item,对每个DOM元素匹配对应的CSS样式。这一步结束之后,就确定了每个DOM元素上该应用什么CSS样式规则。

  • 布局。上一步确定了每个DOM元素的样式规则,这一步就是具体计算每个DOM元素最终在屏幕上显示的大小和位置。web页面中元素的布局是相对的,因此一个元素的布局发生变化,会联动地引发其他元素的布局发生变化。比如,``元素的宽度的变化会影响其子元素的宽度,其子元素宽度的变化也会继续对其孙子元素产生影响。因此对于浏览器来说,布局过程是经常发生的。

  • 绘制。绘制,本质上就是填充像素的过程。包括绘制文字、颜色、图像、边框和阴影等,也就是一个DOM元素所有的可视效果。一般来说,这个绘制过程是在多个层上完成的。

  • 渲染层合并。由上一步可知,对页面中DOM元素的绘制是在多个层上进行的。在每个层上完成绘制过程之后,浏览器会将所有层按照合理的顺序合并成一个图层,然后显示在屏幕上。对于有位置重叠的元素的页面,这个过程尤其重要,因为一旦图层的合并顺序出错,将会导致元素显示异常。

frame-no-layout

如果你修改一个DOM元素的“paint only”属性,比如背景图片、文字颜色或阴影等,这些属性不会影响页面的布局,因此浏览器会在完成样式计算之后,跳过布局过程,只做绘制和渲染层合并过程。

frame-no-layout-paint

如果你修改一个非样式且非绘制的CSS属性,那么浏览器会在完成样式计算之后,跳过布局和绘制的过程,直接做渲染层合并。这种方式在性能上是最理想的,对于动画和滚动这种负荷很重的渲染,我们要争取使用这种渲染流程。

最终得出以下几点改进

1. 简化浏览器重绘的复杂度

绘制,是填充像素的过程,这些像素将最终显示在用户的屏幕上。通常,这个过程是整个渲染流水线中耗时最长的一环,因此也是最需要避免发生的一环。

  1. CSS属性中,除了transform和opacity之外,修改任何属性都会触发绘制

  2. 如果布局被触发,那么接下来绘制一定会被触发。因为改变一个元素的几何属性就意味着该元素的所有像素都需要重新渲染!

  3. 如果改变元素的非几何属性,也可能触发绘制,比如背景、文字颜色或者阴影效果,尽管这些属性的改变不会触发布局。

2. 减小浏览器重绘区域

绘制并非总是在内存中的单层画面里完成的。实际上,浏览器在必要时将会把一帧画面绘制成多层画面,然后将这若干层画面合并成一张图片显示到屏幕上。

layers

这种绘制方式的好处是,使用tranforms来实现移动效果的元素将会被正常绘制,同时不会触发对其他元素的绘制。

在页面中创建一个新的渲染层的最好方式就是使用CSS属性will-change,Chrome/Opera/Firefox都支持该属性。同时再与transform属性一起使用,就会创建一个新的组合层:

.moving-element {
  will-change: transform;
}

对于那些目前还不支持will-change属性、但支持创建渲染层的浏览器,比如Safari和Mobile Safari,你可以使用一个3D transform属性来强制浏览器创建一个新的渲染层:

.moving-element {
  transform: translateZ(0);
}

但需要注意的是:不要创建太多的渲染层。因为每创建一个新的渲染层,就意味着新的内存分配和更复杂的层的管理。

3. 避免大规模、复杂的布局

上面提到的,由于框架的限制,页面布局相对需求来说复杂很多。开发中应该尽量避免复杂的DOM结构,复杂的DOM结构更容易引起大面积的重绘。

4. 优先使用渲染层合并属性

渲染层的合并,就是把页面中完成了绘制过程的部分合并成一层,然后显示在屏幕上。

使用transform/opacity来实现动画效果,目前只有transformsopacity这两个属性不会触发浏览器的布局和绘制,对网页元素这两个属性的修改会直接触发渲染层合并。

5. 优化JavaScript的执行效率
  1. 对于动画效果的实现,避免使用setTimeout或setInterval,请使用requestAnimationFrame

  2. 把耗时长的JavaScript代码放到Web Workers中去做。

这里可以使用Chrome DevToolsTimelineJavaScript Profiler来分析JavaScript的性能。

写在最后

性能优化是一门做减法的艺术。我们首要要尽力简化页面渲染过程,然后要使渲染过程的每一步都尽量高效。

THKS Google

CSS Triggers

render performance

simplify paint


如果觉得我的文章对你有用,请随意赞赏

你可能感兴趣的

11 条评论
九死蚕传人bo · 2016年05月18日

哈哈,我也遇到过一次加载更多结果到最后卡顿性能问题,这样能够增加工作经验的好文就应该大力推荐。

+1 回复

airyland · 2016年05月18日

前排学习 :)

回复

Youwei 作者 · 2016年05月18日

多多交流才行啊

回复

Youwei 作者 · 2016年05月18日

哈哈哈,应该多多交流哈

回复

mlyknown · 2016年05月19日

由上一步可知,对页面中DOM元素的绘制是在多个层上进行的。 如果没有willchange或者translateZ,就以外这一个页面只有一个渲染层吗?

回复

Youwei 作者 · 2016年05月20日

现代浏览器基本上都是支持在多层上面进行渲染的,不过这是浏览器的内部机制,我们并不知道什么时候浏览器会分层渲染,或者说我们并不能控制浏览器的默认分层行为,所以在我们认为必要的地方主动使用 willchange 浏览器在此处便一定会分层渲染,其余的分层行为是根据浏览器内部机制来的。

回复

mlyknown · 2016年05月20日

0.0 理解了。 那所谓使用willchange触发gpu加速提高性能,是否就是因为willchange导致分成渲染?

回复

Youwei 作者 · 2016年05月20日

分层渲染的好处主要有两方面,1是提高渲染性能,2是最小化渲染范围(第二点也可以算作是提高性能)

回复

Youwei 作者 · 2016年05月20日

至于是否使用到了GPU,这块我并没有相关数据证明是willchange导致

回复

风烟 · 2018年05月01日

我这里也遇到的uiwebview的问题,是来回点击次数过多,就会出现cpu暴涨到99%的情况,导致页面卡死,换成wkwebview就没有问题了,现在初步定位也是列表的问题,可以一块交流一hybird模式开始经验,求wx

回复

dragonจุ๊บ · 7月31日

我用的better-scroll实现滚动加载,数据到了700田的时候开始出现卡顿,醉了

回复

载入中...