Agent Developer Roadmap
Web 基础

CSS:从层叠决策到渲染成本

最后核验:2026-06-30|适用版本:CSS Snapshot 2025 与现代常青浏览器|前置知识:HTML

CSS 从声明到像素的职责

CSS 是一个约束求解系统:多来源声明先经过层叠选出指定值,再经继承、计算、布局和绘制得到像素。只会“把页面调到看起来对”不足以维护大型界面;你还需要解释某条声明为什么输、某个盒子为什么溢出,以及一次动画为什么触发布局。

CSS 适合表达视觉规则、布局约束、环境偏好和有限的展示状态。它不适合承载业务真值、权限、安全校验或复杂数据转换。用隐藏样式保护敏感内容、用复选框 hack 管理应用路由、把服务端状态编码进选择器,都会让可见状态与真实状态分离。

选择 CSS 能力时先用平台已有的布局和条件机制:正常流解决文档阅读顺序,Flex 解决单轴分配,Grid 解决二维轨道,容器查询解决组件上下文。只有性能数据证明必要时,才引入虚拟化或 JavaScript 测量布局。

选择器匹配、伪类与权重计算

浏览器把选择器从右向左匹配:先找最右侧候选元素,再验证祖先/兄弟关系。性能上不必为普通页面手工压缩选择器,但结构越长、依赖越强,组件越难移动。类选择器表达可复用样式,属性/状态伪类表达语义状态;不要把 DOM 层级完整复制进选择器。

权重可按 (ID, class/attribute/pseudo-class, type/pseudo-element) 三列比较,内联样式位于更高的 style attribute 阶段。:where() 的自身及参数权重为零,适合低权重默认;:is():not():has() 取参数列表最高权重;伪元素计入 type 列。

/* (0, 1, 0):组件默认 */
.card { border: 1px solid; }

/* (0, 1, 1):有链接的 card,不需要 JavaScript 加 class */
.card:has(> a) { padding-block-end: 1rem; }

/* 始终为零权重,调用者可以轻松覆盖 */
:where(.prose h2, .prose h3) { scroll-margin-block-start: 5rem; }

