Josh Comeau 的自定义 CSS 重置库

原文链接:My Custom CSS Reset[1],2023.06.09,by Josh Comeau

Josh Comeau 的自定义 CSS 重置库

每当我开始一个新项目时,第一件事就是打磨 CSS 语言中的一些粗糙边缘。我使用一组自定义基线样式(baseline styles)来实现这一点。

很长一段时间,我都在使用 Eric Meyer 著名的 CSS Reset 设置[2]。不过它已经有十多年没有更新了,从那以后发生了很多变化!

最近,我一直在使用自己的自定义 CSS 重置。它包含了我发现的所有改进用户体验的一些 CSS 小技巧。

就像其他 CSS 重置一样,这套 CSS 重置样式不涉及 UI 层面,对此保持开放。你可以将此重置用于任何项目,无论 UI 风格如何。

在本教程中,我将向你介绍我的自定义 CSS 重置。将深入研究每一条规则,带你了解它的作用以及为什么要使用它!

我的 CSS 重置库

不多说了,先直接亮代码:

/*
  1. Use a more-intuitive box-sizing model.
*/

*, *::before, *::after {
  box-sizing: border-box;
}
/*
  2. Remove default margin
*/

* {
  margin0;
}
/*
  Typographic tweaks!
  3. Add accessible line-height
  4. Improve text rendering
*/

body {
  line-height1.5;
  -webkit-font-smoothing: antialiased;
}
/*
  5. Improve media defaults
*/

imgpicturevideocanvassvg {
  display: block;
  max-width100%;
}
/*
  6. Remove built-in form typography styles
*/

inputbuttontextareaselect {
  font: inherit;
}
/*
  7. Avoid text overflows
*/

ph1h2h3h4h5h6 {
  overflow-wrap: break-word;
}
/*
  8. Create a root stacking context
*/

#root#__next {
  isolation: isolate;
}

它相对较短,但这个小样式表中包含了很多内容。接下来就来一一介绍。


「✍️ 一个偏学究的笔记」

从历史上看,CSS 重置的主要目标是确保不同浏览器之间显示的一致性,并撤消(undo)所有默认样式,创建一个空白状态。我的 CSS 重置并不是这样。

如今,浏览器在布局(Layout)或间距(Spacing)方面没有太大的差异。总的来说,浏览器忠实地实现了 CSS 规范,并且行为如你所期望的那样。所以现在没必要撤消所有默认样式了。

我也不认为有必要撤消所有的浏览器默认设置。例如,我可能确实希望 <em> 标签设置 font-style: italic!我总是可以在各个项目样式中做出不同的设计决策,但我认为没有必要撤消常识性的默认值。

我的 CSS 重置可能不符合“CSS重置”的经典定义,采用了一个相对自由、具有创造性的形式。


盒子模型

来个突击测试,.box 是一个拥有粉红色边框的元素,假设在没有应用其他 CSS 样式的作用下,会有多宽?

<style>
  .parent {
    width200px;
  }
  .box {
    width100%;
    border2px solid hotpink;
    padding20px;
  }
</style>
<div class="parent">
  <div class="box"></div>
</div>

答案是 244px。下面我们就来分析一下。

我们的 .box 元素是 width: 100% 的,因为它的父对象是 200px 宽,所以 100% 将解析为 200px

默认情况下(默认盒子模型(box model)下),width 设置的是内容框(content box)的尺寸。如果你不熟悉“内容框”的概念也没有关系,它其实就是盒子模型中实际包含内容的那块矩形,在边框(border)和内边距(padding)内:

Josh Comeau 的自定义 CSS 重置库

width: 100% 声明将 .box 的 content-box 设置为 200px。内边距将增加额外的 40px(每边 20px)。边框添加了最后的 4px(每边 2px)。简单做下数学运算,就能得到粉色矩形可见宽度是 244px

因此,当我们试图将一个 244px 的盒子塞进一个 200px 宽的父盒子时,就会溢出:

Josh Comeau 的自定义 CSS 重置库

这种行为会有点很奇怪,不表态符合直觉。幸运的是,我们可以通过设置以下规则来改变它:

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

应用此规则后,宽度百分比将根据最外围的边框区域做解析。在上面的例子中,我们的粉色框将是 200px,内部内容框将缩小到 156px200px - 40px - 4px)。

在我看来,「这是一个必须遵守的规则」,它会让 CSS 更容易使用。

我们使用通配符选择器( * )将这个规则应用于所有元素(包括伪元素)。与流行的看法相反,这对性能并没有多少影响[3]


