89

记一次前端性能优化的案例 - 吕大豹

 6 years ago
source link: http://www.cnblogs.com/lvdabao/p/7771763.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

记一次前端性能优化的案例

前两天遇到一个前端性能相关的bug,感觉还挺典型的,整理了一下解决过程和思路,写下来分享给大家。

场景是这样的,有一个答题的界面,可以播放音频、填空、提交答案,界面是长这个样子的:

520134-20171103110705810-569436228.png

看起来还挺简单吧,但是我们在手机上跑的时候,却遇到了以下问题:

1. 填完空后,提交按钮会由灰色变为蓝色(可提交状态),但是播放完音频后,却无法变蓝

2. 页面较长时,一边播音频一边滚动页面,会出现页面闪烁(短时白屏)

我的第一反应就是:出渲染bug了。因为在一些低端手机上,经常会遇到动态修改页面,渲染没有及时生效,出现花屏或者白屏的情况。

而修改这类bug并没有什么好方法,唯一管用的就是强制浏览器再重绘一次。常用的技术手段比如设置style.visibility="visible",或者是更新一下className。有时候这种“轻度重绘”起不到作用的话,还会修改背景色啦,或者先display:none然后在display:block,目的都是强制触发浏览器的reflow或者repaint,期望它能给渲染正常。

因此我不假思索使出老手段。但是在尝试各种强制重绘后,并没有解决问题1。这我也是能想通的,毕竟是不稳定的hack手段,不生效也是情有可原。

有人会问,是不是逻辑写的有问题啊?经我排查其实并没有逻辑问题,我们是用vue的:class绑定做的样式更新,此时该状态的变量已经更新了,而且class的属性值也更新了,只是视觉上没看到更新而已,还是渲染的问题没错。

此时我有一个胆大的想法:不会是vue的bug吧!猜测如下:vue内部更新class值的时候是不是有什么机制,导致浏览器在某些特殊情况下忽略了这次渲染。其实我并不愿意这样想,毕竟vue已经是一个很稳定的框架了,不至于有这么低级的问题吧,就算真有,应该早就暴漏出来了呀。

但是事实就摆在我眼前,而我还必须解决这个bug,所以还得想办法。

“要不,这里就不用vue绑定了。”

不用vue绑定,自己手动操作更新className。这实在是下下策,我甚至感到有点羞耻。因为我是有代码洁癖的,用vue本身就是为了不手动操作DOM,而现在,却要在用vue一气呵成的代码中插入一段手动操作DOM的代码。简直是一颗老鼠屎,破坏了整个代码的完美度。

上线要紧啊,忍了。于是我把提交按钮这里,改为了手动更新className。问题解决了,提交按钮乖乖变蓝,播放音频的动作不会影响到它了。大家夸我快速解决了问题,而我的良心隐隐作痛。

故事并没有结束,其实,重点才刚刚开始。直到遇到问题2,才让我开始重新审视这个问题。问题2是这样的:在页面高度特别长的时候,会有滚动页面的操作。当正在播放音频的时候去滚动页面,大概在播完音频的那个瞬间(页面还在滚),页面会发生一次严重的抖动,直接白屏一下,然后页面重新正常显示。

这下好了,没法用重绘hack,也没法再用手动操作DOM的恶心方法了。此时,不得不拿出调试利器了:chrome的devtool。在Rendering工具中,勾选Paint Flashing,它能够高亮页面被重绘的区域。

有发现了!在音频播放完毕的时候,提交按钮那块区域竟然发生了一次重绘。这怎么回事呢?它俩隔着老远,而且并没有父子关系,况且提交按钮还是绝对定位放在下面的。我想不到什么原因能让下边的提交按钮发生重绘。

重头来了,这时我打开了Layers工具,看到的景象让我大吃一惊。看下面的动图吧:

520134-20171102131249701-578158392.gif

音频播放组件那里的graphics layer竟然如此之乱,在开始播放的时候,发生了较多的层提升和层合并。更为奇葩的时,下方的提交按钮区域也跟着发生了层提升。如果你细细观察,还能看到右上角的那片绿叶子也发生了层提升,你这家伙跟着起哄什么啊。。。

这下问题的症结就比较清楚了,多余的层变动,导致意外的重绘。恰逢页面正在滚动,一下遇到了渲染瓶颈,就出现了闪烁。那么,是什么原因导致的层变动呢?

经过审查代码,查到了问题所在,总结如下:

音频组件的布局方式存在问题,左侧旋转的圆盘是右侧进度条的子元素,通过绝对定位给定到左侧的。并且其高度是大于父元素的,通过父元素的overflow:visible才得以完整显示。大家知道元素间的遮挡以及裁切都可能会生成新的提升层(graphics layer)。而且左侧的圆盘在播放时还会通过transform进行旋转动画,transform也会进行层提升,同时浏览器还会进行层合并的判断,将可以合并的合成一个graphics layer。而这个判断是全局进行的,也就是说页面底部的提交按钮也被计算在内,可能正好命中了某些规则,所以它也被提升为单独的层。

所以我把音频组件进行了重新的布局(减少遮挡与裁切),不让它产生那么多的提升层行为。

至于右上角那个绿叶子,我发现他的z-index为100,感觉根本不需要这么大,改为了2,工作良好,并且不会被提升了。大家知道z-index也是层提升的一个影响因素,很多同学经常随手就写一个很大的z-index,生怕自己的元素被别人盖住。这是一个很不好的习惯,没准哪天就给命中了层提升的规则,引发重绘了。

经过了上面的修改,再次打开Layers面板,发现此时的层已经很规整了,效果如下:

0?wx_fmt=gif

感觉很清爽了有木有。而在此修改之后,问题1的根本原因也定位到了(由于层提升而引发了重绘),并且顺势恢复正常。那段让我良心隐隐作痛的代码也可以删掉啦!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK