响应式设计和 CSS 自定义属性:定义变量和断点

Avatar of Mikolaj Dobrucki
Mikolaj Dobrucki on

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 200 美元的免费信用额度!

CSS 自定义属性(又称 CSS 变量)越来越受欢迎。 它们终于达到了 相当不错的浏览器支持,并逐渐进入各种生产环境。 自定义属性的流行并不令人意外,因为它们在许多用例中都非常有用,包括管理调色板、自定义组件和 主题。 但 CSS 变量在响应式设计中也大有帮助。

文章系列

  1. 定义变量和断点(此帖子)
  2. 构建灵活的网格系统

让我们考虑一个包含标题和段落的 <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.

请注意,在简写属性(例如 paddingmarginfont)中使用变量会导致一些非常有趣的连锁反应。 由于自定义属性可以保存几乎所有值(稍后将详细介绍),即使是空字符串,也不清楚简写属性的值将在以后如何分解成级联中使用的完整属性。 例如,上面 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);
}

看起来不错,但是所有值(--paddingcalc(--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 自定义属性,我希望您能找到一种方法将它们引入到您自己的工作流程中。接下来,我们将探讨在可重用组件和模块中使用它们的方法,因此请 查看一下