「✍️ 继承 box-sizing」

我在网上还看到了一些建议,是这样做的:

html {
  box-sizing: border-box;
}

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

如果你试图将一个大型的预先存在的项目迁移到使用 border-box,这可能是一个有用的策略。但如果你是从零开始一个全新的项目,这是没有必要的。为了保持简单,我在 CSS 重置中省略了它。

我们举个比较直观的例子说明一下:

首先,我们可以创建一个“legacy”选择器,将框大小翻转到 content-box,也就是box-sizing 属性的默认值:

.legacy {
  box-sizing: content-box;
}

然后,如果我们的应用程序中有一块区域没有迁移到 border-box 时,就可以使用这个类:

<body>
  <header class="legacy">
    <nav>
      <!-- 未迁移内容 -->
    </nav>
  </header>
  <main>
    <section>
      <!-- 已迁移内容 -->
    </section>
    <aside class="legacy">
      <!-- 未迁移内容 -->
    </aside>
  </main>
</body>

为什么这样做?我们来考虑一下这个代码片段中的元素所采用的盒子模型是如何计算的。

<header> 被赋予 legacy 类,所以它使用 box-sizing: content-box。它的孩子 <nav> 设置了 box-sizing: inherit。因为它的父级设置为 content-box,所以 <nav> 也是 content-box

<main> 标记没有 legacy 类,因此它继承自其父级 <body><body> 继承 <html><html> 被设置为 border-box

从本质上讲,每个元素现在都将从父元素中继承 box-sizing 行为。如果它有一个设置了 legacy 类的祖先,就会是 content-box。否则最终从 <html> 继承,就是 border-box


删除默认外边距

* {
  margin0;
}

浏览器会不同元素的外边距做一些常识性的假设。例如,默认情况下,h1 将比段落包含更多的边距。

这些假设在文字处理文档(word-processing document)的上下文中是合理的,但对于现代 Web 应用程序可能就不准确了。

margin 是一个棘手的魔鬼[4],我发现元素没有任何默认外边距反而更好处理,所以我决定移除所有元素外边距设置。🔥

如果/当我确实想为特定的标签添加一些边距时,我可以在我的项目样式中做。通配符选择器( * )的权重非常低,因此也很容易覆盖。

调整行高

body {
  line-height1.5;
}

line-height 用来控制段落中每行文本之间的垂直间距。浏览器针对这个属性的默认值会略有差异,但通常在 1.2 左右。

这个不包含单位的数字是基于字体大小的比率。它的功能与 em 单位相同。当 line-height1.2 时,每行行高将比元素字体大 20%

问题来了:对于那些有阅读障碍的人来说,这些行挤得太紧了,读起来很困难。WCAG 标准规定行高至少要 1.5[5]

不过这个数字确实会导致标题和其他大字体元素上会产生相当大的行:

Josh Comeau 的自定义 CSS 重置库

你可能希望在标题中覆盖 1.5。我的理解是,WCAG 标准中的 1.5 是指“主体”文本,而不是标题。


「✍️ 使用“calc”实现更智能的行高」

我一直在试验一种管理行高的替代方法。这就是:

* {
  line-heightcalc(1em + 0.5rem);
}

这是一个有点涉及高级知识点的代码片段,它超出了这篇博客文章的范围,但我可以做一个快速的解释。

这个替代方法不是通过将 font-size 乘以一个数字(如 1.5)来计算行高,而是使用 font-size 作为基数,并向每行「添加」一个固定的数值高度。

对于像段落这样的正文文本,布局、排版表现正常,每行都会解析为 24px(假设基于浏览器默认字体大小)。

但是,对于较大的文本(表如标题元素),这个声明会产生更为紧凑的行高效果。

Josh Comeau 的自定义 CSS 重置库

「不过谨慎使用」。我还在试验这种方式。

这样的一个缺点是,我必须使用 * 在所有元素上设置,而不是将其应用于 body 。这是因为 em 单位不能很好地继承,它不会为每个元素重新计算 1em 的值。

有关此技术的更多信息,请查看 Jesús Ricarte 的这篇精彩文章:使用 calc 计算最佳行高[6]


字体平滑

body {
  -webkit-font-smoothing: antialiased;
}

是的,这个设定有些争议。

在 MacOS 电脑上,浏览器默认使用“子像素抗锯齿(subpixel antialiasing)”。这是一种通过利用每个像素内的 R/G/B light 来使文本更容易阅读的技术。

