Skip to content

CSS 深入

本文假定你已经掌握盒模型、Flex/Grid 布局、CSS 动画基础和原子化 CSS,聚焦于底层机制与性能优化。

一、BFC(块级格式化上下文)

BFC 是一个独立的渲染区域,内部元素的布局不会影响外部。最常见的两个场景:清除浮动(包裹浮动子元素,避免高度塌陷)和防止 margin 塌陷(不同 BFC 间的垂直 margin 不会合并)。

1.1 触发方式

方式副作用
display: flow-root✅ 无副作用(推荐)
overflow: hidden/auto可能裁剪内容
float: left/right脱离文档流
position: absolute/fixed脱离文档流
display: flex/grid改变布局模型
display: inline-block / table-cell改变盒类型
html
<!-- 清除浮动(高度塌陷) -->
<div style="display: flow-root">
  <div style="float: left">浮动子元素</div>
</div>

<!-- 防止 margin 塌陷(不同 BFC 间 margin 不合并 → 间距 = 50px)-->
<div style="display: flow-root"><p style="margin-bottom: 20px">段落1</p></div>
<div style="display: flow-root"><p style="margin-top: 30px">段落2</p></div>

二、层叠上下文

z-index 只在定位元素(position 不为 static)或 flex/grid 子项上生效,且必须先形成层叠上下文。同一层叠上下文中 z-index 大的在上;不同层叠上下文中,先比较父级的层叠顺序。

2.1 触发条件(不止 position + z-index)

  • position: relative/absolute/fixed/sticky + z-indexauto
  • opacity < 1
  • transform / filter / perspectivenone
  • will-change 指定上述属性
  • contain: layout / paint / strict
层叠顺序(从低到高):
  background/border → 负 z-index → block 块级盒子 → float 浮动盒子
  → inline/inline-block → z-index: auto / 0 → 正 z-index

三、CSS 动画性能

3.1 浏览器渲染管线

属性类型触发阶段性能
left / top / width / height / marginLayout → Paint → Composite❌ 差(触发重排)
color / background / box-shadowPaint → Composite⚠️ 一般(触发重绘)
transform / opacityComposite only✅ 最佳(合成线程)

核心理念:transformopacity 的动画在合成线程执行,不经过主线程的 Layout 和 Paint,所以即使主线程在做复杂 JS 计算也不会卡动画。

css
/* ❌ 触发重排 */
@keyframes bad { to { left: 100px; } }
/* ✅ 合成层执行(GPU,无重排) */
@keyframes good { to { transform: translateX(100px); } }
.animated { will-change: transform; animation: good 1s; }

3.2 will-change

will-change 提前告知浏览器该元素即将变化,浏览器会提前创建独立的合成层(GPU 层)。但不是越多越好——每个合成层都是一份 GPU 位图副本,滥用会导致显存暴涨,移动端可能白屏崩溃。

正确用法:只在动画开始前通过 JS 动态添加,动画结束后移除;或者只在确实有性能瓶颈的元素上使用。大多数情况下让浏览器在动画发生时自动提升为合成层完全够用。


四、合成层(Compositor Layers)

浏览器渲染的最后一步——合成线程将多个图层合并输出到屏幕。理解合成层是排查"动画卡顿"和"内存暴涨"的关键。

4.1 哪些属性在合成线程执行

transform(translate / scale / rotate)、opacityfilter(部分)。

4.2 创建合成层的条件

  1. 3D transform:transform: translateZ(0)
  2. will-change: transform / will-change: opacity
  3. <video> / <canvas> / <iframe> 元素
  4. position: fixed(部分浏览器)

4.3 隐式合成与层爆炸

隐式合成:当某个元素被提升为合成层后,如果它的 z-order 高于页面中其他合成层,浏览器为了保证渲染顺序正确,会"连带"把中间的元素也提升为合成层——哪怕它们本身不需要。这是层爆炸的主因。

排查方法:Chrome DevTools → More tools → Layers 面板,查看每个层的 "Compositing Reasons"。如果看到大量 Overlap with other composited element,就是隐式合成。

translateZ(0) 还能用吗? 能用但不如 will-change: transform 语义明确。translateZ(0) 本质是欺骗浏览器创建 3D 合成层,浏览器不知道你的意图;will-change 则明确声明,浏览器可以更智能地管理资源。但页面合成层已经很多时,两者都别滥用。


五、响应式

5.1 媒体查询 vs 容器查询

  • 媒体查询@media):根据视口宽度切换样式。适合页面级布局(如侧边栏显隐、grid 列数)。
  • 容器查询@container):根据父容器尺寸变化。适合组件内部自适应——一个组件放进 300px 的侧边栏和 800px 的主内容区会自动切换样式,无需写两套。
css
/* 媒体查询 */
@media (max-width: 768px) { .sidebar { display: none; } }

/* 容器查询 */
.card-wrapper { container-type: inline-size; }
@container (min-width: 400px) { .card { flex-direction: row; } }

