CSS 自定义属性(又称 CSS 变量)越来越受欢迎。 它们终于达到了 相当不错的浏览器支持,并逐渐进入各种生产环境。 自定义属性的流行并不令人意外,因为它们在许多用例中都非常有用,包括管理调色板、自定义组件和 主题。 但 CSS 变量在响应式设计中也大有帮助。
文章系列
- 定义变量和断点(此帖子)
- 构建灵活的网格系统
让我们考虑一个包含标题和段落的 <article>
元素
<article class="post">
<h2 class="heading">Post's heading</h2>
<p class="paragraph">
Lorem ipsum dolor sit amet, consectetur adipisicing elit.
Laudantium numquam adipisci recusandae officiis dolore tenetur,
nisi, beatae praesentium, soluta ullam suscipit quas?
</p>
</article>
在这种情况下,通常会根据视窗宽度更改某些大小和尺寸。 实现此目的的一种方法是使用媒体查询
.post {
padding: 0.5rem 1rem;
margin: 0.5rem auto 1rem;
}
.heading {
font-size: 2rem;
}
@media (min-width: 576px) {
.post {
padding: 1rem 2rem;
margin: 1rem auto 2rem;
}
.heading {
font-size: 3rem;
}
}
查看代码笔
#1 使用 CSS 自定义属性构建响应式功能 by Mikołaj (@mikolajdobrucki)
on CodePen.
这种方法为我们提供了一种简单的方法来控制不同屏幕尺寸下的 CSS 属性。 但是,随着项目复杂性的增加,它可能难以维护。 使用媒体查询时,同时保持代码可读和 DRY 往往具有挑战性。
扩展此模式时最常见的挑战包括
- 重复选择器: 除了用多个声明使代码膨胀外,它还会使将来的重构更加困难,例如,每次类名更改时,都需要记住在多个地方更新它。
- 重复属性: 请注意,在媒体查询中覆盖 CSS 规则时,需要重复整个声明(例如
font-size: 3rem;
),即使实际上只有值(3rem
)发生了变化。 - 重复媒体查询: 为了使响应式样式保持上下文,一种常见的做法是在多个地方包含相同的媒体查询,靠近它们覆盖的样式。 不幸的是,它不仅会使代码更重,还会使断点更难以维护。 另一方面,将所有响应式样式保存在一个地方,远离它们的原始声明,可能会非常混乱:我们最终会得到多个指向同一元素的引用,它们位于完全不同的位置。
我们可以说,只要启用了适当的文件压缩,重复的声明和查询就不应该是大问题,至少在我们提到性能的时候是这样。 我们还可以合并多个查询并使用后处理工具优化代码。 但是,如果完全避免这些问题不是更容易吗?
有很多方法可以避免上面列出的问题。 我们将在本文中探讨的一种方法是使用 CSS 自定义属性。
使用 CSS 变量作为属性值
网络上有大量关于 CSS 自定义属性概念的精彩文章。 如果您还没有机会熟悉它们,我建议您从关于此主题的初学者文章开始,例如 Serg Hospodarets 的这篇精彩文章,因为我们不会在本文中详细介绍基本用法。
在响应式设计中使用 CSS 自定义属性最常见的方法是使用变量来存储在媒体查询中更改的值。 为此,声明一个保存要更改的值的变量,然后在媒体查询中重新分配它
:root {
--responsive-padding: 1rem;
}
@media (min-width: 576px) {
:root {
--responsive-padding: 2rem;
}
}
.foo {
padding: var(--responsive-padding);
}
将变量分配给 :root
选择器并不总是好的做法。 与 JavaScript 一样,拥有许多全局变量被认为是一种不好的做法。 在现实生活中,请尝试在实际使用自定义属性的范围内声明它们。
这样,我们避免了 .foo
类的多个规则。 我们还将逻辑(更改值)与实际设计(CSS 声明)分离。 在上面的示例中采用这种方法,我们得到了以下 CSS
.post {
--post-vertical-padding: 0.5rem;
--post-horizontal-padding: 1rem;
--post-top-margin: 0.5rem;
--post-bottom-margin: 1rem;
--heading-font-size: 2rem;
}
@media (min-width: 576px) {
.post {
--post-vertical-padding: 1rem;
--post-horizontal-padding: 2rem;
--post-top-margin: 1rem;
--post-bottom-margin: 2rem;
--heading-font-size: 3rem;
}
}
.post {
padding: var(--post-vertical-padding) var(--post-horizontal-padding);
margin: var(--post-top-margin) auto var(--post-bottom-margin);
}
.heading {
font-size: var(--heading-font-size);
}
查看代码笔
#2 使用 CSS 自定义属性构建响应式功能 by Mikołaj (@mikolajdobrucki)
on CodePen.
请注意,在简写属性(例如 padding
、margin
或 font
)中使用变量会导致一些非常有趣的连锁反应。 由于自定义属性可以保存几乎所有值(稍后将详细介绍),即使是空字符串,也不清楚简写属性的值将在以后如何分解成级联中使用的完整属性。 例如,上面 margin
属性中使用的 auto
可能变成上边距和下边距、左边距和右边距、上边距、右边距、下边距或左边距——这完全取决于周围自定义属性的值。
该代码是否比上一个示例中的代码更简洁是值得商榷的,但在更大规模上,它无疑更易于维护。 现在让我们尝试稍微简化一下这段代码。
请注意,此处重复了一些值。 如果我们尝试合并重复的变量怎么办? 让我们考虑以下修改
:root {
--small-spacing: 0.5rem;
--large-spacing: 1rem;
--large-font-size: 2rem;
}
@media (min-width: 576px) {
:root {
--small-spacing: 1rem;
--large-spacing: 2rem;
--large-font-size: 3rem;
}
}
.post {
padding: var(--small-spacing) var(--large-spacing);
margin: var(--small-spacing) auto var(--large-spacing);
}
.heading {
font-size: var(--large-font-size);
}
查看代码笔
#3 使用 CSS 自定义属性构建响应式功能 by Mikołaj (@mikolajdobrucki)
on CodePen.
它看起来更简洁,但实际上更好吗? 不一定。 为了灵活性和可读性,这可能不是每种情况下的正确解决方案。 我们绝对不应该仅仅因为一些变量碰巧具有相同的值而合并它们。 有时,只要我们是作为经过深思熟虑的系统的一部分来做这件事,它可能有助于我们简化事物并保持整个项目的 consistency。 但是,在其他情况下,这种方式可能会很快被证明是令人困惑和有问题的。 现在,让我们看看另一种方法来处理这段代码。
使用 CSS 变量作为乘数
CSS 自定义属性是现代网络的一项相当新的功能。 calc()
函数是近年来推出的另一项出色功能。 它使我们能够在实时 CSS 中执行真正的数学运算。 就浏览器支持而言,它在所有支持 CSS 自定义属性的浏览器中都受到支持。
calc()
与 CSS 变量配合得很好,使其更加强大。 这意味着我们可以在自定义属性内使用 calc()
,也可以在 calc()
内使用自定义属性!
例如,以下 CSS 是完全有效的
:root {
--size: 2;
}
.foo {
--padding: calc(var(--size) * 1rem); /* 2 × 1rem = 2rem */
padding: calc(var(--padding) * 2); /* 2rem × 2 = 4rem */
}
这对我们和我们的响应式设计有什么意义? 这意味着我们可以在媒体查询中使用 calc()
函数来修改 CSS 自定义属性。 假设我们有一个填充,它在移动设备上应该为 5px
,在桌面设备上应该为 10px
。 我们不必两次声明此属性,而是可以将一个变量分配给它,并在较大的屏幕上将其乘以二
:root {
--padding: 1rem;
--foo-padding: var(--padding);
}
@media (min-width: 576px) {
:root {
--foo-padding: calc(var(--padding) * 2);
}
}
.foo {
padding: var(--foo-padding);
}
看起来不错,但是所有值(--padding
、calc(--padding * 2)
)都远离它们的声明(padding
)。 该语法也可能很混乱,因为有两个不同的填充变量(--padding
和 --foo-padding
),以及它们之间的关系不清楚。
为了使事情更清晰一些,让我们尝试以相反的方式编写代码
:root {
--multiplier: 1;
}
@media (min-width: 576px) {
:root {
--multiplier: 2;
}
}
.foo {
padding: calc(1rem * var(--multiplier));
}
这样,我们以更简洁的代码实现了相同的计算输出! 因此,我们不是使用一个变量来保存属性的初始值(1rem
),而是使用一个变量来保存乘数(在小屏幕上为 1
,在较大屏幕上为 2
)。 它还使我们能够在其他声明中使用 --multiplier
变量。 让我们将此技术应用于前面代码段中的填充和边距
:root {
--multiplier: 1;
}
@media (min-width: 576px) {
:root {
--multiplier: 2;
}
}
.post {
padding: calc(.5rem * var(--multiplier))
calc(1rem * var(--multiplier));
margin: calc(.5rem * var(--multiplier))
auto
calc(1rem * var(--multiplier));
}
现在,让我们尝试用排版实现相同的方法。 首先,我们将向设计中添加另一个标题
<h1 class="heading-large">My Blog</h1>
<article class="post">
<h2 class="heading-medium">Post's heading</h2>
<p class="paragraph">
Lorem ipsum dolor sit amet, consectetur adipisicing elit.
Laudantium numquam adipisci recusandae officiis dolore tenetur,
nisi, beatae praesentium, soluta ullam suscipit quas?
</p>
</article>
有了多个文本样式,我们可以使用一个变量来控制它们的大小
:root {
--headings-multiplier: 1;
}
@media (min-width: 576px) {
:root {
--headings-multiplier: 3 / 2;
}
}
.heading-medium {
font-size: calc(2rem * var(--headings-multiplier))
}
.heading-large {
font-size: calc(3rem * var(--headings-multiplier))
}
您可能已经注意到,3 / 2
根本不是有效的 CSS 值。 那为什么它不会导致错误呢? 原因是 CSS 变量的语法非常宽松,这意味着几乎任何东西都可以分配给变量,即使它不是任何现有 CSS 属性的有效 CSS 值。 声明的 CSS 自定义属性几乎完全不进行评估,直到它们在特定声明中被用户代理计算。 因此,一旦变量在某个属性的值中使用,该值将在计算值时变为有效或无效。
哦,关于最后一个注释的另一个注释:如果您想知道,我使用 3 / 2
的值只是为了说明一个观点。 在现实生活中,使用 1.5
更合理,这样可以使代码更易读。
现在,让我们看看结合了上面讨论的所有内容的完整实时示例
查看代码笔
#4 使用 CSS 自定义属性构建响应式功能 by Mikołaj (@mikolajdobrucki)
on CodePen.
再说一次,我绝不会主张将 calc()
与自定义属性结合起来以使代码更简洁作为一般规则。 但是,我可以想象出它有助于使代码更井井有条和更易于维护的场景。 当明智地使用这种方法时,它还可以显着减少 CSS 的权重。
就可读性而言,一旦理解了底层规则,我们就可以认为它更易读。一次 它有助于解释值之间的逻辑和关系。另一方面,有些人可能认为它不太易读,因为在进行数学运算之前,很难立即读出属性中包含的值。此外,一次使用太多变量和 `calc()` 函数可能会不必要地使代码变得模糊,并使其更难理解,尤其是对于不关注 CSS 的初级和前端开发人员而言。
结论
总之,在响应式设计中使用 CSS 自定义属性有很多方法,绝不仅限于上面显示的示例。CSS 变量可以简单地用于将值与设计分离。它们还可以更进一步,与一些数学运算结合使用。所提出的方法中没有一种比另一种更好或更差。使用它们的合理性取决于具体情况和上下文。
现在您已经了解了如何在响应式设计中使用 CSS 自定义属性,我希望您能找到一种方法将它们引入到您自己的工作流程中。接下来,我们将探讨在可重用组件和模块中使用它们的方法,因此请 查看一下。
非常有用,谢谢。这是我之前知道但没有这样考虑的事情之一;)
如果您想更高级地使用它,可以在计算中包含视窗值(vw 和 vh)以创建无需媒体查询的响应式布局。但是,您必须小心使用它,因为像填充和字体大小这样的东西没有最小值或最大值。有一些 SASS 混入可以根据视窗宽度以及最小/最大媒体查询生成响应式缩放属性,但这可能会变得有点混乱。最好的方法是谨慎使用这些值,并在极大尺寸和极小尺寸下测试您的布局。
使之可维护的诀窍是使用非常明确且有意义的 CSS 变量名称。因此,与其使用 `--big-font`,不如使用 `--header-h1-font-size` 或 `--article-h2-font-size`。
扩展此原则,如果您正在使用包含大量数字的 CSS 声明(例如,用于边距或 CSS 网格宽度),那么您可以将所有这些逻辑放在文档顶部的 CSS 变量声明中,这些声明引用其他 CSS 变量。
因此,与其像这里为 `.post` 类指定的填充方式,不如使用 `padding: var(–post-padding);`,然后在顶部定义 `–post-padding`,其中详细信息仅在根元素中指定,而乘数则在几行之外。
获得正确的命名约定是使事情可维护的最佳方式。许多复杂性转移到 CSS 变量,通过严格遵守命名约定,您可以对 CSS 变量进行排序,并使一切易于理解。
此外,像“乘数”这样的模糊术语也不是很有用,“`–page-margin`”正如其字面意思所描述的那样,而不是一个神奇的数字。
如果您正在使用一些 CSS 编译器,那么将 CSS 变量保存在单独的文件中会有所帮助,在较小的项目中,您可以只使用一个文件,并拆分代码编辑器中的视图,以便在浏览代码时可以轻松地参考 CSS 变量。如果所有内容都在页面顶部的 CSS 变量块中,那么使用 calc 就可以了,这样您的 CSS 就可以使用简单的属性声明,并抽象出逻辑。
如果您正在使用 CSS 变量和 CSS 网格,那么您不妨从像素、百分比和 rem 转向 em 和查看器单位。2019 年没有像素的空间,当屏幕具有虚拟像素时,它们是一种糟糕的思维方式。因此,与其使用 576 像素,不如使用 36 em。
级联也是如此,因此与其在文档中充斥着无数的类元素,不如使用级联和 em 的神奇之处,通过在文章上设置属性来使段落文本的字体大小与标题相关联。
关于此主题的信息非常有用。我喜欢最后的示例是如何完成的,但我也在我的工作中看到一个问题,当我遇到项目经理要求我告诉我某件事的大小并“提高”一个像素点时。在一个动态环境中跟踪一个特定值总是很困难……我猜这是一个权衡取舍的情况。
如果只有一种方法可以在媒体查询声明中使用自定义变量(例如 `@media screen and (min-width: var(--breakpoint)) { ... }`)据我所知,这是不可能的——您对此有什么想法吗?
不幸的是,这是正确的(据我所知)。您不能在 CSS 的 `@规则`(例如 `@media`)中使用自定义属性。我从来没有需要过这样的东西,但我可以想象在某些情况下它会很有用。
将此视为一种渐进增强,您可以始终使用 JavaScript 来实现它。