在过去,这种技术被视为可访问性的胜利,因为它提高了文本对比度(text contract)。你可能读过一篇很受欢迎的博客文章,停止“修复”字体平滑[7],主张「不要」切换到“抗锯齿”。

问题来了:这篇文章写于 2012 年,在高 DPI“视网膜”显示器时代之前。今天的像素要小得多,肉眼看不见。

LED 像素的物理排列也发生了变化。如果你在显微镜下观察现代显示器,你将不再看到 R/G/B light 的有序网格。

在 2018 年发布的 MacOS Mojave 中,「苹果在整个操作系统中禁用了子像素抗锯齿」。我猜他们意识到这对现代硬件来说弊大于利。

令人困惑的是,Chrome 和 Safari 等 MacOS 浏览器默认情况下仍然使用子像素抗锯齿。我们需要通过将 -webkit-font-smoothing 设置为 antialiased 来显式地关闭它。

Josh Comeau 的自定义 CSS 重置库

MacOS 是唯一使用子像素抗锯齿的操作系统,因此此规则对 Windows、Linux 或移动的设备没有影响。

合理的默认媒体设置

imgpicturevideocanvassvg {
  display: block;
  max-width100%;
}

有件奇怪的事是图片被认为是“内联”元素。表示默认是跟其他内联元素一起在段落中间使用,比如 <em><strong>

这与我大部分时间使用图片的方式并不一致。通常,我都是把图片作为跟段落、标题或侧边栏一样的布局元素使用。

但是,如果我们尝试在布局中使用内联元素,就会发生奇怪的事情。如果你曾经有过一个神秘的 4px 间隙,不是 marginpaddingborder,它可能就是浏览器用 line-height 添加的“内联魔法空间(inline magic space)”。

通过在所有图片上设置 display: block,我们回避了这类难搞的问题。

我还设置了 max-width: 100%,这样做是为了避免大图片溢出(如果它们被放置在一个不够宽的容器中)。

大多数块级元素会自动增长/收缩以适应它们的父元素,但像  这样的媒体元素比较特殊:它们被称为替换元素,并不遵循相同的规则。

如果一个图片的“原生”大小为 800×600,那么 <img> 元素的宽度也将是 800px,即使我们将其放入 500px 宽的父元素中。

因此 max-width: 100% 规则将避免图片超出其容器,对我来说是更明智的默认行为。

表单控件的字体继承

inputbuttontextareaselect {
  font: inherit;
}

还有一件奇怪的事。默认情况下,按钮和输入框这些表单控件并不会从父级继承排版样式(typographical styles)。相反,他们有自己「奇怪的」风格。

例如, <textarea> 会使用系统默认的等宽字体。文本输入框则使用系统默认的无衬线字体。两者都将选择一个偏小的(microscopically-small)字体大小(Chrome 中为 13.333px)。

你可以想象,在移动终端上阅读 13 像素的文本是非常困难的。当我们聚焦一个小字体的输入时,浏览器会自动放大,这样文本就更容易阅读。

但这不是一个很好的体验:

Josh Comeau 的自定义 CSS 重置库

如果我们想避免这种自动缩放行为,输入的字体大小至少要是 1rem/16px。下面是解决这个问题的一个方法:

inputbuttontextareaselect {
  font-size1rem;
}

这解决了自动缩放问题,但这是一个表面方案(band-aid)。让我们来解决根本原因:表单输入不应该有自己的排版风格!

inputbuttontextareaselect {
  font: inherit;
}

font 是一种很少使用的简写,它设置了一系列与字体相关的属性,如 font-sizefont-weightfont-family。通过将其设置为 inherit,我们指示这些元素与周围环境中的排版相匹配。

只要我们不选择一个令人讨厌的小字体(obnoxiously small font size)作为我们正文的尺寸,这就一次性解决了我们的所有问题。🎉

词内自动折行

ph1h2h3h4h5h6 {
  overflow-wrap: break-word;
}

在 CSS 中,如果没有足够的空间容纳一行中的所有字符,文本就会自动换行。

默认情况下,折行算法会寻找“软换行(soft wrap)”机会,也就是寻找那些可以分割的字符。在英语中,唯一的软换行的机会是空格和连字符,但这因语言而异。

如果一行如果没有找到软换行的机会,就会导致文本溢出:

Josh Comeau 的自定义 CSS 重置库

这可能会导致一些恼人的布局问题。本例种会导致添加一个水平滚动条。其他情况下,可能会导致文本与其他元素重叠,或滑到(slip)图片/视频的后面。