/* (0, 2, 0),不是 (1, 1, 0);:where 清零内部 #id */
.dialog:where(#detail) { max-inline-size: 40rem; }

伪类描述状态或结构::focus-visible 区分应显示焦点环的交互,:focus-within 表示后代获得焦点,:checked/:disabled/:invalid 对应原生控件状态,:nth-child(... of selector) 可在过滤集合中计数。伪元素 ::before/::after 生成表现内容,不放核心文本;::marker::selection::backdrop 分别作用列表标记、选择文本与 top layer 背景。

@layer base, components, overrides;

@layer base {
  :where(button, input, select, textarea):focus-visible {
    outline: .2rem solid CanvasText;
    outline-offset: .2rem;
  }
}

@layer components {
  .field:has(:invalid:not(:focus)) { border-color: var(--error); }
}

调试“规则未生效”按固定顺序:元素是否匹配、声明是否有效、层叠阶段谁胜出、属性是否继承、计算值是否因变量/单位无效、布局算法是否覆盖可用值。不要跳到提高权重。

盒模型、包含块与尺寸值

每个盒子由 content、padding、border、margin 组成。默认 box-sizing: content-box 让声明 width 只控制内容;工程常用 border-box 让 width 包含 padding/border。margin 不属于盒子尺寸,垂直块 margin 在特定正常流条件下会折叠;Flex/Grid 项目之间不会按同样规则折叠。

*, *::before, *::after { box-sizing: border-box; }

.page {
  inline-size: min(100% - 2rem, 72rem);
  margin-inline: auto;
}

.media {
  inline-size: 100%;
  block-size: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

width/height 是物理属性;inline-size/block-size 随书写模式变化。百分比相对 containing block(包含块),但不同属性的百分比解析基准可能不同;绝对定位、fixed、sticky 的包含块还会被 transform、contain 等祖先改变。遇到定位漂移要在 DevTools 检查包含块,而不是继续加 offset。

尺寸关键字解释内容约束:min-content 是不产生可选换行时的最小内在尺寸,max-content 是内容自然展开尺寸,fit-content() 在可用空间内夹取。Flex/Grid 中 auto 最小尺寸常阻止收缩,min-inline-size: 0minmax(0, 1fr) 是在确认内容允许裁/换行后的定点修复。

单位相对基准适合易错点
rem根字号全局间距、可缩放尺寸不等于固定像素
em当前元素字号(font-size 自身看父级)与组件文字成比例嵌套会累乘
%属性定义的包含块/其他基准流体尺寸block-size 百分比可能缺明确基准
vw/vh初始包含块视口大幅视觉比例移动浏览器工具栏改变可视区域
svh/lvh/dvh小/大/动态视口移动全屏布局需按内容与跳动风险选择
ch字体“0”的 advance文本行宽近似不是汉字或所有字符宽度
cqi/cqb查询容器行内/块轴组件内流体尺寸没有查询容器时回退到小视口基准

clamp(min, preferred, max) 适合流体字号/间距,但最小最大值必须在 200% 缩放和窄屏测试。不要用 viewport 单位覆盖用户字号到不可放大。

正常流、display、定位与层叠上下文

正常流按书写模式排列 block 和 inline formatting context。display 同时有外部和内部角色:block flowinline flowblock flexblock griddisplay: contents 会移除元素生成的自身盒子但保留子盒,不应在未验证可访问性树和事件/伪元素行为时滥用。

Block Formatting Context(BFC)隔离内部浮动并影响 margin 折叠,可由 flow-root 明确建立。清除浮动使用容器 display: flow-root 比伪元素 clearfix 更直接。浮动适合文字环绕图像,不是现代页面主布局。

定位模型:

position是否保留原流空间定位基准典型用途
static不应用 inset默认正常流
relative自身正常位置小幅偏移、建立绝对定位包含块
absolute最近合格 containing block徽标、局部覆盖层
fixed通常视口,可能被 transform 等改变全局工具条/浮层,优先考虑 top layer
sticky是直到阈值最近滚动容器与 containing block表头、章节导航

sticky 不工作时检查:是否设置对应 inset;祖先是否产生意外滚动容器;容器是否真的有可滚动空间;sticky 项是否与容器一样高。overflow: hidden/auto 可能改变它参照的 scrollport。

z-index 只在相应 stacking context 中比较。opacity 小于 1、transform、filter、isolation、某些 contain、定位 + z-index 等会创建新层叠上下文;子元素再大的 z-index 也不能越过父上下文的兄弟。模态 dialog/popover 应使用 top layer,不用无穷大数字与页面层级竞赛。

层叠顺序、继承与计算值

层叠不是“选择器权重越大越赢”。一条声明先经过相关性过滤,再按来源与重要性、封装上下文、层、选择器权重、作用域邻近度和出现顺序比较。CSS Cascading Level 6 把 @scope 的邻近度放在权重与顺序之间;层的比较发生在权重之前。CSS Cascading Level 6

层叠顺序可用下面的决策链记忆:

阶段问题工程含义
相关性媒体条件、支持条件、选择器是否匹配未匹配规则不进入竞争
Origin / Importance过渡、用户、作者、动画与 UA 声明谁优先用户重要样式可覆盖作者,保障可访问性
Context声明位于哪个封装上下文Shadow Tree 内外有明确边界
Layer声明属于哪个 @layer普通声明后层胜;重要声明层序反转
Style / Specificity内联样式或选择器权重如何不要用 ID 和 !important 堆债务
Scope Proximity@scope 根相隔多少层同权重时更近作用域胜
Order其余条件相同,谁最后出现最后的确定性裁决

这张渲染流水线是成本模型,不是所有浏览器实现的固定线程图。改变 width 往往需要布局、绘制和合成;改变 background 通常跳过布局;在合适图层上改变 transformopacity 可能只需合成。是否真的如此应以 Performance 与 Layers 面板为准,不能从属性名直接保证。

继承发生在每个属性定义允许时。自定义属性默认继承,其值在使用点参与计算:

:root { --accent: oklch(55% .14 185); }
.panel { border-color: var(--panel-border, var(--accent)); }

回退值只在变量缺失或无效时启用,不会自动证明业务语义正确。需要类型、初始值和继承控制时可用 @property 注册;否则保留普通自定义属性,少建一层配置系统。

Flex:主轴空间分配与收缩

Flexbox 面向一维布局。容器先确定主轴和交叉轴,项目以 flex base size 参与计算;比较项目外边距盒总尺寸与容器内部主轴尺寸得到 free space,再按 grow 或 shrink 因子分配。flex: 1 常展开为 1 1 0%,并不等于“每项最终绝对相等”:min/max、内容最小尺寸、margin 和冻结步骤仍会改变结果。

.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .75rem;
}

.toolbar__search {
  flex: 1 1 18rem;
  min-inline-size: min(100%, 12rem);
}

.toolbar__actions {
  display: flex;
  flex: 0 0 auto;
  gap: .5rem;
}

主轴由 flex-direction 决定,所以 justify-content 沿主轴,align-items 沿交叉轴。使用 row-reverse/order 只改变视觉顺序,DOM 阅读/Tab 顺序通常不随之变化;不要用它修复错误内容顺序。多行 flex 每行独立分配空间,若需要跨行列对齐应使用 Grid。

收缩按 flex-shrink × flex base size 加权,而不是简单按 shrink 数字平均;大项目承担更多绝对收缩。内容仍溢出时检查自动最小尺寸、不可换行字符串、替换元素固有尺寸。overflow-wrap: anywhere 适合长 URL,不能代替正确尺寸约束。

Grid、Subgrid 与容器查询

Grid 面向二维轨道。显式 grid 由 grid-template-* 定义,放置到范围外的项目生成隐式轨道;auto-placement 根据 grid-auto-flow 寻找位置。轨道 sizing 会综合固定值、百分比、内在贡献、minmax 和剩余空间;fr 只分配轨道算法完成其他约束后的 leftover space。

.catalog {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
  gap: 1rem;
}

.catalog__featured {
  grid-column: 1 / -1;
}

auto-fit 会折叠空轨道,auto-fill 保留它们;两者在容器还能容纳空列时表现不同。命名线/区域适合页面结构,但媒体查询重排时仍保持 DOM 逻辑顺序。Grid 项的 z-index 可在无需 position 时参与层叠,但仍受 stacking context 限制。

Subgrid 让子 grid 在指定轴复用父轨道 sizing 和 line names,解决卡片内部跨行对齐。它不是穿透任意祖先:只继承直接作为 grid item 所在父 grid 的对应轨道,gap 和 line 命名也应在 DevTools 验证。

以下单文件样式覆盖显式层、Grid、Subgrid、容器查询、逻辑属性、流体排版、颜色方案和减少动效偏好。环境是示例仓库与现代常青浏览器。

@layer reset, base, layout, components;

@layer reset {
  *, *::before, *::after { box-sizing: border-box; }
  body, h1, h2, p { margin: 0; }
}

@layer base {
  :root {
    color-scheme: light dark;
    --paper: #f4f1e8;
    --ink: #18201f;
    --accent: #087f73;
    font-family: ui-sans-serif, system-ui, sans-serif;
  }

  body {
    background: var(--paper);
    color: var(--ink);
    line-height: 1.6;
  }

  h1 { font-size: clamp(2.5rem, 8vw, 6rem); }
  :focus-visible { outline: .2rem solid var(--accent); outline-offset: .2rem; }
}

@layer layout {
  main {
    inline-size: min(100% - 2rem, 72rem);
    margin-inline: auto;
  }

  .catalog {
    container: catalog / inline-size;
    display: grid;
    grid-template-columns: repeat(12, 1fr);
  }

  .entry {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: subgrid;
    padding-block: 1rem;
  }

  @container catalog (inline-size > 42rem) {
    .entry > * { grid-column: span 4; }
  }
}

@layer components {
  .entry { border-block-start: 1px solid color-mix(in oklch, currentColor 25%, transparent); }
  .entry:hover { background: color-mix(in oklch, var(--accent) 8%, transparent); }
  dialog { transition: opacity .18s ease, translate .18s ease; }
}

@media (prefers-color-scheme: dark) {
  :root { --paper: #111716; --ink: #edf4f1; --accent: #54d8c7; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    scroll-behavior: auto !important;
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
  }
}

把代码保存为示例的 styles.css,运行:

pnpm --filter @roadmap/web-foundations test
python3 -m http.server 4173

输入:访问 http://localhost:4173/examples/frontend-slices/web-foundations/,把视口从 390 px 调到 1280 px,并在系统中切换深色与减少动效偏好。

预期结果:窄容器中的条目按单列排列,宽于 42 rem 后复用父 Grid 轨道;系统深色偏好改变设计令牌;减少动效后进入动画近乎立即完成,焦点轮廓仍然可见。

Subgrid 让子网格沿指定轴继承父网格轨道尺寸和线名,适合多行内容对齐,不等于让任意后代穿透层级。CSS Grid Level 2

容器查询比较的是组件祖先查询容器,而非整个视口。container-type: inline-size 建立行内轴尺寸查询容器;规则作用于其后代,不能让容器依据自己的尺寸反过来改变自身并造成循环。CSS Containment Level 3

正常流与格式化上下文的布局决策

知识浏览器的样式表只做四件事:建立令牌、组织页面轨道、表达组件状态、响应用户环境。它使用 @layer reset, base, layout, components, utilities 明确覆盖关系,避免靠导入顺序和选择器升级打补丁。

布局决策如下:

区域布局机制选择理由降级行为
页头Flex标题与主题按钮是一维分布窄屏改为纵向
筛选区Grid标签、输入和按钮需要列对齐媒体查询切单列
目录与选择摘要Grid主工作区与次要上下文是二维区域窄屏按 DOM 顺序堆叠
技术条目Grid + Subgrid名称、摘要、操作跨行对齐不支持时仍按普通 Grid 展示
条目内部Container Query组件宽度可能与视口无关默认单列最安全
对比表正常表格布局保留二维数据语义容器横向滚动,不压碎文字

逻辑属性 inline-sizemargin-inlinepadding-block 让版式服从书写模式;媒体查询只处理页面级空间,容器查询处理条目本身。颜色由少量令牌控制,交互状态仍使用边框、文字和 aria-pressed,不把颜色作为唯一信号。

样式表没有 CSS-in-JS、预处理器或工具类框架。这个固定示例不需要构建期样式抽象;原生 CSS 已能表达层、嵌套上下文、查询和颜色计算。以后只有在真实产品出现跨包主题发布或大规模无用样式问题时,再引入相应工具。

排版、颜色与用户环境

文本排版先建立可读基础版本:系统或已加载字体族、可缩放字号、约 1.5 的正文行高、受控行宽和清晰层级。font-display 决定 Web Font 加载期间行为;字体文件是网络资源,会影响布局和隐私。优先可变字体/必要字重子集,但必须测量真实产物。

:root {
  color-scheme: light dark;
  font-family: system-ui, sans-serif;
  font-size: 100%;
  line-height: 1.6;
  text-rendering: optimizeLegibility;
}

.prose {
  max-inline-size: 70ch;
  text-wrap: pretty;
  hyphens: auto;
}

.prose h1,
.prose h2 {
  line-height: 1.15;
  text-wrap: balance;
}

text-wrap: balance/pretty 是排版增强,不能依赖它避免关键溢出。line-height 用无单位值让子元素按自身字号计算。全局禁止 text-size-adjust、固定 px 字号或 user-scalable=no 会破坏放大需求。

颜色不仅是十六进制。oklch 的 lightness/chroma/hue 更接近感知维度,color-mix 可派生状态;是否可读仍需按实际前景、背景、字号和状态测对比度。currentColor 让边框/图标跟随文本;系统颜色如 Canvas/CanvasText 有助于强制颜色模式。

用户环境查询应改变表现而不删除功能:

查询应对
prefers-color-scheme提供浅/深令牌并声明 color-scheme
prefers-reduced-motion减少非必要位移、缩放和自动运动,保留状态反馈
prefers-contrast增强边界和区分;仍测试 forced-colors
forced-colors允许系统颜色接管,不用背景图作为唯一状态
pointer/hover调整触控尺寸与 hover 增强,不推断设备类别

响应式设计从内容约束出发,不按具体设备型号堆断点。先写最小可用单列,再在内容需要空间时添加 @media (width >= ...);组件内部使用容器查询。打印样式、横竖屏和 200% 缩放都是环境,不应被“桌面/移动”二分遗漏。

过渡、关键帧与离散属性

transition 在属性计算值变化时插值;animation 由 keyframes 与时间模型驱动。并非所有属性可动画,display 等离散属性不会连续插值;新离散过渡能力需要 transition-behavior: allow-discrete@starting-style 等机制,并按支持范围增强。

[popover] {
  opacity: 0;
  translate: 0 -.5rem;
  transition:
    opacity .18s ease,
    translate .18s ease,
    display .18s allow-discrete,
    overlay .18s allow-discrete;
}

[popover]:popover-open {
  opacity: 1;
  translate: 0 0;
}

@starting-style {
  [popover]:popover-open {
    opacity: 0;
    translate: 0 -.5rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  [popover] { transition-duration: .01ms; }
}

动画结束事件不是提交业务状态的可靠唯一入口:减少动效、元素移除、取消或未生成动画都可能改变事件。业务状态先更新,动画只呈现结果。无限动画暂停后台/不可见状态,避免 CPU 与电量浪费。

渲染流水线、溢出与动画成本

调试层叠时,在 Computed 面板找到最终值,再展开匹配声明,按层叠阶段解释输赢。先检查层和来源,最后才看权重。盲目加 !important 会让重要层顺序反转后的结果更难预测。

布局问题使用 Layout 面板显示 Grid/Flex 轨道、盒模型和滚动容器;溢出时依次检查可用尺寸、最小内容尺寸、min-inline-size: auto 与不可换行内容。1fr 不保证轨道能缩到零,常见修复是 minmax(0, 1fr) 或给项目设置 min-inline-size: 0,而不是全局 overflow: hidden

性能上:

  • 避免在循环中交替读取布局属性和写入样式,防止强制同步布局。
  • content-visibility: auto 可跳过屏外子树的部分渲染工作,但需要合理的固有尺寸,并验证查找、焦点和可访问性行为。
  • will-change 会消耗内存,不是通用加速开关;仅在已测得的短期过渡前使用。
  • 动画优先选择不改变文档几何的属性,但仍需在目标设备用 Performance 验证。
  • View Transitions 会捕获旧、新视觉状态并生成伪元素动画;它是增强,不应阻断状态更新。CSS View Transitions Level 1

安全边界容易被忽略:CSS 可以加载外部资源、覆盖安全提示、制造点击遮挡或通过复杂选择器泄露有限状态。CSP 的 style-src、可信资源源站和可见焦点都要保留。不要把未经约束的用户输入直接插入 <style>style 属性。

常见误区:把 z-index: 999999 当层级方案;用固定高度掩盖内容增长;只测鼠标 hover;为每个断点复制整份规则;把 DOM 顺序改乱后再用 Grid 视觉排序;在减少动效偏好下直接删除所有状态反馈。

CSS 模块演进与渐进增强

CSS 由多个模块独立演进,不能用“CSS3/4”概括支持状态。应记录具体模块级别、功能与核验日期。Cascade Layers、Subgrid、Container Queries 和 :has() 已成为现代工程常用能力;@scope、View Transitions、锚点定位等较新能力仍应按目标浏览器核验并渐进增强。

@scope 限定选择器的有效树范围,并引入 scope proximity 决胜条件;它解决局部覆盖,不替代 Shadow DOM 的封装。View Transitions Level 2 继续描述同源跨文档导航、transition type 与共享样式等能力,但其 W3C 页面仍标为 Working Draft,因此不能把草案细节当稳定契约。CSS View Transitions Level 2

未来升级的最低条件:功能在目标环境可用,失败时核心内容仍成立,并且它明显减少现有复杂度。若为了使用新语法同时引入大量 polyfill 和分支,先比较不使用该功能是否更简单。

新 CSS 能力的采用与回退难点

CSS 模块独立演进,没有一次覆盖全平台的“CSS 4 重构”。真正需要迁移审计的是默认值、包含块、滚动容器、层叠上下文和新功能回退是否改变布局。采用新能力时把 @supports 当增强开关,不把旧浏览器分支复制成另一套完整设计。

高风险变化常见严重表现最短修正
引入 @layer 后原有规则跨层竞争原本靠源码顺序覆盖的组件突然失效先声明稳定层序,再减少跨层 !important
给祖先添加 transformfiltercontainposition: fixed、绝对定位或层叠上下文参照改变在 Layout/Layers 面板确认包含块与 stacking context
min-width: auto / min-height: auto 参与 Flex/Grid内容拒绝收缩并造成整页溢出在真正可收缩项设置 min-inline-size: 0,不要全局隐藏 overflow
容器查询未建立 containment context查询永不匹配或尺寸形成循环明确 container-type,查询样式避免反向改变被查询轴
动画依赖布局属性主线程持续 layout/paint,输入延迟升高先测量;能保持语义时改用 transform/opacity,保留 reduced-motion

回归应至少覆盖目标浏览器、375 px、200% 缩放、长文本、键盘焦点和两种颜色方案。视觉截图能发现像素变化,却不能证明 DOM 阅读顺序、焦点可见性或渲染成本;这些必须由 Accessibility、Computed 和 Performance 证据补齐。CSS ContainmentCSS Overflow

Flex、Grid 与查询条件的约束维度

约束正常流FlexGridSubgridContainer Query
文档线性阅读首选可用可用不适用不负责布局
单轴分配与对齐有限首选可做但偏重不适用只切换条件
二维轨道不适合需换行推断首选继承父轨道只切换条件
跨多行对齐不适合困难同一网格内可行首选不负责对齐
组件随容器变化依赖固有换行依赖固有换行依赖固有轨道依赖父轨道首选

页面断点用媒体查询,因为它描述视口和用户偏好;可复用组件用容器查询,因为同一组件可能同时出现在主栏和侧栏。Flex 与 Grid 不是竞争版本:看约束维度选最小能力。

DevTools 布局与性能练习

  • 在 DevTools 中选一条被覆盖声明,按相关性到顺序完整解释层叠结果。
  • 建立三个 layer,分别验证普通声明与 !important 的层顺序为何相反。
  • 用 Flex、Grid 各实现一次条目行,并根据约束说明保留哪一个。
  • 把同一条目放进主栏和侧栏,确认容器查询按组件宽度而非视口触发。
  • 在 200% 缩放、390 px 视口和长英文 URL 下检查是否丢内容。
  • 开启深色、高对比和减少动效偏好,确认状态不只依赖颜色或动画。
  • 录制一次筛选交互,区分 Style、Layout、Paint、Composite 成本。

验收标准:能够从层叠链解释“为什么没生效”,从约束维度选择布局,并用性能录制而非经验判断优化。如果答案只是“提高权重”“多加断点”“加 transform”,说明模型仍未建立。

CSS 模块规范与观察资料

在 GitHub 上编辑此页

本页目录