@charset必须位于CSS文件第一行首字符,因其是解析器启动前的解码指令;前面有空行、注释、BOM或空格均导致失效,浏览器将退回到ISO-8859-1等默认编码引发中文乱码。

@charset 必须放在最顶端,是因为它不是样式规则,而是解析器启动前的“解码指令”——浏览器在读取第一个字节时就要决定怎么解码后续所有内容,晚了就来不及了。
为什么不能有空行或注释在 @charset 前
浏览器 CSS 解析器在打开文件后,会逐字节扫描,一旦遇到非空白、非 BOM、非 @charset 的字符,就认为“元信息声明期”已结束,直接进入样式规则解析阶段。此时再出现 @charset,会被当成非法 at-rule 忽略。
- ✅ 正确:
@charset "UTF-8";—— 文件第一行,无任何前置字符(包括 BOM 后的换行也算“前置”) - ❌ 错误:
/* 注释 */\n@charset "UTF-8";—— 注释占了首行,@charset实际是第二行,被静默丢弃 - ❌ 错误:
\n@charset "UTF-8";—— 首行为空行,解析器跳过空白后看到@charset,但此时已错过“必须首个非空白 token”的窗口
@charset 不生效时,浏览器怎么猜编码
没写 @charset 或写错位置时,浏览器按固定优先级 fallback,顺序不可控:
- HTTP
Content-Type: text/css; charset=UTF-8响应头(最优先) - 文件开头的 UTF-8 BOM(
EF BB BF) - HTML 页面的
</meta charset="UTF-8">(仅对<link>引入的 CSS 有弱影响) - 最终默认用 ISO-8859-1 解码 —— 这会导致中文
content、注释、字体名全变
构建工具里 @charset 容易被删或错位
Webpack/Vite/PostCSS 在 CSS 合并、压缩、提取过程中,可能把 @charset 当作无用声明移除,或因拼接顺序导致它不再居首:
立即学习“前端免费学习笔记(深入)”;
- 多个 CSS 文件合并时,只保留第一个
@charset,其余被丢弃;但如果第一个文件没写,而第二个写了,合并后它就变成第二行 → 失效 - PostCSS 插件如
cssnano默认删除@charset(认为冗余),需显式配置discardCharsets: false - Vite 2.x 中
build.cssCodeSplit开启时,拆分出的 chunk CSS 可能丢失@charset,需手动在入口 CSS 补全
真正关键的不是“要不要写”,而是“能不能稳稳落在第一行”。哪怕你用了 UTF-8 且没中文,只要团队里有人保存文件带 BOM、有人加了首行注释、有人跑了一键压缩脚本,@charset 就可能在某次构建后突然失效——而问题现象只是「字体名不显示」或「伪元素 content 出乱码」,很难联想到是解析起点偏移了。


