overflow-wrap: break-word;的设置允许我们调整换行算法,再找不到软换行机会时使用硬换行:

Josh Comeau 的自定义 CSS 重置库

这两种解决方案都不是完美的,但至少硬换行不会弄乱布局!

感谢 Sophie Alpert 提出了类似的规则[8]!她建议将其应用于所有元素,这可能是一个好主意,但是没亲自测试过。

当然,你也可以尝试添加 hyphens 属性:

p {
  overflow-wrap: break-word;
  hyphens: auto;
}

hyphens: auto 使用连字符(在支持它们的语言中)来表示硬换行。

如果你的文本列很窄,这设置还是挺好的,但它也可能会导致分散注意力。因此,我就没有把它放在我的重置选项里,但也值得尝试!

根堆叠上下文

#root#__next {
  isolation: isolate;
}

最后一个是可选的。通常只有当你在使用像 React 这样的 JS 框架时才需要。

正如我们在“见鬼,z-index??[9]“, isolation 属性允许我们创建一个新的堆栈上下文,而无需设置 z-index

这是有帮助的,因为它让我们在保证某些高优先级的元素(modals、dropdowns、tooltips)将始终显示在我们的应用程序中的其他元素之上。没有奇怪的堆栈上下文 BUG,也不用关心我设置的 z-index 是不是不足够大。

当然,你应该调整这里的选择器来匹配你正在使用的框架。这里只是指示我们去选项应用程序渲染时所在的顶级元素。例如,create-react-app 使用 <div id="root">,因此正确的选择器应该是 #root

最后的成品

下面是 CSS 重置了,移除了每条注释,方便大家复制使用:

/*
  Josh's Custom CSS Reset
  https://www.joshwcomeau.com/css/custom-css-reset/
*/

*, *::before, *::after {
  box-sizing: border-box;
}
* {
  margin0;
}
body {
  line-height1.5;
  -webkit-font-smoothing: antialiased;
}
imgpicturevideocanvassvg {
  display: block;
  max-width100%;
}
inputbuttontextareaselect {
  font: inherit;
}
ph1h2h3h4h5h6 {
  overflow-wrap: break-word;
}
#root#__next {
  isolation: isolate;
}

你可以随意复制/粘贴到你自己的项目!它的发布没有任何限制,进入公共领域(当然,如果你想保留这篇博客文章的链接,我会很感激!)。

我选择不发布这个 CSS 重置作为一个 NPM 包的原因,是因为我觉得你应该拥有你的重置。把它带到你的项目中,随着时间的推移,当你学习新东西或发现新技巧时,再调整它。如果你愿意的话,你可以制作自己的 NPM 包来促进跨项目的重用。请记住:「你拥有这段代码,它应该和你一起成长」

感谢 Andy Bell 分享他的 Modern CSS Reset[10]。它帮助调整了我的一些想法,并激发我写了这篇文章!

更新日志

  • 2023 年 6 月——我从 htmlbody 中删除了 height: 100%。添加此规则是为了可以在应用程序中使用基于百分比的高度。现在动态视口单位[11]得到了很好的支持,这个 hacky 修复也不再需要了。

References

[1]

My Custom CSS Reset: https://www.joshwcomeau.com/css/custom-css-reset/

[2]

CSS Reset 设置: https://meyerweb.com/eric/tools/css/reset

[3]

对性能并没有多少影响: https://www.paulirish.com/2012/box-sizing-border-box-ftw/

[4]

棘手的魔鬼: https://www.joshwcomeau.com/css/rules-of-margin-collapse/

[5]

行高至少要 1.5: https://www.w3.org/WAI/WCAG21/Understanding/text-spacing.html

[6]

使用 calc 计算最佳行高: https://kittygiraudel.com/2020/05/18/using-calc-to-figure-out-optimal-line-height/

[7]

停止“修复”字体平滑: https://usabilitypost.com/2012/11/05/stop-fixing-font-smoothing/

[8]

提出了类似的规则: https://twitter.com/sophiebits/status/1462921205359386628

[9]

见鬼,z-index??: https://www.joshwcomeau.com/css/stacking-contexts/

[10]

Modern CSS Reset: https://piccalil.li/blog/a-modern-css-reset/

[11]

动态视口单位: https://web.dev/viewport-units/


原文始发于微信公众号(写代码的宝哥):Josh Comeau 的自定义 CSS 重置库

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/243798.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!