使用自定义属性'堆栈”来驯服级联
CSS自定义属性:掌控级联和继承的新方法
自1994年CSS诞生以来,级联和继承就定义了我们在网页上的设计方式。两者都是强大的功能,但是作为开发者,我们对它们如何交互的控制力非常有限。选择器特异性和源代码顺序提供了一些最小的“分层”控制,但缺乏细微差别——并且继承需要一个 unbroken lineage。现在,CSS自定义属性允许我们以新的方式管理和控制级联和继承。
本文将展示如何使用自定义属性“堆栈”来解决级联中的一些常见问题:从作用域组件样式到更明确的意图分层。
快速了解自定义属性
浏览器使用供应商前缀(如 -webkit- 或 -moz-)定义新属性的方式相同,我们可以使用“空” -- 前缀定义自己的自定义属性。像Sass或JavaScript中的变量一样,我们可以使用它们来命名、存储和检索值——但是像CSS中的其他属性一样,它们会随着DOM级联和继承。
<code>/* 定义自定义属性 */ html { --brand-color: rebeccapurple; }</code>
为了访问这些捕获的值,我们使用 var() 函数。它有两个部分:首先是自定义属性的名称,然后是该属性未定义时的后备值:
<code>button { /* 如果可用,则使用 --brand-color,否则回退到 deeppink */ background: var(--brand-color, deeppink); }</code>
这不是旧浏览器的支持回退。如果浏览器不理解自定义属性,它将忽略整个 var() 声明。相反,这是一种处理未定义变量的内置方法,类似于字体堆栈在字体不可用时定义后备字体系列。如果我们不提供后备值,则默认为未设置。
构建变量“堆栈”
这种定义后备值的能力类似于在 font-family 属性中使用的“字体堆栈”。如果第一个系列不可用,则将使用第二个系列,依此类推。var() 函数只接受单个后备值,但我们可以嵌套 var() 函数来创建任意大小的自定义属性后备“堆栈”:
<code>button { /* 尝试 Consolas,然后是 Menlo,然后是 Monaco,最后是 monospace */ font-family: Consolas, Menlo, Monaco, monospace; /* 尝试 --state,然后是 --button-color,然后是 --brand-color,最后是 deeppink */ background: var(--state, var(--button-color, var(--brand-color, deeppink))); }</code>
如果用于堆叠属性的嵌套语法看起来很笨重,可以使用 Sass 等预处理器使其更紧凑。
需要单个后备值限制才能支持包含逗号的后备值——例如字体堆栈或分层背景图像:
<code>html { /* 后备值为 "Helvetica, Arial, sans-serif" */ font-family: var(--my-font, Helvetica, Arial, sans-serif); }</code>
定义“作用域”
CSS 选择器允许我们深入到 HTML DOM 树中,并设置页面上任何位置的元素或特定嵌套上下文中的元素的样式。
<code>/* 所有链接 */ a { color: slateblue; } /* section 内的链接 */ section a { color: rebeccapurple; } /* article 内的链接 */ article a { color: deeppink; }</code>
这很有用,但它并没有捕捉“模块化”面向对象或组件驱动样式的现实。我们可能有许多 article 和 aside,以各种配置嵌套。我们需要一种方法来阐明在它们重叠时哪个上下文或作用域应该优先。
邻近作用域
假设我们有一个 .light 主题和一个 .dark 主题。我们可以在根元素上使用这些类来定义页面范围的默认值,但我们也可以将它们应用于以各种方式嵌套的特定组件:
每次我们应用颜色模式类之一时,背景和颜色属性都会被重置,然后由嵌套的标题和段落继承。在我们的主上下文中,颜色继承自 .light 类,而嵌套的标题和段落继承自 .dark 类。继承基于直接血统,因此具有定义值的最近祖先将优先。我们称之为邻近性。
邻近性对继承很重要,但它对选择器没有影响,选择器依赖于特异性。如果我们想设置内部暗色或浅色容器中的某些内容的样式,这就会成为一个问题。
在这里,我尝试定义浅色和深色按钮变体。浅色模式按钮应为 rebeccapurple,带有白色文本,以便它们脱颖而出,而深色模式按钮应为 plum,带有黑色文本。我们根据浅色和深色上下文直接选择按钮,但这不起作用:
一些按钮同时处于两种上下文中,具有 .light 和 .dark 祖先。在这种情况下,我们想要的是让最近的主题接管(继承邻近性行为),但我们得到的却是第二个选择器覆盖第一个选择器(级联行为)。由于这两个选择器具有相同特异性,因此源代码顺序决定了获胜者。
自定义属性和邻近性
我们需要一种方法来从主题继承这些属性,但只将它们应用于特定的子元素,例如我们的按钮。自定义属性使这成为可能!我们可以在浅色和深色容器上定义值,而只在嵌套元素(如我们的按钮)上使用它们的继承值。
我们将首先设置按钮以使用自定义属性,并使用后备“默认”值,以防这些属性未定义:
<code>button { background: var(--btn-color, rebeccapurple); color: var(--btn-contrast, white); }</code>
现在我们可以根据上下文设置这些值,它们将根据邻近性和继承作用域到相应的祖先:
<code>.dark { --btn-color: plum; --btn-contrast: black; } .light { --btn-color: rebeccapurple; --btn-contrast: white; }</code>
作为额外奖励,我们总体上使用了更少的代码,以及一个统一的按钮定义:
我认为这是为按钮组件创建可用参数的 API。Sara Soueidan 和 Lea Verou 都在最近的文章中很好地介绍了这一点。
组件所有权
有时邻近性不足以定义作用域。当 JavaScript 框架生成“作用域样式”时,它们正在建立特定的对象元素所有权。“选项卡布局”组件拥有选项卡本身,但不拥有每个选项卡后面的内容。这也是 BEM 约定试图在复杂的 .block__element 类名中捕捉的内容。
Nicole Sullivan 在 2011 年创造了“甜甜圈作用域”一词来讨论这个问题。虽然我相信她对这个问题有更新的想法,但根本问题并没有改变。选择器和特异性非常适合描述我们如何在广泛模式之上构建详细样式的方式,但它们并没有传达清晰的所有权感。
我们可以使用自定义属性堆栈来帮助解决这个问题。我们将首先在元素上创建“全局”属性,这些属性用于我们的默认颜色:
<code>html { --background--global: white; --color--global: black; --btn-color--global: rebeccapurple; --btn-contrast--global: white; }</code>
现在,我们可以在任何想要引用它的位置使用它。我们将使用 data-theme 属性来应用我们的前景和背景颜色。我们希望全局值提供默认后备值,但我们也希望可以选择使用特定主题覆盖它。这就是“堆栈”的用武之地:
<code>[data-theme] { /* 如果没有组件值,则使用全局值 */ background: var(--background--component, var(--background--global)); color: var(--color--component, var(--color--global)); }</code>
现在我们可以通过将 *--component 属性设置为全局属性的反向来定义反向组件:
<code>[data-theme='invert'] { --background--component: var(--color--global); --color--component: var(--background--global); }</code>
但我们不希望这些设置继承到所有权甜甜圈之外,因此我们在每个主题上将这些值重置为 initial(未定义)。我们需要以较低特异性或较早的源代码顺序执行此操作,以便它提供每个主题都可以覆盖的默认值:
<code>[data-theme] { --background--component: initial; --color--component: initial; }</code>
initial 关键字在用于自定义属性时具有特殊含义,将其恢复为 Guaranteed-Invalid 状态。这意味着它不会传递给 set background: initial 或 color: initial,自定义属性将变为未定义,我们将回退到堆栈中的下一个值,即全局设置。
我们可以对按钮执行相同的操作,然后确保将 data-theme 应用于每个组件。如果没有给出特定主题,则每个组件都将默认为全局主题:
定义“来源”
CSS 级联是一系列过滤层,用于确定在同一属性上定义多个值时哪个值应该优先。我们最常与特异性层交互,或者基于源代码顺序的最终分层——但级联的第一层是样式的“来源”。来源描述了样式的来源——通常是浏览器(默认值)、用户(首选项)或作者(我们)。
默认情况下,作者样式会覆盖用户首选项,用户首选项会覆盖浏览器默认值。当任何人将!important
应用于样式时,这就会发生变化,并且来源会反转:浏览器!important
样式具有最高的来源,然后是重要的用户首选项,然后是我们的作者重要样式,高于所有普通层。还有一些其他的来源,但我们在这里不再赘述。
当我们创建自定义属性“堆栈”时,我们正在构建非常类似的行为。如果我们想将现有来源表示为自定义属性堆栈,它看起来会像这样:
<code>.origins-as-custom-properties { color: var(--browser-important, var(--user-important, var(--author-important, var(--author, var(--user, var(--browser)))))); }</code>
这些层已经存在,因此没有理由重新创建它们。但是当我们在上面分层我们的“全局”和“组件”样式时,我们正在做类似的事情——创建一个“组件”来源层,它会覆盖我们的“全局”层。同样的方法可以用来解决CSS中各种分层问题,这些问题不能总是用特异性来描述:
- 覆盖 » 组件 » 主题 » 默认值
- 主题 » 设计系统或框架
- 状态 » 类型 » 默认值
让我们再看看一些按钮。我们需要一个默认按钮样式、一个禁用状态和各种按钮“类型”,例如 danger、primary 和 secondary。我们希望禁用状态始终覆盖类型变体,但选择器无法捕捉这种区别:
但是我们可以定义一个堆栈,以我们想要优先的顺序提供“类型”和“状态”层:
<code>button { background: var(--btn-state, var(--btn-type, var(--btn-default))); }</code>
现在,当我们设置两个变量时,状态将始终优先:
我已经使用这种技术创建了一个级联颜色框架,该框架允许基于分层进行自定义主题设置:
- HTML 中预定义的主题属性
- 用户颜色偏好
- 浅色和深色模式
- 全局主题默认值
混合搭配
这些方法可以达到极致,但是大多数日常用例可以使用堆栈中的两三个值来处理,通常结合使用上述技术:
- 定义层的变量堆栈
- 继承根据邻近性和作用域设置它们
- 仔细应用 initial 值以从作用域中删除嵌套元素
我们在 OddBird 的项目中一直在使用这些自定义属性“堆栈”。我们仍在不断探索,但它们已经帮助我们解决了仅使用选择器和特异性难以解决的问题。使用自定义属性,我们不必与级联或继承作斗争。我们可以按预期捕获和利用它们,并更好地控制它们在每个实例中的应用方式。对我来说,这对 CSS 来说是一个巨大的胜利——尤其是在开发样式框架、工具和系统时。
以上是使用自定义属性'堆栈”来驯服级联的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

您是否曾经在项目上需要一个倒计时计时器?对于这样的东西,可以自然访问插件,但实际上更多

在元素个数不固定的情况下如何通过CSS选择第一个指定类名的子元素在处理HTML结构时,常常会遇到元素个数不�...

关于Flex布局中紫色斜线区域的疑问在使用Flex布局时,你可能会遇到一些令人困惑的现象,比如在开发者工具(d...

格子呢是一块图案布,通常与苏格兰有关,尤其是他们时尚的苏格兰语。在Tartanify.com上,我们收集了5,000多个格子呢

在Safari中使用自定义样式表的问题探讨今天我们来探讨一个关于Safari浏览器的自定义样式表应用问题。前端新手...
