Skip to content

🎨 卷① 现代 CSS 布局与视觉

20 条。适用场景:这个布局用现在的 CSS 该怎么写,而不是用五年前的办法

「要点」「我的补充」为本人撰写,非原文摘录。

← 返回前端资料库总览


一、布局基础:两份该收藏的完整指南

1. A Complete CSS Flexbox Layout Guide

来源:CSS-Tricks · css-tricks.com 读它解决什么:Flexbox 的属性你记不住哪个加在容器、哪个加在子项——这份是查表用的。

要点

  • Flexbox 是一维布局:主轴由 flex-direction 决定,交叉轴垂直于它,所有对齐属性都是相对这两个轴而言
  • 容器属性(justify-contentalign-itemsflex-wrap)与子项属性(flex-growflex-shrinkflex-basisalign-self)分工明确,混淆是最常见的错误
  • flex: 1flex: 1 1 0% 的简写,与 flex: auto1 1 auto)行为不同——后者会考虑内容尺寸

我的补充:最经典的坑是 flex 子项不收缩到比内容更小flex-shrink: 1 也拦不住,因为默认 min-width: auto。文本溢出撑破容器时加 min-width: 0(或 overflow: hidden)就好,这一条能省你几个小时。另外要垂直居中现在直接 display: flex; align-items: center; justify-content: center 就够,不需要 transform 那套老办法。

阅读原文 →


2. A Complete CSS Grid Layout Guide

来源:CSS-Tricks · css-tricks.com 读它解决什么:需要二维布局(行列同时控制)时,Grid 的术语和语法一次查清。

要点

  • Grid 是二维布局,能同时定义行与列,这是它与 Flexbox 的根本分野
  • grid-template-areas 用字符画的方式描述布局,可读性极好,适合页面级框架
  • minmax()auto-fit/auto-fillfr 单位组合起来能做出无需媒体查询的响应式网格

我的补充:选择判据很简单:一个方向排列用 Flexbox,需要行列对齐用 Grid。最实用的一行是 grid-template-columns: repeat(auto-fit, minmax(280px, 1fr))——卡片列表自动按容器宽度换行,完全不用写断点。这个博客的多个页面用的就是它。auto-fitauto-fill 的区别在于空轨道是否折叠,多数情况你想要 auto-fit

阅读原文 →


3. CSS Container Queries

来源:CSS-Tricks · css-tricks.com 读它解决什么:组件需要根据自己所在容器的宽度变样式,而不是视口宽度。

要点

  • 媒体查询问的是"视口多宽",容器查询问的是"我的父容器多宽",后者才是组件化的正确粒度
  • 用法分两步:容器上声明 container-type: inline-size,子元素里写 @container (min-width: 400px)
  • container-type 会创建包含块并影响布局,声明后容器的尺寸计算方式会变

我的补充:这是组件库开发的分水岭——同一个卡片组件放在侧栏和主区域能自动适配,不必再靠外部传 size prop。两个坑:一是 container-type: size 需要显式高度否则塌陷,多数情况该用 inline-size;二是容器查询不能查询自身,必须查父容器,所以要多包一层。配合 container-name 可以指定查询哪一级。

阅读原文 →


4. CSS Anchor Positioning Guide

来源:CSS-Tricks · css-tricks.com 读它解决什么:做下拉菜单、tooltip、弹出框时,想让元素相对另一个元素定位而不用 JS 算坐标。

要点

  • 锚点定位让元素声明"我锚定在某个元素上",浏览器负责算位置,替代了 Popper.js 这类库的核心职责
  • anchor-name 声明锚点,position-anchoranchor() 函数消费它
  • position-try-fallbacks 处理边缘溢出:空间不够时自动换到备选位置

我的补充:这个特性能删掉前端项目里相当可观的一坨代码——tooltip 定位逻辑历来是 bug 温床(滚动、resize、溢出翻转都要手写)。但浏览器支持还不均衡,Safari 跟进较慢。落地策略是渐进增强:用 @supports (anchor-name: --x) 包起来,不支持时退回原来的 JS 方案或简单的 position: absolute。这个博客的 tooltip 目前还是 JS 实现的。

阅读原文 →


5. Cascade Layers Guide

来源:CSS-Tricks · css-tricks.com 读它解决什么:样式被覆盖了却找不到是谁覆盖的——级联层能从根上治这个病。

要点

  • @layer 显式声明优先级顺序,层内的选择器权重不会越过层的边界
  • 由此可以把第三方库放进低优先级层,自己的样式放高优先级层,不必再靠提高选择器权重或 !important 打架
  • 声明顺序决定层优先级,未分层的样式优先级最高

