背景
,也就是,指的是网页渲染时,外部样式还没加载好,就以浏览器默认样式短暂地展示了部分内容,等到外部样式加载完成,又恢复正常的这个页面闪烁的过程。
看到网上有的文章说现代浏览器已经不需要关注的问题了,这其实是不对的,虽然现代浏览器针对首次绘制做了一些优化,但是代码上的不合理依然可以导致出现。
在这个盛行的时代,大部分情况下都不那么容易引起重视,但是有些时候,带来的影响仍然是不可忽视的。举个例子,想象一下,你在黑暗的环境下使用了一个黑暗的主题,打开了一个深色主题的页面,本来是很和谐的,却因为每次都会先在默认的白色背景下闪烁一下,非常影响体验。
原因分析
要了解的原因,首先我们要了解一下浏览器渲染的原理。
浏览器的渲染流程
值得注意的是,整个渲染过程是同步进行的。也就是说,浏览器一边解析,一边构建渲染树,构建一部分,就会把当前已有的元素渲染出来。如果这个时候外部样式并没有加载完成,渲染出来的就是浏览器默认样式了。
脚本和样式的执行顺序
会阻塞解析(parser blocking)
浏览器中的是在一个线程中执行的,所有的都是依次同步执行的。当浏览器解析到一个并开始执行时,就会阻塞后面所有的构建和渲染。
一般来说,现代浏览器在阻塞渲染的时候,都会提前加载所需的静态资源,如和脚本,但是此时并不会执行。
会阻塞渲染(render blocking)
当一个尚未加载完成时,浏览器会继续解析和构建,但是并不会渲染,因为渲染需要的渲染树是由和共同构建而成的。因此,这个时候页面的渲染会被阻塞,直到加载完成。
性能指标
首次绘制(FP,First paint),表示浏览器渲染任何在视觉上不同于导航前屏幕内容之内容的时间点。
首次内容绘制(FCP,First contentful paint),表示浏览器开始渲染内容的时间点。
一般来说,如果和同时发生,页面就不会出现闪烁。当然也有例外,如果发生的时候,所需的样式依然没有加载完成,那么依然会出现,这种情况一般发生于,不是通过标签加载的,而是使用动态插入的。
和发生的时机可以通过的来观察:
这里的是事件,其他的节点这里就不详细展开了。
绘制的时机
前面已经说过,浏览器的解析和渲染是同步进行的,只要有合适的和构建成了渲染树,就会渲染出来,触发浏览器绘制。这个过程都是在一个线程中进行,为了优化性能,同步的操作会被合并,只有当所有的同步操作完成后,构建的渲染树才会被渲染。
一个简单的例子:
对于一个简单页面,当加载完成,且所有的都同步解析完成,才会触发第一次渲染。也就是说,紧跟在后发生。
当加入之后,就变得不一样了。
当浏览器开始执行一个时,的构建会停下来,因为我们的脚本很可能对当前的进行查询和操作。所以这个时候,就会将已经构建好的渲染树先渲染出来。
值得一提的是,如果树的内容为空,浏览器会直接跳过本次渲染。
所以对于,更好的做法是在脚本中去动态创建顶层的容器,而不是写到中。如果是在先写一个动画提升体验就另说了。
如果触发了强制/,就会产生更多的绘制,即使之前的树为空,也有可能使提前。
多个标签放在中,会多次触发。原因和上面说过的一样,每次执行一个的时候,浏览器都会暂停树的构建,先把当前的渲染树渲染出来。所以如果前面的创建了元素,后面的执行前一定会先触发,如果这时发生样式的变化,就会出现。
hello, world
可以看到,这里的三个标签导致了额外的三次/。
这个问题不容忽视,因为有时候费了很大劲做的优化,一次打包就可以让你前功尽弃。比如使用了之后,把图标资源打包到中,会得到:
在执行的时候,会向上插入一个大的。再到执行的时候,就会闪烁了。
解决方案
了解了浏览器绘制的时机,的问题就可以迎刃而解了。这里主要针对页面,毕竟对的页面来说,或许不是一个大问题。
将资源尽量放到中,只保留最后一个包含主逻辑的脚本在中,因为它很可能要往上挂载元素。这可以解决上面提到的标签导致的多次渲染问题。
第一次渲染,不论是、还是,一定要同步放到主逻辑中,确保发生在之前。
避免对进行不必要的读操作,因为他们会带来的额外的绘制。
参考资料
https://www.html5rocks.com/en/tutorials/internals/howbrowserswork/
https://developers.google.com/web/fundamentals/performance/user-centric-performance-metrics
https://developers.google.com/web/fundamentals/performance/critical-rendering-path/analyzing-crp
领取专属 10元无门槛券
私享最新 技术干货