CSS 自定义属性的策略指南
已发表: 2022-03-10所有现代浏览器现在都支持 CSS 自定义属性(有时称为“CSS 变量”),人们开始在生产中使用它们。 这很好,但它们与预处理器中的变量不同,而且我已经看到很多人使用它们的例子而不考虑它们提供的优势。
自定义属性具有巨大的潜力,可以改变我们编写和构建 CSS 的方式,并在较小程度上改变我们使用 JavaScript 与 UI 组件交互的方式。 我不会关注语法及其工作方式(为此我建议您阅读“是时候开始使用自定义属性了”)。 相反,我想更深入地了解充分利用 CSS 自定义属性的策略。
它们与预处理器中的变量有何相似之处?
自定义属性有点像预处理器中的变量,但有一些重要的区别。 第一个也是最明显的区别是语法。
在SCSS
中,我们使用美元符号来表示变量:
$smashing-red: #d33a2c;
在 Less 中,我们使用@
符号:
@smashing-red: #d33a2c;
自定义属性遵循类似的约定并使用--
前缀:
:root { --smashing-red: #d33a2c; } .smashing-text { color: var(--smashing-red); }
自定义属性和预处理器中的变量之间的一个重要区别是自定义属性具有不同的语法来分配值和检索该值。 在检索自定义属性的值时,我们使用var()
函数。
下一个最明显的区别是名称。 它们被称为“自定义属性”,因为它们确实是 CSS 属性。 在预处理器中,您几乎可以在任何地方声明和使用变量,包括外部声明块、媒体规则中,甚至作为选择器的一部分。
$breakpoint: 800px; $smashing-red: #d33a2c; $smashing-things: ".smashing-text, .cats"; @media screen and (min-width: $breakpoint) { #{$smashing-things} { color: $smashing-red; } }
使用自定义属性时,上面的大多数示例都是无效的。
自定义属性对于它们可以用作普通 CSS 属性的位置具有相同的规则。 将它们视为动态属性要比变量好得多。 这意味着它们只能在声明块内使用,或者换句话说,自定义属性与选择器相关联。 这可以是:root
选择器,也可以是任何其他有效的选择器。
:root { --smashing-red: #d33a2c; } @media screen and (min-width: 800px) { .smashing-text, .cats { --margin-left: 1em; } }
您可以在任何您会在属性声明中使用值的地方检索自定义属性的值。 这意味着它们可以用作单个值,作为速记语句的一部分,甚至可以在calc()
方程中使用。
.smashing-text, .cats { color: var(--smashing-red); margin: 0 var(--margin-horizontal); padding: calc(var(--margin-horizontal) / 2) }
但是,它们不能用于媒体查询或包括:nth-child()
在内的选择器。
关于语法和自定义属性如何工作,您可能还想了解更多信息,例如如何使用回退值以及是否可以将变量分配给其他变量(是的),但是这个基本介绍应该足以理解其余部分本文中的概念。 有关自定义属性如何工作的详细信息,您可以阅读 Serg Hospodarets 撰写的“是时候开始使用自定义属性”了。
动态与静态
抛开外观差异不谈,预处理器中的变量和自定义属性之间最显着的区别在于它们的作用域。 我们可以将变量称为静态或动态范围。 预处理器中的变量是静态的,而自定义属性是动态的。
就 CSS 而言,静态意味着您可以在编译过程的不同点更新变量的值,但这不能更改之前代码的值。
$background: blue; .blue { background: $background; } $background: red; .red { background: $background; }
结果是:
.blue { background: blue; } .red { background: red; }
一旦将其呈现给 CSS,变量就消失了。 这意味着我们可以潜在地读取.scss
文件并确定其输出,而无需了解任何有关 HTML、浏览器或其他输入的信息。 自定义属性不是这种情况。
预处理器确实有一种“块作用域”,其中变量可以在选择器、函数或混合中临时更改。 这会更改块内变量的值,但它仍然是静态的。 这与块相关,而不是选择器。 在下面的示例中,变量$background
在.example
块内发生了更改。 即使我们使用相同的选择器,它也会变回块外的初始值。
$background: red; .example { $background: blue; background: $background; } .example { background: $background; }
这将导致:
.example { background: blue; } .example { background: red; }
自定义属性的工作方式不同。 就自定义属性而言,动态范围意味着它们受继承和级联的影响。 该属性与选择器相关联,如果值更改,则会影响所有匹配的 DOM 元素,就像任何其他 CSS 属性一样。
这很棒,因为您可以在媒体查询中更改自定义属性的值,使用诸如悬停之类的伪选择器,甚至使用 JavaScript。
a { --link-color: black; } a:hover, a:focus { --link-color: tomato; } @media screen and (min-width: 600px) { a { --link-color: blue; } } a { color: var(--link-color); }
我们不必更改自定义属性的使用位置——我们使用 CSS 更改自定义属性的值。 这意味着使用相同的自定义属性,我们可以在同一页面的不同位置或上下文中具有不同的值。
全球与本地
除了静态或动态之外,变量也可以是全局的或局部的。 如果您编写 JavaScript,您将对此很熟悉。 变量既可以应用于应用程序中的所有内容,也可以将其范围限制为特定的函数或代码块。
CSS 类似。 我们有一些在全球范围内应用的东西和一些更本地化的东西。 品牌颜色、垂直间距和排版都是您可能希望在您的网站或应用程序中全局一致地应用的示例。 我们也有当地的东西。 例如,一个按钮组件可能有一个小变体和一个大变体。 您不希望将这些按钮的大小应用于所有输入元素甚至页面上的每个元素。
这是我们在 CSS 中熟悉的东西。 我们开发了设计系统、命名约定和 JavaScript 库,所有这些都有助于隔离本地组件和全局设计元素。 自定义属性为处理这个老问题提供了新的选择。
默认情况下,CSS 自定义属性的范围仅限于我们应用它们的特定选择器。 所以它们有点像局部变量。 但是,自定义属性也是继承的,因此在许多情况下它们的行为类似于全局变量——尤其是在应用于:root
选择器时。 这意味着我们需要考虑如何使用它们。
如此多的示例显示将自定义属性应用于:root
元素,尽管这对于演示来说很好,但它可能导致混乱的全局范围和意外的继承问题。 幸运的是,我们已经吸取了这些教训。
全局变量往往是静态的
有一些小的例外,但一般来说,CSS 中的大多数全局事物也是静态的。
品牌颜色、排版和间距等全局变量往往不会从一个组件到另一个组件发生太大变化。 当他们确实发生变化时,这往往是全球品牌重塑或其他一些在成熟产品上很少发生的重大变化。 将这些东西作为变量仍然是有意义的,它们在许多地方使用,并且变量有助于保持一致性。 但它们是动态的是没有意义的。 这些变量的值不会以任何动态方式改变。
出于这个原因,我强烈建议对全局(静态)变量使用预处理器。 这不仅确保它们始终是静态的,而且在代码中直观地表示它们。 这可以使 CSS 更具可读性和更易于维护。
局部静态变量没问题(有时)
您可能会认为,鉴于全局变量是静态的,通过反射,所有局部变量可能都需要是动态的。 虽然局部变量确实往往是动态的,但这远没有全局变量是静态的趋势那么强烈。
在许多情况下,局部静态变量是完全可以的。 我在组件文件中使用预处理器变量主要是为了方便开发人员。
考虑具有多种尺寸变化的按钮组件的经典示例。
我的scss
可能看起来像这样:
$button-sml: 1em; $button-med: 1.5em; $button-lrg: 2em; .btn { // Visual styles } .btn-sml { font-size: $button-sml; } .btn-med { font-size: $button-med; } .btn-lrg { font-size: $button-lrg; }
显然,如果我多次使用变量或从大小变量派生边距和填充值,这个例子会更有意义。 然而,快速制作不同尺寸原型的能力可能是一个充分的理由。
因为大多数静态变量都是全局变量,所以我喜欢区分仅在组件内部使用的静态变量。 为此,您可以为这些变量添加组件名称的前缀,或者您可以使用其他前缀,例如组件的c-variable-name
或本地l-variable-name
。 您可以使用任何您想要的前缀,也可以为全局变量添加前缀。 无论您选择什么,区分都很有帮助,尤其是在将现有代码库转换为使用自定义属性时。
何时使用自定义属性
如果可以在组件内部使用静态变量,我们什么时候应该使用自定义属性? 将现有的预处理器变量转换为自定义属性通常没有什么意义。 毕竟,自定义属性的原因是完全不同的。 当我们的 CSS 属性相对于 DOM 中的条件发生变化时,自定义属性才有意义——尤其是动态条件,例如:focus
、 :hover
、媒体查询或 JavaScript。
我怀疑我们将始终使用某种形式的静态变量,尽管将来我们可能需要更少,因为自定义属性提供了组织逻辑和代码的新方法。 在那之前,我认为在大多数情况下,我们将使用预处理器变量和自定义属性的组合。
知道我们可以将静态变量分配给自定义属性会很有帮助。 无论它们是全局的还是本地的,在许多情况下将静态变量转换为本地动态自定义属性都是有意义的。
注意:您知道$var
是自定义属性的有效值吗? 最新版本的 Sass 认识到这一点,因此我们需要插入分配给自定义属性的变量,例如: #{$var}
。 这告诉 Sass 你想要输出变量的值,而不仅仅是样式表中的$var
。 这仅在自定义属性等情况下才需要,其中变量名也可以是有效的 CSS。
如果我们采用上面的按钮示例并决定所有按钮都应该使用移动设备上的小变化,而不管 HTML 中应用的类是什么,现在这是一个更加动态的情况。 为此,我们应该使用自定义属性。
$button-sml: 1em; $button-med: 1.5em; $button-lrg: 2em; .btn { --button-size: #{$button-sml}; } @media screen and (min-width: 600px) { .btn-med { --button-size: #{$button-med}; } .btn-lrg { --button-size: #{$button-lrg}; } } .btn { font-size: var(--button-size); }
在这里,我创建了一个自定义属性: --button-size
。 此自定义属性最初的作用域是使用btn
类的所有按钮元素。 然后,我将btn-med
和btn-lrg
类的--button-size
值更改为 600px 以上。 最后,我将这个自定义属性应用到一个地方的所有按钮元素。
不要太聪明
自定义属性的动态特性允许我们创建一些巧妙而复杂的组件。
随着预处理器的引入,我们中的许多人使用 mixin 和自定义函数创建了具有巧妙抽象的库。 在有限的情况下,像这样的例子在今天仍然有用,但在大多数情况下,我使用预处理器的时间越长,我使用的功能就越少。 今天,我几乎只对静态变量使用预处理器。
自定义属性不会(也不应该)不受此类实验的影响,我期待看到许多聪明的例子。 但从长远来看,可读和可维护的代码总是会战胜聪明的抽象(至少在生产中)。
我最近在 Free Code Camp Medium 上阅读了一篇关于此主题的优秀文章。 它由 Bill Sourour 编写,名为“Don't Do It At Runtime。 在设计时做。” 我不会解释他的论点,而是让你阅读它。
预处理器变量和自定义属性之间的一个关键区别是自定义属性在运行时起作用。 这意味着就复杂性而言,使用预处理器可能已经可以接受的边界对于自定义属性可能不是一个好主意。
最近为我说明了这一点的一个例子是:
:root { --font-scale: 1.2; --font-size-1: calc(var(--font-scale) * var(--font-size-2)); --font-size-2: calc(var(--font-scale) * var(--font-size-3)); --font-size-3: calc(var(--font-scale) * var(--font-size-4)); --font-size-4: 1rem; }
这会产生一个模块化的规模。 模块化比例是一系列使用比率相互关联的数字。 它们通常用于网页设计和开发来设置字体大小或间距。
在此示例中,每个自定义属性都是使用calc()
确定的,方法是获取前一个自定义属性的值并将其乘以比率。 这样做,我们可以得到规模中的下一个数字。
这意味着比率是在运行时计算的,您可以通过仅更新--font-scale
属性的值来更改它们。 例如:
@media screen and (min-width: 800px) { :root { --font-scale: 1.33; } }
如果您想更改比例,这比再次计算所有值要聪明、简洁且快得多。 这也是我在生产代码中不会做的事情。
尽管上面的示例对原型设计很有用,但在生产中,我更希望看到这样的内容:
:root { --font-size-1: 1.728rem; --font-size-2: 1.44rem; --font-size-3: 1.2em; --font-size-4: 1em; } @media screen and (min-width: 800px) { :root { --font-size-1: 2.369rem; --font-size-2: 1.777rem; --font-size-3: 1.333rem; --font-size-4: 1rem; } }
与比尔文章中的示例类似,我发现查看实际值是有帮助的。 我们阅读代码的次数比编写代码的次数要多得多,并且字体比例等全局值在生产中很少更改。
上面的例子仍然不完美。 它违反了之前的规则,即全局值应该是静态的。 我更喜欢使用预处理器变量并使用前面演示的技术将它们转换为本地动态自定义属性。
避免我们从使用一个自定义属性到另一个自定义属性的情况也很重要。 当我们这样命名属性时,就会发生这种情况。
改变值而不是变量
更改值而不是变量是有效使用自定义属性的最重要策略之一。
作为一般规则,您永远不应更改用于任何单一目的的自定义属性。 这很容易做到,因为这正是我们使用预处理器做事的方式,但是对于自定义属性来说意义不大。
在此示例中,我们有两个自定义属性用于示例组件。 我根据屏幕大小从使用--font-size-small
的值切换到--font-size-large
的值。
:root { --font-size-small: 1.2em; --font-size-large: 2em; } .example { font-size: var(--font-size-small); } @media screen and (min-width: 800px) { .example { font-size: var(--font-size-large); } }
一个更好的方法是定义一个范围为组件的自定义属性。 然后使用媒体查询或任何其他选择器更改其值。
.example { --example-font-size: 1.2em; } @media screen and (min-width: 800px) { .example { --example-font-size: 2em; } }
最后,在一个地方,我使用了这个自定义属性的值:
.example { font-size: var(--example-font-size); }
在此示例和之前的其他示例中,媒体查询仅用于更改自定义属性的值。 您可能还注意到只有一处使用了var()
语句,并且更新了常规 CSS 属性。
变量声明和属性声明之间的这种分离是有意的。 这有很多原因,但在考虑响应式设计时,好处是最明显的。
具有自定义属性的响应式设计
当响应式设计严重依赖媒体查询时,其中一个困难是,无论您如何组织 CSS,与特定组件相关的样式在样式表中都会变得支离破碎。
很难知道哪些 CSS 属性会发生变化。 尽管如此,CSS 自定义属性可以帮助我们组织一些与响应式设计相关的逻辑,并使处理媒体查询变得更加容易。
如果它改变,它是一个变量
使用媒体查询更改的属性本质上是动态的,自定义属性提供了在 CSS 中表达动态值的方法。 这意味着如果您使用媒体查询来更改任何 CSS 属性,您应该将此值放在自定义属性中。
然后,您可以将其连同所有媒体规则、悬停状态或定义值如何变化的任何动态选择器一起移动到文档顶部。
将逻辑与设计分离
如果做得正确,逻辑和设计的分离意味着媒体查询仅用于更改自定义属性的值。 这意味着与响应式设计相关的所有逻辑都应该在文档的顶部,并且无论我们在 CSS 中看到var()
语句的什么地方,我们都会立即知道这个属性发生了变化。 使用传统的 CSS 编写方法,无法一眼就知道这一点。
我们中的许多人都非常擅长一目了然地阅读和解释 CSS,同时在脑海中跟踪哪些属性在不同情况下发生了变化。 我已经厌倦了,我不想再这样做了! 自定义属性现在提供了逻辑与其实现之间的链接,因此我们不需要跟踪它,这非常有用!
逻辑折叠
在文档或函数的顶部声明变量的想法并不是一个新想法。 这是我们在大多数语言中所做的事情,现在我们也可以在 CSS 中做到这一点。 以这种方式编写 CSS 会在文档顶部和底部的 CSS 之间创建清晰的视觉区别。 当我谈论它们时,我需要一种方法来区分这些部分,“逻辑折叠”的概念是我开始使用的一个隐喻。
首屏包含所有预处理器变量和自定义属性。 这包括自定义属性可以具有的所有不同值。 应该很容易跟踪自定义属性的变化。
首屏下的 CSS 简单明了,具有高度的声明性且易于阅读。 感觉就像在媒体查询和现代 CSS 的其他必要复杂性之前的 CSS。
看一个非常简单的六列 flexbox 网格系统示例:
.row { --row-display: block; } @media screen and (min-width: 600px) { .row { --row-display: flex; } }
--row-display
自定义属性最初设置为block
。 超过 600 像素的显示模式设置为 flex。
首屏下方可能如下所示:
.row { display: var(--row-display); flex-direction: row; flex-wrap: nowrap; } .col-1, .col-2, .col-3, .col-4, .col-5, .col-6 { flex-grow: 0; flex-shrink: 0; } .col-1 { flex-basis: 16.66%; } .col-2 { flex-basis: 33.33%; } .col-3 { flex-basis: 50%; } .col-4 { flex-basis: 66.66%; } .col-5 { flex-basis: 83.33%; } .col-6 { flex-basis: 100%; }
我们立即知道--row-display
是一个变化的值。 最初,它将是block
,因此将忽略 flex 值。
这个例子相当简单,但如果我们将它扩展为包含一个灵活宽度的列来填充剩余空间,则可能需要将flex-grow
、 flex-shrink
和flex-basis
值转换为自定义属性。 你可以试试这个或者看看这里更详细的例子。
主题的自定义属性
我主要反对对全局动态变量使用自定义属性,并希望暗示将自定义属性附加到:root
选择器在许多情况下被认为是有害的。 但是每条规则都有一个例外,对于自定义属性,它是主题。
有限地使用全局自定义属性可以使主题化变得更加容易。
主题化通常是指让用户以某种方式自定义 UI。 这可能类似于更改个人资料页面上的颜色。 或者它可能是更本地化的东西。 例如,您可以在 Google Keep 应用程序中选择便笺的颜色。
主题化通常涉及编译单独的样式表以覆盖用户偏好的默认值,或者为每个用户编译不同的样式表。 这两者都可能很困难,并且会对性能产生影响。
使用自定义属性,我们不需要编译不同的样式表; 我们只需要根据用户的喜好更新属性的值。 由于它们是继承值,如果我们在根元素上执行此操作,它们可以在应用程序的任何地方使用。
资本化全局动态属性
自定义属性区分大小写,并且由于大多数自定义属性都是本地的,因此如果您使用全局动态属性,则将它们大写是有意义的。
:root { --THEME-COLOR: var(--user-theme-color, #d33a2c); }
变量的大写通常表示全局常量。 对我们来说,这意味着该属性是在应用程序的其他地方设置的,我们可能不应该在本地更改它。
避免直接设置全局动态属性
自定义属性接受后备值。 避免直接覆盖全局自定义属性的值并将用户值分开可能很有用。 我们可以使用后备值来做到这一点。
上面的示例将--THEME-COLOR
的值设置为--user-theme-color
的值(如果存在)。 如果--user-theme-color
未设置,将使用#d33a2c
的值。 这样,我们不需要在每次使用--THEME-COLOR
时都提供回退。
您可能期望在下面的示例中背景将设置为green
。 但是,-- --user-theme-color
的值没有在根元素上设置,所以--THEME-COLOR
的值没有改变。
:root { --THEME-COLOR: var(--user-theme-color, #d33a2c); } body { --user-theme-color: green; background: var(--THEME-COLOR); }
像这样间接设置全局动态属性可以保护它们不被本地覆盖,并确保始终从根元素继承用户设置。 这是一个有用的约定,可以保护您的主题值并避免意外继承。
如果我们确实想将特定属性公开给继承,我们可以将:root
选择器替换为*
选择器:
* { --THEME-COLOR: var(--user-theme-color, #d33a2c); } body { --user-theme-color: green; background: var(--THEME-COLOR); }
现在为每个元素重新计算--THEME-COLOR
的值,因此可以使用--user-theme-color
的本地值。 换句话说,此示例中的背景颜色将为green
。
您可以在使用自定义属性操作颜色部分中查看此模式的一些更详细的示例。
使用 JavaScript 更新自定义属性
如果你想使用 JavaScript 设置自定义属性,有一个相当简单的 API,它看起来像这样:
const elm = document.documentElement; elm.style.setProperty('--USER-THEME-COLOR', 'tomato');
在这里,我在文档元素上设置--USER-THEME-COLOR
的值,或者换句话说,将由所有元素继承的:root
元素。
这不是一个新的 API; 它与更新元素样式的 JavaScript 方法相同。 这些是内联样式,因此它们将比常规 CSS 具有更高的特异性。
这意味着很容易应用本地自定义:
.note { --note-color: #eaeaea; } .note { background: var(--note-color); }
在这里,我为--note-color
设置了一个默认值,并将其范围限定为.note
组件。 即使在这个简单的示例中,我也将变量声明与属性声明分开。
const elm = document.querySelector('#note-uid'); elm.style.setProperty('--note-color', 'yellow');
然后,我以.note
元素的特定实例为目标,并仅为该元素更改--note-color
自定义属性的值。 这现在将具有比默认值更高的特异性。
您可以使用 React 在此示例中看到它是如何工作的。 这些用户偏好可以保存在本地存储中,或者在更大的应用程序的情况下,可以保存在数据库中。
使用自定义属性操作颜色
除了十六进制值和命名颜色之外,CSS 还具有颜色函数,例如rgb()
和hsl()
。 这些允许我们指定颜色的各个组成部分,例如色调或亮度。 自定义属性可以与颜色函数结合使用。
:root { --hue: 25; } body { background: hsl(var(--hue), 80%, 50%); }
这很有用,但是预处理器的一些最广泛使用的特性是高级颜色函数,它允许我们使用诸如变亮、变暗或去饱和等函数来操纵颜色:
darken($base-color, 10%); lighten($base-color, 10%); desaturate($base-color, 20%);
在浏览器中拥有其中一些功能会很有用。 它们即将到来,但在我们在 CSS 中拥有原生颜色修改功能之前,自定义属性可以填补一些空白。
我们已经看到自定义属性可以在rgb()
和hsl()
等现有颜色函数中使用,但它们也可以在calc()
中使用。 这意味着我们可以通过乘以将实数转换为百分比,例如calc(50 * 1%)
= 50%
。
:root { --lightness: 50; } body { background: hsl(25, 80%, calc(var(--lightness) * 1%)); }
我们要将亮度值存储为实数的原因是我们可以在将其转换为百分比之前使用calc
对其进行操作。 例如,如果我想将颜色变暗20%
,我可以将其亮度乘以0.8
。 我们可以通过将亮度计算分离到一个局部范围的自定义属性中来使它更容易阅读:
:root { --lightness: 50; } body { --lightness: calc(var(--lightness * 0.8)); background: hsl(25, 80%, calc(var(--lightness) * 1%)); }
我们甚至可以抽象出更多的计算,并使用自定义属性在 CSS 中创建类似颜色修改函数的东西。 对于大多数实际的主题化案例来说,这个示例可能过于复杂,但它展示了动态自定义属性的全部功能。
简化主题
使用自定义属性的优点之一是能够简化主题。 应用程序不需要知道如何使用自定义属性。 相反,我们使用 JavaScript 或服务器端代码来设置自定义属性的值。 这些值的使用方式由样式表决定。
这再次意味着我们能够将逻辑与设计分开。 如果您有技术设计团队,作者可以更新样式表并决定如何应用自定义属性,而无需更改一行 JavaScript 或后端代码。
自定义属性还允许将一些主题的复杂性转移到 CSS 中,这种复杂性会对 CSS 的可维护性产生负面影响,因此请记住尽可能保持简单。
立即使用自定义属性
即使您支持 IE10 和 11,您也可以立即开始使用自定义属性。 本文中的大多数示例都与我们如何编写和构建 CSS 有关。 在可维护性方面的好处是显着的,但是,大多数示例只是减少了使用更复杂的代码可以完成的工作。
我使用一个名为 postcss-css-variables 的工具将自定义属性的大部分功能转换为相同代码的静态表示。 其他类似的工具会忽略媒体查询或复杂选择器中的自定义属性,将自定义属性视为预处理器变量。
这些工具不能做的是模拟自定义属性的运行时特性。 这意味着没有动态特性,如使用 JavaScript 进行主题化或更改属性。 在许多情况下,这可能没问题。 根据具体情况,UI 自定义可能被视为渐进式增强,而默认主题对于旧版浏览器来说是完全可以接受的。
加载正确的样式表
您可以通过多种方式使用 postCSS。 我使用gulp
过程为新旧浏览器编译单独的样式表。 我的gulp
任务的简化版本如下所示:
import gulp from "gulp"; import sass from "gulp-sass"; import postcss from "gulp-postcss"; import rename from "gulp-rename"; import cssvariables from "postcss-css-variables"; import autoprefixer from "autoprefixer"; import cssnano from "cssnano"; gulp.task("css-no-vars", () => gulp .src("./src/css/*.scss") .pipe(sass().on("error", sass.logError)) .pipe(postcss([cssvariables(), cssnano()])) .pipe(rename({ extname: ".no-vars.css" })) .pipe(gulp.dest("./dist/css")) ); gulp.task("css", () => gulp .src("./src/css/*.scss") .pipe(sass().on("error", sass.logError)) .pipe(postcss([cssnano()])) .pipe(rename({ extname: ".css" })) .pipe(gulp.dest("./dist/css")) );
这会产生两个 CSS 文件:一个具有自定义属性( styles.css
)的常规文件和一个用于旧浏览器的( styles.no-vars.css
)。 我希望为 IE10 和 11 提供styles.no-vars.css
和其他浏览器以获取常规 CSS 文件。
通常,我提倡使用功能查询,但 IE11 不支持功能查询,而且我们已经广泛使用自定义属性,因此在这种情况下提供不同的样式表是有意义的。
智能地提供不同的样式表并避免一闪而过的无样式内容并不是一项简单的任务。 如果您不需要自定义属性的动态特性,您可以考虑提供所有浏览器styles.no-vars.css
并将自定义属性简单地用作开发工具。
如果你想充分利用自定义属性的所有动态特性,我建议使用关键的 CSS 技术。 按照这些技术,主样式表是异步加载的,而关键的 CSS 是内联渲染的。 您的页眉可能如下所示:
<head> <style> /* inlined critical CSS */ </style> <script> loadCSS('non-critical.css'); </script> </head>
我们可以扩展它来加载styles.css
或styles.no-vars.css
,这取决于浏览器是否支持自定义属性。 我们可以像这样检测支持:
if ( window.CSS && CSS.supports('color', 'var(--test)') ) { loadCSS('styles.css'); } else { loadCSS('styles.no-vars.css'); }
结论
如果您一直在努力有效地组织 CSS,在响应式组件方面遇到困难,想要实现客户端主题,或者只是想从自定义属性开始,那么本指南应该会告诉您您需要知道的一切。
归结为理解 CSS 中动态变量和静态变量之间的区别以及一些简单的规则:
- 将逻辑与设计分离;
- 如果 CSS 属性发生变化,请考虑使用自定义属性;
- 更改自定义属性的值,而不是使用哪个自定义属性;
- 全局变量通常是静态的。
如果您遵循这些约定,您会发现使用自定义属性比您想象的要容易得多。 这甚至可能会改变您处理 CSS 的一般方式。
延伸阅读
- “It's Time To Start Using Custom Properties,” Serg Hospodarets
A general introduction to the syntax and the features of custom properties. - “Pragmatic, Practical, And Progressive Theming With Custom Properties,” Harry Roberts
More useful information on theming. - Custom Properties Collection, Mike Riethmuller on CodePen
A number of different examples you can experiment with.