前端性能优化中,CSS 的优化是非常关键的一环,因为 CSS 会阻塞渲染,影响首屏加载时间、帧率(FPS)和交互响应。
一、加载阶段优化(减少阻塞、加速首屏)
浏览器在构建渲染树前,必须等待所有 CSS 下载并解析完成(CSS 是渲染阻塞资源)。
消除渲染阻塞的 CSS
xml
<!-- 非关键样式异步 -->
<link rel="preload" href="non-critical.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="non-critical.css"></noscript>
rel="preload"
- 告诉浏览器:"请先以最高优先级把资源下载到本地缓存,但暂时不要应用它"
href="non-critical.css"
- 指定要下载的样式文件地址
as="style"
- 告诉浏览器资源类型是 style
onload="this.onload=null;this.rel='stylesheet'"
- 当文件下载完成后,手动把这条 变成普通样式表,让浏览器立即应用样式。
noscript
- 渐进增强 / 降级:当用户禁用 JS 时, preload 的 onload 永远不会执行,页面永远无样式。 里的普通 确保最弱环境也能正常展示。
使用 media
属性避免不必要的阻塞
默认情况下,所有 CSS 都会阻塞渲染,即使它们不适用于当前设备。
ini
<link rel="stylesheet" href="print.css" media="print">
<link rel="stylesheet" href="mobile.css" media="(max-width: 600px)">
场景 | 示例 | 含义 |
---|---|---|
设备宽度 | media="(max-width: 600px)" |
600 px 及以下才加载/阻塞 |
设备宽度区间 | media="(min-width: 601px) and (max-width: 1024px)" |
平板区间 |
竖横屏 | media="(orientation: landscape)" |
横屏才加载 |
高分屏 | media="(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)" |
Retina 屏 |
打印 | media="print" |
仅打印/打印预览时加载,日常完全不阻塞首屏 |
语音阅读器 | media="speech" |
屏幕阅读器模式 |
深色模式 | media="(prefers-color-scheme: dark)" |
用户切到暗黑才加载 |
减少动画 | media="(prefers-reduced-motion: reduce)" |
用户开启"减少动画"时加载 |
任意组合 | media="screen and (min-width: 960px) and (prefers-color-scheme: dark)" |
宽屏 + 暗黑同时满足 |
压缩与去重
减少代码大小,会快一点
- 压缩:使用工具如 cssnano、clean-css、esbuild 等去除空格、注释、缩短属性名。打包时配合服务器使用 Gzip。
- 去重:避免重复引入相同样式(如多个组件库重复加载 normalize.css)。
使用 HTTP/2 或 HTTP/3 多路复用
- 避免 CSS 文件过多导致的队头阻塞(Head-of-line blocking)。
- 拆分 CSS 为多个小文件,利用 HTTP/2 的多路复用并行加载。
二、解析与构建阶段优化(减少 CSS 复杂度)
减少 CSS 选择器复杂度
复杂选择器(如深嵌套、通配符、属性选择器)会增加匹配时间。
- 谨慎使用伪类和定位
- 减少嵌套层级
- 减少使用占内存和 CPU 性能的属性
- 例如:text-indnt: -9999px
- 避免耗电大的属性
- 3D、transforms、transitions、opactiy
- 避免使用 css 表达式
- 例如:expression(js 表达式)
- 避免使用通配符
- 避免使用正则属性选择器
减少 CSS 文件体积
移除未使用的 CSS、减少 CSS 文件体积、使用 contain 属性隔离样式影响
移除未使用的 CSS
- 避免使用
*
通配符。 - 避免使用标签 + 类 + 伪类 + 属性的组合(如
div.nav-item:hover[data-active="true"]
)。 - 优先使用类选择器(
.btn
),避免嵌套过深(如.a .b .c .d
)。 - 避免使用
:nth-child()
、:not()
等复杂伪类。
减少 CSS 文件体积
- 移除未使用的 CSS
- 避免冗余样式
- 使用 CSS 变量减少重复值
使用 contain 属性隔离样式影响
原子值 | 开启后浏览器得到什么承诺 | 若违背承诺的后果 |
---|---|---|
layout | 1. 元素内部任何子元素布局变化不会影响到本元素外部 (反之亦然)。2. 浏览器可跳过整棵外部树的重排计算。 | 如果你手动把内部元素改成 position: fixed 并贴到视口边缘,就破坏了承诺,可能出现错位。 |
paint | 该元素不会绘制到自身边框盒之外 (即不可能溢出 )。浏览器可直接裁剪合成层,省去父层重绘。 | 实际溢出部分会被视觉裁剪 (类似 overflow:hidden 但不触发滚动条)。 |
style | 1. counters(counter-* )和 quotes 变化不会穿越 本元素。2. 浏览器可不重新计算外部计数器。 |
若你在内部用 counter-increment 却希望外部继续累加,会失效。 |
size | 该元素的尺寸不受子元素撑开 (类似替换元素 )。浏览器可在子树布局完成前就确定父层尺寸。 | 若子元素比显式尺寸高/宽,直接溢出被裁 ;父盒不会自动撑大。 |
inline-size | 仅横向尺寸 不受子元素影响,高度仍可被子元素撑开 (Level 3 新增,Safari ≥ 16 支持)。 | 同上,但仅宽度固定。 |
三、渲染阶段优化(减少重排/重绘/合成层)
避免触发重排(Reflow)的属性
- 会触发重排的属性 :
width
,height
,margin
,padding
,border
,position
,top
,left
,font-size
,display
,float
,clear
,overflow
,min-height
,line-height
等。
- 优化建议 :
- 使用
transform
替代top/left
。 - 使用
opacity
替代visibility
或display
。 - 避免在动画中改变布局属性。
- 使用
使用合成层(Composite Layer)优化动画
- 触发合成层的属性 :
transform
,opacity
,filter
,will-change
- 实战技巧 :
- 使用
transform: translateZ(0)
或will-change: transform
强制提升为合成层(需谨慎,避免过度使用导致内存暴涨)。
- 使用
避免强制同步布局
JS 读取布局属性(如
offsetWidth
)后立即修改样式,会强制浏览器同步计算布局。
如果用框架会好点,批量的写
四、运行时优化(减少样式计算与匹配)
减少样式计算范围
DOM 节点越多,样式计算越慢。
- 避免在
body
上设置复杂选择器(如body > div:nth-child(3) > ...
)。 - 使用
class
替代标签 + 属性选择器。
使用 CSS 变量减少运行时计算
CSS 变量(--var
)在运行时计算,适合主题切换等场景,避免 JS 频繁操作样式。
避免使用 @import
一般打包工具和框架会处理
@import
会串行加载,阻塞渲染。- 替代方案 :使用
<link>
标签并行加载,或使用打包工具合并 CSS。
五、进阶优化策略
使用 CSS Modules / Scoped CSS 避免全局污染
减少全局选择器冲突,降低样式匹配复杂度。
vue、react 这些框架都有
- Vue:
<style scoped>
- React: CSS Modules、Styled-components、Emotion
使用 CSS-in-JS 需谨慎
样式按需加载、避免全局污染。
- 运行时生成样式,增加 JS 负担。
- 可能导致 SSR 样式闪烁(FOUC)。
监控与度量
一般没怎么用专业的工具看,就是浏览器的工具看看,没怎么接触大项目,要求优化到这么细致的
- Lighthouse(Performance 评分)
- Chrome DevTools → Performance → Rendering → Paint flashing
- CSS Triggers(csstriggers.com/)
- Web Vitals(CLS、LCP、FID)
服务器设置缓存
通常服务器会设置静态文件缓存、index.html(除外防止更新后没能及时加载最新版本)
项目开发通常没怎么想过 css 优化?
- Vue/React 脚手架已经帮你干了最粗的那一层
- 压缩、去空行、合文件、把
@import
拉平、加指纹哈希 → 你一行配置都不用写,上线默认就有。
- 压缩、去空行、合文件、把
- 缓存 服务器来处理不用前端处理
- 不解决首次白屏、不掉帧、不防重排。
- 剩下内容则需要手动处理,或者开发的时候避免
- 首屏 CSS 太多 → 抽"关键 CSS"内联。
- 文件虽压缩了,但 70 % 样式没用到 → PurgeCSS 删掉。
- 列表一万行 → 加 contain: layout paint 防掉帧。
- 动画用 top/left → 改成 transform 就不阻塞主线程。