做前端和站点运维的朋友多半都听过网页优化这个词,但它真不只是把图片压小、把CSS合并这么简单。很多时候页面打开慢,用户流失快,根子出在浏览器渲染过程里被某些资源悄悄卡住了。这篇文章想聊的,就是实际项目里常碰到的渲染阻塞现象,以及怎么在不伤功能的前提下把它们挪开。
浏览器从服务器取回HTML文档,会先构建DOM树。遇到<script>标签且没加defer或async时,解析会停下来去下载并执行脚本,这段时间DOM构建被阻断。CSS也一样,如果后面有脚本依赖样式,那么CSSOM没建好,脚本就得等着。这种等待就是典型的渲染阻塞。
用一段最小化的HTML就能看清楚顺序:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="slow.css">
</head>
<body>
<p>内容</p>
<script src="app.js"></script>
</body>
</html>上面这种写法,slow.css不加载完,app.js就不会执行,而app.js执行前页面虽然能显示出文字,但交互完全不可用。用户感知到的“点了没反应”往往就是这么来的。
很多人第一反应是上构建工具把JS体积砍一半,但如果是阻塞造成的白屏,省几十KB不如改两个属性。给脚本加defer,浏览器会继续解析HTML,等文档解析完再按顺序执行:
<script defer src="app.js"></script>如果是独立统计或聊天组件,用async更合适,它不保证顺序,但下载时不卡解析:
<script async src="track.js"></script>我们曾经把一个三方的客服插件从head里挪到body末尾并加async,首屏可交互时间从四点二秒降到一点九秒,也没改任何业务逻辑。
首屏用不到的样式可以延迟加载。比如打印样式、次级页面才用的弹窗样式,用media属性让浏览器先忽略:
<link rel="stylesheet" href="print.css" media="print">对移动端首屏,把关键CSS直接内联到head里,其余放外部文件异步拉取,是常见做法。内联体积控制在十四KB内比较稳妥,超出反而拖慢HTML传输。
| 方式 | 是否阻塞渲染 | 适用场景 |
|---|---|---|
| head内普通link | 是 | 首屏必需样式 |
| link加media非all | 否 | 打印或特定端样式 |
| 关键CSS内联 | 否(已含在HTML) | 首屏核心布局 |
| JS动态插入link | 否 | 路由切换后再加载 |
用Web字体时,如果没设置font-display,浏览器默认会等字体下载完才显示文字,这段时间页面可能空白。加上font-display: swap,先显示系统字体,下载完再换:
@font-face {
font-family: 'MyFont';
src: url('my.woff2') format('woff2');
font-display: swap;
}另外预连接也能省下握手时间,对用CDN托管字体的站点明显:
<link rel="preconnect" href="https://fonts.cdn.com">别只靠感觉。Chrome DevTools的Performance面板录一段加载过程,看Main线程里哪段任务最长。也可以跑Lighthouse看“消除渲染阻塞资源”这项的得分和建议。我们在一个企业站上发现阻塞全来自一个没必要的历史轮播库,删掉后评分从六十一到八十九。
渲染阻塞优化不是要把所有JS都删了,而是让关键路径上的资源最少化。
开启HTTP/2后,多个CSS和JS可以并行推,但别滥用推送,推了不用的资源反而占带宽。更实在的是给静态资源加长期缓存,HTML用no-cache,这样回访用户只拉变动的部分。
还有个细节:服务端渲染时,如果把大数据量的JSON直接塞进script里,会拖慢HTML解析。把它拆成接口请求,虽多一次往返,但首屏更快。网页优化做到后来,常常是在这种权衡里找平衡点。
渲染慢的页面,搜索引擎抓取时可能只拿到空壳,影响索引质量。特别依赖客户端渲染的站,建议做动态渲染或预渲染,把完整HTML交给爬虫。我们跟踪过一个站,异步组件改预取后,有效收录页数涨了三成。
把这些阻塞点一个个挪开,不需要多高深的理论,就是看清浏览器在每一步等了谁,然后让该等的变少、不该等的靠后。下次打开自己站点时,不妨用工具量一下,说不定卡住的就是那行没加defer的旧脚本。
评论
加载中...