会,CSS 放在 </body> 前仍阻塞 DOM 解析和页面渲染,因浏览器流式解析中遇样式表必暂停构建 CSSOM;<style> 写在 <body> 会被自动提升至 <head>,同样触发阻塞。

CSS 放在 </body> 之前会阻塞渲染吗
会,而且阻塞得比你想象中更彻底。浏览器解析 HTML 是自上而下流式进行的,一旦遇到 <link rel="stylesheet"> 或 <style>,就会暂停 DOM 构建,去加载、解析、计算 CSSOM —— 这个过程不完成,后续的 HTML 不会继续解析,页面也不会开始绘制(paint)。
哪怕你把 <link> 放在 </body> 前面(比如紧贴在 </body> 上方),只要它还在 HTML 文档流里,就仍属于「解析过程中遇到的样式表」,触发同步阻塞行为。
常见错误现象:
– 页面长时间白屏,直到 CSS 加载完毕才突然全部渲染
– 控制台 Network 面板显示 HTML 已下载完成,但 DOMContentLoaded 事件迟迟不触发
– 移动端弱网下首屏时间显著变长
<style> 写在 </body> 里能绕过阻塞吗
不能。HTML 规范明确要求:所有 <style> 元素必须出现在 <head> 中;若出现在 <body>,浏览器会自动将其提升(move up)到 <head>,并在解析时按提升后的位置处理 —— 所以你“写在底部”,实际还是被当成 <head> 里的样式来阻塞。
立即学习“前端免费学习笔记(深入)”;
验证方式:打开 DevTools → Elements 面板,搜索 <style>,你会发现它已经不在 </body> 附近了。
注意:
– <style> 在 <body> 属于 HTML5 parse error,不是合法写法
– 某些 SSR 框架(如 Next.js)动态注入的 <style> 是通过 JS 插入的,不走 HTML 解析流程,因此不触发该阻塞 —— 但这和“手写在 HTML 底部”是两回事
真想延迟加载非关键 CSS,该用什么方式
目标是让 CSS 不参与初始渲染路径,又不影响功能。推荐组合策略:
- 用
rel="preload"+onloadJS 注入:先异步加载 CSS 文件,加载完成后再插入<link rel="stylesheet"> - 对非首屏组件样式,用
media="print"占位,再用 JS 切换为media="all"(避免阻塞且兼容性好) - 使用
fetch()+CSSStyleSheet.replace()(现代方案,需检查CSSStyleSheet支持) - 服务端做 critical CSS 提取,内联首屏所需样式,其余外链并加
media="print" onload="this.media='all'"
错误做法示例:
– <link rel="stylesheet" href="non-critical.css" media="none" onload="this.media='all'"> ❌ media="none" 不被所有浏览器识别,部分会直接忽略加载
– 把 <link> 放在 <script> 后面指望“JS 执行完再加载” ❌ 浏览器预加载器(preload scanner)会在解析 HTML 时提前发现并发起请求,位置无效
为什么有时放底部看起来“没影响”
那是因为你观察的是「最终渲染结果」,而不是「渲染时机」。几种典型干扰场景:
– CSS 文件本地缓存命中,加载极快,阻塞时间可忽略
– 页面内容极少,DOM 构建本身几毫秒就完了,你感知不到暂停
– 开启了 Service Worker 缓存,或使用了 CDN 边缘缓存,网络耗时大幅压缩
– 浏览器开启了 speculative parsing (推测解析):一边等 CSS,一边偷偷解析后续 HTML,但构建 DOM 树仍被卡住,只是不那么明显
真正影响的是 First Contentful Paint (FCP) 和 Largest Contentful Paint (LCP) —— 这两个指标不会骗人。用 Lighthouse 或 Chrome Performance 面板录制一次,对比 <head> 和 </body> 两种写法,差异一目了然。
最容易被忽略的一点:即使你只改了一个按钮颜色,只要它用到了尚未加载的 CSS 规则,整个页面的首次绘制就得等那个 CSS 文件落地。渲染流程不讲道理,也不看“样式重要不重要”。


















