FOUC 是因浏览器未在首次绘制前确认关键样式就绪导致的闪屏现象;可通过 preload 关键 CSS 并配合 onload 注入、用 media 隔离非关键样式、内联首屏 critical CSS 三重策略防控。

为什么 CSS 加载时会闪(FOUC)
浏览器解析 HTML 时,遇到 <link rel="stylesheet"> 默认是阻塞渲染的,但若样式表体积大、网络慢,或被 JS 动态插入,就可能先渲染无样式的 HTML 再补上样式——这就是 FOUC(Flash of Unstyled Content)。尤其在 SSR + 客户端 hydration 场景下更明显。
关键不是“有没有 CSS”,而是“浏览器是否在首次绘制前确认了所有关键样式已就绪”。
用 rel="preload" 提前拉取关键 CSS
预加载本身不执行样式,只是提前发起请求;必须配合 as="style" 和 onload 注入,否则无效。
-
<link rel="preload" href="/main.css" as="style" onload="this.rel='stylesheet'">是最小可行写法 - 不要漏掉
onload,否则资源下载完也不会应用,页面仍白屏或闪烁 - 只对首屏必需的 CSS 做 preload,比如
base.css或critical.css;全量app.css预加载反而拖慢首字节(TTFB) - Webpack/Vite 构建时,
preload不会自动加onload,需插件(如html-webpack-plugin的preload: true+ 自定义 template)或手动注入
媒体查询(media)怎么防非关键 CSS 干扰渲染
把非首屏样式(如打印、暗色模式、宽屏布局)用 media 属性隔离,能让浏览器跳过解析和应用,直到条件匹配——这直接减少渲染阻塞路径。
立即学习“前端免费学习笔记(深入)”;
-
<link rel="stylesheet" href="print.css" media="print">:打印样式完全不影响页面首次渲染 -
<link rel="stylesheet" href="dark.css" media="(prefers-color-scheme: dark)">:仅在用户系统设为暗色时加载 - 避免写
media="all"或空media,那等于没隔离;也别用 JS 动态改media值来“开关”样式,触发重排且不可靠 - Vite/Next.js 等现代框架默认会对
media属性做优化,但 Webpack 5+ 需开启css.experimentalImportSupport才能正确处理带 media 的@import
内联 critical CSS 是最稳的兜底方案
预加载 + media 能大幅降低 FOUC 概率,但 CDN 延迟、HTTP/2 排队、服务端缓存失效都可能导致失败。此时,把首屏必须的 CSS 内联进 是唯一 100% 可控的方式。
- 内联内容必须严格限制在首屏 DOM 所需样式,一般不超过 14KB(避免突破 TCP 初始拥塞窗口)
- 用工具提取:Webpack 用
critters插件,Vite 用vite-plugin-critical-css,Next.js 有next-critical-css - 内联后记得移除对应外链,否则样式重复应用可能引发优先级冲突(比如内联的
.btn { color: red }和外链的.btn { color: blue }同时存在) - 服务端渲染时,critical CSS 应随 HTML 一起生成;客户端路由切换时,无需也不该重复内联,否则破坏缓存和性能
真正难的不是加 preload 或 media,而是判断哪些 CSS 算“关键”——它依赖真实设备、网络条件、首屏 DOM 结构,而且每次 UI 改动都可能让之前提取的 critical CSS 失效。


