我的补充:最大价值是终结 !important 军备竞赛。实践模式:@layer reset, vendor, base, components, utilities; 一次定好顺序,之后 @layer components { ... } 往里填。引入第三方 CSS 时用 @import url(...) layer(vendor),它的样式就永远压不过你的。注意未分层样式优先级最高这点很反直觉,迁移中的项目要留意。

阅读原文 →


6. CSS Custom Properties Guide

来源:CSS-Tricks · css-tricks.com 读它解决什么:CSS 变量的作用域、继承与回退规则,一次搞清。

要点

  • 自定义属性是运行时的,会继承、能被 JS 读写,这与 Sass 变量(编译期替换)本质不同
  • var(--x, fallback) 的回退只在变量未定义时生效,定义了但值非法会走"无效时的保证值"(unset)
  • 可以用 @property 注册类型,从而让自定义属性支持动画过渡

我的补充:主题切换的标准做法就是它::root 定义一套,[data-theme="dark"] 覆盖,切换只需改一个属性。未注册的自定义属性无法过渡动画——想让颜色渐变必须用 @property { syntax: '<color>'; } 注册。JS 侧读取用 getComputedStyle(el).getPropertyValue('--x'),注意返回值带前导空格,要 .trim(),这个坑我踩过。

阅读原文 →


7. CSS Media Queries Guide

来源:CSS-Tricks · css-tricks.com 读它解决什么:媒体查询除了宽度还能查什么——有些能力被长期忽略。

要点

  • 除了 width,还能查 prefers-color-scheme(深色偏好)、prefers-reduced-motion(减少动画)、hover(是否支持悬停)、orientationdynamic-range
  • 区间语法(@media (400px <= width <= 800px))比 min-width/max-width 组合可读性好得多
  • prefers-reduced-motion可访问性要求而不是可选项

我的补充:两条应该成为默认习惯。一是所有动画外面包一层 @media (prefers-reduced-motion: no-preference),或者反过来在 reduce 时禁用——前庭功能障碍用户会因为视差滚动产生眩晕。二是@media (hover: hover) 包住 hover 效果,否则触屏上会出现"点一下之后样式卡住"的诡异状态。这两条改起来很便宜,收益却实在。

阅读原文 →


8. CSS Color Functions

来源:CSS-Tricks · css-tricks.com 读它解决什么:需要在 CSS 里生成配色(变亮、变暗、混合),不想靠预处理器。

要点

  • 现代颜色函数支持更宽的色域与更符合感知的色彩空间:oklch()lab()hsl() 更适合做等亮度调色
  • color-mix() 直接在 CSS 里混合两色,light-dark() 按主题给两个值
  • 相对颜色语法可以基于一个基色派生整套(改亮度、改饱和度)

我的补充oklch() 的实际好处是同一亮度值在不同色相上看起来真的一样亮——用 hsl() 做主题色时,黄色和蓝色在相同 L 值下亮度感知差很多,这是配色难调的技术原因。落地建议:主色用 oklch() 定义,派生色用 color-mix() 或相对颜色语法,比手工列一堆十六进制好维护。老浏览器降级用 @supports 或直接给两次声明。

阅读原文 →


9. CSS Length Units

来源:CSS-Tricks · css-tricks.com 读它解决什么vh 在手机上不对、emrem 什么时候用哪个——单位的选择有明确依据。

要点

  • em 相对当前元素字号(会嵌套累积),rem 相对根字号(稳定),做间距体系用 rem 更可控
  • 视口单位家族要分清:vh 是大视口高度,svh(小)、lvh(大)、dvh(动态)针对移动端浏览器工具栏伸缩
  • chex 与字体度量相关,适合排版场景(比如限制正文每行字符数)

我的补充移动端 100vh 撑出滚动条的经典问题,正解是 100dvh(动态视口高度,随地址栏收放变化),以前只能用 JS 监听 resize 写 CSS 变量。注意 dvh 会导致滚动时布局变化,做全屏遮罩用 100dvh,而固定高度区块用 100svh 更稳。正文宽度限制推荐 max-width: 65ch,比固定 px 值在不同字号下都合适。

阅读原文 →


10. CSS Selectors

来源:CSS-Tricks · css-tricks.com 读它解决什么:选择器除了类和标签还有一大批被忽略的,尤其新的函数式伪类。

要点

  • :is():where() 都能合并选择器列表,区别是 :where() 权重为零:is() 取列表中最高权重
  • :has() 是"父选择器":.card:has(img) 选中包含图片的卡片,这是长期缺失的能力
  • :not() 现在支持复杂选择器列表