/* clamp() 流式尺寸:一个声明覆盖最小、理想、最大值 */
font-size: clamp(1rem, 2.5vw, 2rem);
padding: clamp(1rem, 5%, 3rem);

六、contain

contain 属性告诉浏览器元素的内部变化不会影响外部,从而跳过不必要的布局/绘制计算,实现性能隔离。

css
contain: layout;         /* 内部布局不影响外部(BFC 强化版) */
contain: paint;          /* 后代不绘制在元素边界外 */
contain: layout paint;   /* 组合使用,适合长列表中的单项隔离 */
content-visibility: auto /* 跳过屏幕外元素的渲染(≈懒渲染) */
隔离范围
layout内部布局变化不影响外部几何
paint后代超出边界部分不绘制
size元素尺寸不依赖子元素(需显式指定宽高)
stylecounter / quotes 等样式不穿透

七、CSS Houdini(了解即可)

CSS Houdini 允许开发者用 JS 编写 CSS 引擎内部逻辑,目前浏览器支持有限。

API用途
Paint API自定义 background-image 绘制
Layout API自定义布局算法
Properties & Values API注册带类型的 CSS 自定义属性(支持动画)
Animation Worklet合成线程运行动画(不阻塞主线程)
css
/* Properties & Values API:注册带类型的 CSS 变量 */
@property --my-color {
  syntax: '<color>';
  initial-value: #000;
  inherits: false;
}

八、大屏自适应方案

大屏(数据看板、可视化驾驶舱)的分辨率跨度极大——从 1366×768 到 4K/8K 拼接屏都可能跑同一套页面。核心诉求是"等比缩放、不变形、铺满全屏"。

8.1 方案对比

方案原理优点缺点适用场景
vw/vh + rem视口单位 + rem 动态基准纯 CSS,无 JS 缩放计算比例关系不严格时文字可能偏大/偏小简单大屏、内容型页面
transform: scale监听 resize,按比例整体缩放设计稿 1:1 还原,不走样鼠标事件坐标偏移需手动修正设计稿固定比例(如 16:9)
DataV / FlyFish组件库内置自适应容器开箱即用,配套图表丰富绑定框架生态,定制受限可视化大屏、数据看板
自适应 + 媒体查询多断点切换布局灵活可控断点间过渡生硬,设计工作量大通用页面

8.2 scale 变换方案(推荐掌握)

核心思路:页面按固定设计稿尺寸(如 1920×1080)开发,JS 监听 resize 算缩放比例,取最小方向比例(保证内容不裁剪),以 transform: scale + transform-origin: left top 整体缩放并居中。

html
<div id="app" class="screen-container">
  <div class="screen-content"><!-- 所有内容按 1920×1080 开发 --></div>
</div>

<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  html, body, #app { width: 100%; height: 100%; overflow: hidden; background: #000; }
  .screen-content {
    width: 1920px; height: 1080px;
    transform-origin: left top;
  }
</style>

<script>
(function() {
  const DESIGN_W = 1920, DESIGN_H = 1080;
  const container = document.querySelector('.screen-content');

  function resize() {
    const scaleX = window.innerWidth  / DESIGN_W;
    const scaleY = window.innerHeight / DESIGN_H;
    const scale = Math.min(scaleX, scaleY); // 取较小比例,保证不裁剪
    container.style.transform = `scale(${scale})`;
    container.style.marginLeft = (window.innerWidth  - DESIGN_W * scale) / 2 + 'px';
    container.style.marginTop  = (window.innerHeight - DESIGN_H * scale) / 2 + 'px';
  }

  window.addEventListener('resize', resize);
  resize();
})();
</script>

常见坑与修复

问题原因修复
鼠标点击位置偏移scale 后事件坐标仍在原始坐标系事件处理中 clientX / scale - offsetX 换算
屏幕出现滚动条缩放后元素实际占位仍为 1920×1080父容器 overflow: hidden
字体模糊scale < 1 时文字缩放到非整数像素适当加大设计稿字号,translateZ(0) 提层
iframe/video 不缩放内嵌元素有独立渲染上下文对其也应用 transform: scale
ECharts tooltip 偏移ECharts 用 event.offsetX/Y 定位chart.setOption({ tooltip: { position: point => [point[0]/scale, point[1]/scale] } })

8.3 vw/vh + rem 方案

通过 rem 单位绑定根字体大小,利用 vw/vh 动态计算 htmlfont-size。适合内容型大屏,实现简洁,但比例关系不严格时会偏大/偏小。

css
/* 设计稿宽度 1920px,100vw = 1920px 时 1rem = 10px */
html { font-size: calc(100vw / 1920 * 10); }

/* 防字体过小/过大 */
@media (max-width: 1200px) { html { font-size: calc(100vw / 1200 * 10); } }

配合 postcss-pxtorem 自动将 px 转为 rem,开发时继续写 px,构建后自动换算。

基于 VitePress 构建 · 内容持续更新