CSS:不可避免的坏部分

Lobsters Hottest 新闻

摘要

一位非Web开发者的个人博客文章讨论了CSS中不可避免的坏部分,包括布局难题、浏览器默认设置和过度使用包装器,同时强调了可处理简单任务的子集。

<p><a href="https://lobste.rs/s/pwmlvz/css_unavoidable_bad_parts">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/06/09 12:43

# CSS:不可避免的糟糕部分 来源:https://matklad.github.io/2026/06/04/css-unavoidable-bad-part-en.html 2026年6月4日 这是一篇面向那些需要为网页添加样式但并非 Web 开发者的替代性 CSS 教程。我并非写这类内容的最佳人选,既没有时间也没有经验。我更愿意去读一本相关的书。可惜,我必须通过在 MDN 上苦苦搜寻来学习所有这些内容,所以或许记录下我目前所学是有价值的。 CSS、HTML 和 Web API 确实非常庞大,需要整个职业生涯才能成为专业人士。好消息是,现代 Web 有一个规模适中、可学习的子集,足以完成简单的任务,比如一个编程博客或一个简单的 GUI。我还没见过只教授这个子集的资源,但搞清楚这一点并不太难。坏消息是,还有一堆讨厌的陷阱,它们会搞乱你的页面,而你根本不会想到它们的存在,需要花费数天调试才能发现。不过,情况也没那么糟。我对这个网站的样式非常满意,它只有大约 200 行可读的 CSS(https://matklad.github.io/css/main.css)。 ****好的:**** HTML5 语义化标签名 值得浏览一下 MDN 元素参考(https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements)。元素并不多,像 `main`、`article`、`nav`、`kbd` 这些标签让页面结构更加清晰。不那么明显的: - `ul` 用于任何类型的列表,比如 `header > nav` 中的站点导航。 - `details` 用于目录(查看 MDN 的源代码)。 - `dl`/`dt` 用于键值对列表。 ****糟糕的:**** 包装元素(Wrapper) 如果你在任何“真正的”网站上“查看源代码”,会发现每样东西都有层层叠叠的包装元素,这可能会让你误以为包装元素是解决布局问题的唯一方式。我无法完全赞同或反对,因为我从未写过“生产环境”的 CSS,但根据我的经验,反其道而行之会更容易理解——只使用有标记含义的语义标签,然后找出与你现有标记配合的 CSS。 ****糟糕的:**** 布局 这并非 Web 独有的问题,在我知道的每一个 GUI 框架中,布局都是一场斗争。想象一张固定大小的位图,以及一段描述它的文字。在屏幕的矩形区域中,有多种方式排列这两个元素。通常,对于任何给定的宽度和高度,只要总面积足够,你都能做得不错。一个典型的 GUI 就是这种盒子的层次结构,具有很大的“布局自由度”。但问题是,每个盒子的布局会影响所有其他盒子的布局,因为你通常希望所有盒子恰好贴合,没有间隙和重叠。一个重要的负面认识是:*不存在*通用的布局算法。没有完全通用的解决方案来定位和调整 GUI 盒子的大小。相反,不同的系统使用不同的启发式方法来完成任务,从简单的 RectCut(https://web.archive.org/web/20210306102303/https://halt.software/dead-simple-layouts/)到完全通用的约束求解器(https://github.com/inamiy/Cassowary),以及介于两者之间的一切(https://www.youtube.com/watch?v=UUfXWzp0-DU)。要形成布局如何工作的心智模型,*总体上*是很困难的。所以,不要想“我如何在给定的系统中实现我的布局”,而是想“这个系统允许哪些可能的布局”。 ****糟糕的:**** 浏览器默认样式 从一个没有 CSS 的博客文章的裸(但语义化)HTML 标记开始。如果你在浏览器中打开它,它会显示*某种*东西。内容并非没有样式——文本有某种颜色、字体和大小。标题比正文大,链接有下划线,等等。这些就是你的浏览器的默认样式。它们很有用!问题是这些样式在不同浏览器之间是不同的。所以,即使你添加了自己的 CSS,最终结果在你的浏览器中看起来不错,我可能看到的是不同的,因为你可能依赖了某个浏览器默认样式而不自知。最后一点是关键——问题出在你*没有写*的东西上。 这里的一般解决方案是 CSS 重置(https://www.joshwcomeau.com/css/custom-css-reset/)或归一化——用一组明确的规则覆盖默认值来开始你的 CSS。不是因为默认值本身不好,而是因为它们不一致。我不知道在实践中需要覆盖*哪些*规则,但比较几个现有的 CSS 重置是一个好主意。 这触及到一个大问题:*应该*为你的网页添加样式吗?Web 平台有两种相互竞争的观点——一些人将其视为一种灵活、自适应、主要以视觉表达设计为媒介,另一些人则希望 Web 专注于提供内容,允许每个用户自定义呈现。我个人在此的回答是务实的——默认情况下,一个没有样式的页面可用性差且看起来糟糕。我宁愿生活在一个没有 CSS 的页面也能直接阅读的世界,但在这个世界里,我认为为内容添加样式是有帮助的。同时,允许高级用户使用他们自己的 CSS 是个好主意。确保你的 HTML 标记是合理的,不要让你的 HTML 过度适配 CSS(反之则没问题),并确保你的页面在阅读器模式下也能正常工作。 ****好的:**** 无类 CSS(https://boltcss.com/) 你不可能将样式重置为真正的完全中性:如果你让文字不可见(白色或透明),它仍然是一种样式。所以你不妨接受它:在重置后,直接对常见的 HTML 元素应用样式。例如,为所有代码片段设置你喜欢的字体: ```css code { font-family: "JetBrains Mono", monospace; } ``` 如果你使用 `main`、`header`、`footer`、`nav` 标签,你可以设置整个页面布局而无需编写任何 CSS 选择器。这当然需要在 CSS 中对 HTML 的结构做出假设,但,这是你的 HTML 和你的 CSS,你可以做任何事,如果你不喜欢结果,随时可以修改! ****糟糕的:**** CSS 选择器 在编程领域,我们逐渐形成了对继承的怀疑并偏爱组合。默认的 CSS 就像增强版的继承,你页面上的每个设计元素受到多条规则的影响,而且你总是可以通过在 CSS 末尾追加内容来“猴子补丁”现有元素。CSS 的 affordances 与你实际想要做的事情之间存在不幸的差距。两种合理的方法是: 1. 认定 CSS 选择器在错误的维度上增加了抽象能力,从而坚持无类 CSS 和内联样式,使用像 Tailwind 这样的工具让内联写法更美观,并使用像 JSX(或任何支持组合的模板引擎)来避免 HTML 中的重复。 2. 使用 CSS 嵌套(https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Nesting)来避免编写“影响范围广”的选择器,并逐个组件地应用样式: ```css header { /* 站点头部 */ margin-bottom: 2rem; & nav { /* 头部中导航的特定样式。 */ } } ``` ****糟糕的:**** 盒模型(box-sizing)(https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/box-sizing) UI 是递归的矩形,布局就是找出每个矩形位置的过程,而这由矩形本身的大小决定。因此,理解*什么是*大小非常基础。遗憾的是,默认情况下 HTML 中大小的定义非常不直观:元素的宽度和高度不包括元素的边框和内边距,这导致令人惊讶的结果:起初一切看起来完美,但在某处增加内边距会意外地移动整个布局。出于这个原因,`* { box-sizing: border-box; }` 应该成为你 CSS 重置的第一行。它让元素变得封装,使得添加边框只影响局部。 ****混乱善良:**** 边距折叠(https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Box_model/Margin_collapsing) 假设你想在一个元素周围有 `8px` 的间隙。你*认为*你需要设置 padding 属性。但那就错了——如果你把两个这样的元素放在一起,它们之间的间隙将是 `16px`。内边距会相加,产生比预期更大的视觉间隙。你需要的更像是社交距离,如果一个人更内向,这个人更大的排斥半径就是定义距离的依据。这就是 `margin` 属性的工作方式。两个相邻的外边距使用 `max` 而不是 `sum` 合并。边距折叠非常有用,但它可能会让你意外。例如,我*认为*子元素的 margin 可以超出父元素的范围?说实话,我对 margin 没有很好的直觉理解,但我至少知道如何识别它是否是问题。 Margins 也是这篇帖子的间接灵感之一。在《Moving away from Tailwind, and learning to structure my CSS》(https://jvns.ca/blog/2026/05/15/moving-away-from-tailwind--and-learning-to-structure-my-css-/)中,Julia Evans 写道,通常你不会想直接在元素上设置 margin,而应该让父元素通过所谓的猫头鹰选择器来控制子元素之间的间距: ```css section > * + * { margin-top: 1rem; } ``` 也就是说,为 `section` 除第一个子元素外的所有子元素添加 margin。我之前不知道这个!而且,鉴于 margin 之前给我带来的所有痛苦,我确实理解为什么你想这么做,以及为什么这是一个好主意。但让我困扰的是,如果不成为“专业”的 Web 开发人员,或者不反向工程他人的 CSS 框架,你就无法学到这一点。 ****糟糕的:**** 默认(流式)布局(https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Display/Block_and_inline_layout) 布局总体上很棘手,因为没有通用的“布局算法”,只有一堆特例。但 HTML 实际上做了什么?默认布局算法我*认为*可以追溯到 HTML 作为文档语言的起源,并且过度适配了生成论文的用例——主要是带有一些插图的文本内容,文本可以围绕图片流动。这对于你博客正文的主要文本来说实际上正是你想要的,但一旦你想实际控制页面上元素的空间排列,你就需要别的东西,例如…… ****好的:**** 弹性盒子(flexbox)(https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/CSS_layout/Flexbox) 这确实是现代 Web 开发与旧时代的分水岭,在旧时代,你需要一个 CSS 博士学位或一个完全不透明的 CSS 框架才能说出“这个向左,这个向右”。这种布局允许你将一系列元素垂直或水平排列,并适应可用空间。它相当复杂,我无法在不频繁查阅 MDN 的情况下使用 flexbox,但通常我最终能搞定。 ****糟糕的:**** 响应式设计 现代 CSS 允许查询屏幕尺寸,并据此实现条件逻辑——一种“响应”用户代理约束的设计。这可能是“真正”的 CSS 应该使用的,但请注意 HTML *本身*就是响应式的。与 PostScript(PDF)不同,当你改变窗口大小时,它会自动重排段落。所以,一个好主意是避免编写*显式*的响应式规则,而是依靠布局来做出合理的事情。例如,这个博客在移动端、平板和桌面端看起来都不错,没有任何显式的 `@media` 查询。无条件地在主文本列上设置 `max-width` 就足够了。 ****守序邪恶:**** 像素 `1px` 做的事情是你想要的,但并非字面意思。它*不是*你屏幕上一个物理像素的大小。相反,它是一种视角(visual angle)度量(http://inamidst.com/stuff/notes/csspx)。也就是说,`1px` 应该在任何屏幕上看起来在感知上相同,并且根据屏幕尺寸、像素密度和典型观看距离被转换为不同数量的物理像素。所以你可以*直接*以像素为单位缩放一切,而无需考虑不同显示屏的像素密度。这变得更奇怪了。CSS 允许“真实”单位,如厘米或英寸,但它们*也是*角度,因为所有东西都*定义*为像素。 ****双倍糟糕:**** 字体大小(https://tonsky.me/blog/font-size/) Flexbox 是布局 UI 元素的好方法。流式布局对于排列文本段落还行。但在单行和字形层面上发生的事情,在我看来是一场灾难和新手陷阱。让我们从基础开始:如果你写 `font-size: 16px`,那么 `16px` 是什么的大小?遗憾的是,答案是“没有什么特别”——这是字形周围一个虚拟盒子的大小,但这个盒子并不紧密,字形的大小因字体而异。幸运的是,`font-size-adjust` 属性可以修复这个问题,并使 `font-size` 在不同字体之间保持一致。详情见这两篇文章: - font-size-adjust 很有用(https://matklad.github.io/2025/07/16/font-size-adjust.html) - 字体大小没用;让我们修复它(https://tonsky.me/blog/font-size/) 不过,目前 `font-size-adjust` 似乎非常小众,所以虽然我个人会把 `font-size-adjust: ex-height 0.53;` 放在 `box-sizing` 旁边,但很少有页面这么做。 关于 `font-size` 的下一个问题是一个棘手的默认值问题。好消息是它是跨浏览器比较一致的属性之一,`16px` 是压倒性的默认值。坏消息是,根据不同的字体,`16px` 可能偏小。不是完全无法阅读,但非常接近下限。更糟糕的是,一些*默认*字体特别小。例如,在 Apple 上,`font-family: serif` 看起来比 `sans-serif` 小得多,并且在 16px 下几乎难以舒适阅读。 你可以直接设置 `font-size: 18px` 或对你选定的字体效果最好的大小吗?我认为答案是肯定的,但有一些注意事项需要牢记。细节请参考《可访问性:px 还是 rem?》(https://matklad.github.io/2022/11/05/accessibility-px-or-rem.html)。问题是现代浏览器支持两种方式使页面上的文字变大: - 缩放,有专门的 UI 元素、快捷键/手势、每页的持久化/覆盖以及全局默认值。 - 更改默认字体大小,这是一个深藏在配置页面中的全局设置。 在你的 CSS 中设置 `font-size` 会禁用第二种方式。 综合所有这些:不要假设你页面上的文本默认是可读的,检查不同的配置。设置 `font-size-adjust` 以减少自由度数并固定 `font-size` 的含义。如果结果在你选定的(或用户默认的)字体以及默认的 `16px` 字体大小下看起来不错,那么你就完成了。否则,将 `font-size` 设置为一个更大的数字。之后,检查页面在阅读器模式下是否也可读。 ****糟糕的:**** 行高(line-height)(https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/line-height) 尽管名字叫“行高”,`line-height` 并不设置一行的实际高度。它是一段字形(使用相同字体设置)的高度。当所有文本使用同一种字体时,两者是一致的。但如果你有一些单词设置为 `monospace` 字体,你就会遇到意外。虽然 `font-size-adjust` 修复了字形在盒子内部的大小,但它仍然留下了未指定的相对位置。所以,当两段不同字体的文本垂直对齐以共享基线时,它们的 line-height 行框会相对移动:一个下伸,一个上伸。最终的行高整体上会大于你预期的值,因为它被配置为它们的并集。参见《深入 CSS:字体度量、行高和垂直对齐》(https://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align)以获得这个效果的详细解释。 ****糟糕的:**** 垂直节奏 如果你搜索足够久,这堆问题

相似文章

引用Julia Evans

Simon Willison's Blog

Julia Evans分享了她学会爱上CSS的见解,解释了许多常见的烦恼已经得到解决,CSS的复杂性反映了布局问题的难度。

告别Tailwind,学习组织CSS

Lobsters Hottest

作者反思了从Tailwind CSS迁移到带语义HTML的原生CSS的过程,分享了利用从Tailwind学到的重置、组件和工具类等系统来组织CSS的心得。

简单HTML的超凡有效性

Hacker News Top

一篇博文,论证了简单HTML对于普遍可访问性的持续重要性,并通过一位女性使用PSP浏览器访问政府服务的故事加以说明。

不要自己造轮子…

Lobsters Hottest

作者将“不要自己造轮子”的原则扩展到Web开发领域,反对自定义实现滚动、链接导航、文本选择等浏览器原生行为。