我的补充:where()写库时的关键工具——用它包住默认样式,使用者不必提高权重就能覆盖。:has() 的实际用法比想象中多:form:has(:invalid) 给含错误的表单加样式、body:has(dialog[open]) 打开弹窗时锁滚动,都能省掉 JS 加 class 的往返。性能上 :has() 已经不是问题,但别写 :has(* * *) 这种大范围匹配。

阅读原文 →


二、动画与滚动

11. A First Look at Scroll-Triggered Animations

来源:CSS-Tricks · css-tricks.com 读它解决什么:元素滚动进视口时播放一次动画,想不用 IntersectionObserver。

要点

  • 滚动触发(trigger)与滚动驱动(driven)是两件事:前者进入视口就播完整动画,后者动画进度跟着滚动位置走
  • 触发式更符合"入场动画"的直觉需求,而此前 CSS 只提供了驱动式
  • 纯 CSS 实现意味着动画在主线程之外,不受 JS 阻塞影响

我的补充:这正好解决了 IntersectionObserver 方案的老问题——JS 加载完之前元素停在初始态(通常是 opacity: 0),网络慢时用户看到大片空白。CSS 方案没有这个窗口。但要记得配 prefers-reduced-motion 降级,入场动画是最容易引起不适的一类。渐进增强写法:默认可见,@supports 里再加动画。

阅读原文 →


12. Using Scroll-Driven Animations for Opposing Scroll Directions

来源:CSS-Tricks · css-tricks.com 读它解决什么:想做两个元素向相反方向移动的滚动效果(视差类)。

要点

  • 滚动驱动动画把时间轴换成滚动进度:animation-timeline: scroll()view()
  • 反向运动靠让两个元素在同一时间轴上跑相反的关键帧
  • scroll() 跟踪滚动容器的整体进度,view() 跟踪元素在视口中的可见进度,用途不同

我的补充:这类效果以前必须写 JS 监听 scroll 事件,而 scroll 事件在主线程上、高频触发,是滑动卡顿的常见来源——即使加了节流也会掉帧。CSS 版本跑在合成线程上,流畅度完全不同。视差效果是 prefers-reduced-motion 的首要适用对象,务必降级。判断支持:@supports (animation-timeline: scroll())

阅读原文 →


13. Scroll-Driven, Scroll-Triggered, Scroll States, and View Transitions

来源:CSS-Tricks · css-tricks.com 读它解决什么:这几个名字相近的滚动特性到底各管什么,选型时不要混。

要点

  • 四者定位不同:驱动(进度绑定滚动)、触发(进入即播)、滚动状态(@container scroll-state() 查询是否吸顶等)、视图过渡(页面/状态切换的连续动画)
  • 名字相似但适用场景几乎不重叠,混用会写出别扭的实现
  • 滚动状态查询解决的是"元素 position: sticky 吸顶时换个样式"这类需求

我的补充:这条值得当选型清单用。@container scroll-state(stuck: top) 特别实用——吸顶导航栏在吸住时加阴影,以前必须用 IntersectionObserver 配一个哨兵元素才能做到,是个人尽皆知的丑陋 hack。现在一条 CSS 搞定。这个博客的导航栏也可以这么改。

阅读原文 →


14. The "Most Hated" CSS Feature: cos() and sin()

来源:CSS-Tricks · css-tricks.com 读它解决什么:需要在 CSS 里做圆形排布、波形等几何计算。

要点

  • CSS 已支持三角函数,配合 calc() 与自定义属性可以在样式层完成几何布局
  • 典型用途是把 N 个元素均匀排在圆周上,此前必须靠预处理器循环或 JS 生成
  • 争议点在于可读性:数学表达式塞进 CSS 后维护成本上升

我的补充:配合 sibling-index() 才是完整形态——无需给每个子元素手写 --i,CSS 自己能拿到序号。做圆形菜单、雷达图刻度这类需求现在纯 CSS 可行。但别过度:复杂几何放 SVG 里更合适,CSS 三角函数的甜区是"简单规律的重复排布"。写的时候留一行注释说明公式,否则半年后没人看懂。

阅读原文 →


15. Animating CSS border-image

来源:CSS-Tricks · css-tricks.com 读它解决什么:想做渐变边框、动态描边效果。

要点

  • border-image 能用图片或渐变填充边框,比 border-color 表达力强得多
  • 渐变边框的传统做法是双层背景加 background-clipborder-image 更直接
  • 动画化需要注意可过渡的属性有限

我的补充:渐变边框有三种做法,各有代价:border-image(简单但不支持圆角)、双层 background + background-clip: padding-box, border-box(支持圆角,代码略绕)、伪元素叠加(最灵活但要管层级)。要圆角渐变边框就用双层背景那套,这是目前最可靠的方案。新特性 border-shape 值得关注,能解决圆角限制。

阅读原文 →


16. Get Ready For the Powerful CSS border-shape Property

来源:CSS-Tricks · css-tricks.com 读它解决什么:需要非矩形的边框形状(斜角、缺口、异形卡片)。

要点

  • border-shape 让边框跟随任意形状,而不是只能圆角矩形
  • 此前做异形卡片只能用 clip-path,但它会裁掉阴影和边框,效果受限
  • 有了它,异形元素也能有正常的边框与阴影

我的补充clip-path 裁掉 box-shadow 这个限制困扰了很多设计实现——票券式缺口卡片、斜切卡片都只能靠伪元素拼或者直接上 SVG 背景。这个属性还很新,现在别用在生产,先关注。当下要做异形卡片仍推荐 SVG 或者 mask 配合额外的阴影层。

阅读原文 →


三、细节与实践

17. Gap Decorations Are Now Available

来源:CSS-Tricks · css-tricks.com 读它解决什么:想在 Grid/Flex 的间隙里画分隔线,不想加空 div。

要点

  • 间隙装饰允许直接给 gap 区域绘制线条,语法类似 border
  • 此前的做法是给子项加边框再修掉首尾,或者插入分隔元素——两者都脆弱
  • gap 联动,子项数量变化时自动正确

我的补充:这个需求太常见了:卡片网格中间要细分隔线,换行时首尾不能多画。老办法(:not(:last-child) 加 border)在换行的 Grid 里就是错的,因为行末元素并不是 :last-child。这个特性直接消掉那类 hack。同样属于比较新的能力,用 @supports 保护。

阅读原文 →


18. Styling Counters in CSS

来源:CSS-Tricks · css-tricks.com 读它解决什么:需要自定义列表编号(章节号、多级编号、非数字序号)。

要点

  • CSS 计数器(counter-resetcounter-incrementcounter())独立于 HTML 的 <ol>,可以给任意元素编号
  • @counter-style 可以定义自己的编号系统(符号、汉字、循环)
  • 多级编号靠 counters()(复数形式)拼接

我的补充:技术文档的"1.2.3"式多级标题编号可以纯 CSS 实现,作者写 Markdown 时完全不用手写编号——这对长文档维护是质的差别,插入一节不必重编后面所有号。用 counters(section, ".") 拼接。中文场景可以用 @counter-style 定义"一、二、三",比 list-style-type: cjk-ideographic 更可控。

阅读原文 →


19. Dark mode toggles: two states are enough

来源:CSS-Tricks · css-tricks.com 读它解决什么:深色模式切换该做两态还是三态(浅/深/跟随系统)。

要点

  • 三态设计(含"跟随系统")增加了 UI 复杂度,而多数用户的实际需求是"现在给我我想要的那个"
  • 两态方案的关键是默认跟随系统,用户手动切换后才记住偏好
  • 涉及 prefers-color-schemelocalStorage 与首屏闪烁的处理

我的补充:这条与这个博客直接相关。两态实现的核心难点是避免首屏闪白(FOUC):主题判断脚本必须内联在 <head> 且在 CSS 之前执行,不能放进打包后的 JS——否则用户会看到浅色一闪。VitePress 已内置处理好了。另外 color-scheme: light dark 这个属性别忘了,它让浏览器的滚动条、表单控件也跟着变色。

阅读原文 →


20. 5 CSS Properties You Should Know for Better Text Designs

来源:Frontend Masters · frontendmasters.com 读它解决什么:正文排版看起来不专业,但不知道该调什么。

要点

  • 排版质量往往取决于几个不常用的属性:text-wrap: balance(标题换行均衡)、text-wrap: pretty(避免孤字)、hanging-punctuationtext-box-trim(裁掉字体的额外行高空间)
  • line-height 建议用无单位值(如 1.5),带单位会导致子元素继承计算值出错
  • 中文排版还要考虑 word-break 与标点挤压

我的补充text-wrap: balance 用在标题上是立竿见影的改善——避免出现"最后一行只有一个词"的难看换行,一行 CSS 换来明显的观感提升。正文用 text-wrap: pretty(代价是排版计算稍多)。中文场景注意 text-wrap: balance 有字符数上限,长段落无效。这个博客的文章标题可以直接加上。

阅读原文 →


本卷小结

三条最值钱的:

  1. flex 子项溢出加 min-width: 0。默认 min-width: auto 让它不收缩到比内容小,这是最高频的 Flexbox 疑难。
  2. 移动端全屏用 100dvh 不用 100vh。地址栏伸缩造成的多余滚动条问题,一个单位就解决。
  3. @layer 终结 !important 军备竞赛。第三方 CSS 用 @import url(...) layer(vendor) 引入,永远压不过你自己的样式。

← 返回总览 · 下一卷:浏览器平台能力 →