您当前的位置: 首页 > 

寒冰屋

暂无认证

  • 2浏览

    0关注

    2286博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

不要与级联战斗,控制它!

寒冰屋 发布时间:2022-06-02 21:15:28 ,浏览量:2

目录

级联啊,:where你是吗?

示例:可配置的表格组件

CSS自定义属性

添加具有另一个数据属性的参数

级联层

现在你可以控制了!

如果你很自律,并且利用了CSS级联提供的继承,你最终会写更少的CSS。但是因为我们的样式通常来自各种来源——并且可能很难构建和维护——级联可能是令人沮丧的根源,也是我们最终得到比必要更多的CSS 的原因。

几年前,Harry Roberts提出了ITCSS,这是一种构建CSS的巧妙方法。

与BEM相结合,ITCSS已成为人们编写和组织CSS的流行方式。

然而,即使使用ITCSS和BEM,我们仍然有时会与级联作斗争。例如,我确信您必须在特定位置@import外部CSS组件以防止破坏东西,或者在某个时间点触及可怕的!important。

最近,我们的CSS工具箱中添加了一些新工具,它们使我们能够最终控制级联。让我们看看他们。

级联啊,:where你是吗?

使用:where伪选择器允许我们删除“就在用户代理默认样式之后”的特定性,无论CSS何时何地加载到文档中。这意味着整个事物的特异性几乎为零——完全消失了。这对于通用组件很方便,我们稍后会研究。

首先,想象一些通用样式,使用:where:

:where(table) {
  background-color: tan;
}

现在,如果您在:where选择器之前添加一些其他表格样式,如下所示:

table {
  background-color: hotpink;
}

:where(table) {
  background-color: tan;
}

…表格背景变为hotpink,即使table选择器是在级联中的:where选择器之前指定的。这就是:where的美妙之处,也是为什么它已经被用于CSS重置的原因。

:where有一个兄弟,其效果几乎完全相反::is选择器。

:is()伪类的特殊性被其最具体的论点的特殊性所取代。因此,使用:is()编写的选择器不一定具有与没有使用:is()编写的选择器相同的特性。选择器4级规范

扩展我们之前的示例:

:is(table) {
  --tbl-bgc: orange;
}
table {
  --tbl-bgc: tan;
}
:where(table) {
  --tbl-bgc: hotpink;
  background-color: var(--tbl-bgc);
}

背景颜色会是tan,因为:is的特殊性与table相同,但table放在后面。

https://codepen.io/geoffgraham/pen/XWeVaQw

但是,如果我们将其更改为:

:is(table, .c-tbl) {
  --tbl-bgc: orange;
}

...背景颜色将是orange,因为:is它具有最重的选择器的权重,即.c-tbl。

https://codepen.io/geoffgraham/pen/RwLxZmK

示例:可配置的表格组件

现在,让我们看看如何在我们的组件中使用:where。我们将构建一个表格组件,从HTML开始:

https://codepen.io/team/css-tricks/pen/abLEygb

让我们用一个:where-selector包裹起.c-tbl,为了好玩,给表格添加圆角。这意味着我们需要border-collapse:separate,因为当表格使用border-collapse: collapse时我们不能在表格单元格上使用border-radius:

:where(.c-tbl) {
  border-collapse: separate;
  border-spacing: 0;
  table-layout: auto;
  width: 99.9%;
}

和单元格对使用不同的样式:

:where(.c-tbl thead th) {
  background-color: hsl(200, 60%, 40%);
  border-style: solid;
  border-block-start-width: 0;
  border-inline-end-width: 1px;
  border-block-end-width: 0;
  border-inline-start-width: 0;
  color: hsl(200, 60%, 99%);
  padding-block: 1.25ch;
  padding-inline: 2ch;
  text-transform: uppercase;
}
:where(.c-tbl tbody td) {
  background-color: #FFF;
  border-color: hsl(200, 60%, 80%);
  border-style: solid;
  border-block-start-width: 0;
  border-inline-end-width: 1px;
  border-block-end-width: 1px;
  border-inline-start-width: 0;
  padding-block: 1.25ch;
  padding-inline: 2ch;
}

而且,由于我们的圆角和缺少的border-collapse: collapse,我们需要添加一些额外的样式,特别是表格边框和单元格上的悬停状态:

:where(.c-tbl tr td:first-of-type) {
  border-inline-start-width: 1px;
}
:where(.c-tbl tr th:last-of-type) {
  border-inline-color: hsl(200, 60%, 40%);
}
:where(.c-tbl tr th:first-of-type) {
  border-inline-start-color: hsl(200, 60%, 40%);
}
:where(.c-tbl thead th:first-of-type) {
  border-start-start-radius: 0.5rem;
}
:where(.c-tbl thead th:last-of-type) {
  border-start-end-radius: 0.5rem;
}
:where(.c-tbl tbody tr:last-of-type td:first-of-type) {
  border-end-start-radius: 0.5rem;
}
:where(.c-tbl tr:last-of-type td:last-of-type) {
  border-end-end-radius: 0.5rem;
}
/* hover */
@media (hover: hover) {
  :where(.c-tbl) tr:hover td {
    background-color: hsl(200, 60%, 95%);
  }
}

https://codepen.io/stoumann/pen/GRMjpyb

现在我们可以通过在我们的通用样式之前或:where之后注入其他样式来创建表格组件的变体(通过覆盖.c-tbl元素或添加BEM样式修饰符类(例如c-tbl--purple):

.c-tbl--purple th {
  background-color: hsl(330, 50%, 40%)
}
.c-tbl--purple td {
  border-color: hsl(330, 40%, 80%);
}
.c-tbl--purple tr th:last-of-type {
  border-inline-color: hsl(330, 50%, 40%);
}
.c-tbl--purple tr th:first-of-type {
  border-inline-start-color: hsl(330, 50%, 40%);
}

https://codepen.io/team/css-tricks/pen/ExwowYY

酷哦!但请注意我们如何不断重复颜色?如果我们想改变border-radiusorborder-width怎么办?这最终会导致大量重复的CSS。

让我们将所有这些移动到CSS自定义属性中,当我们这样做时,我们可以将所有可配置属性移动到组件“作用域”的顶部——即表格元素本身——这样我们以后可以轻松地使用它们。

CSS自定义属性

我将在HTML中进行切换,并在table元素上使用一个可以作为样式目标的data-component属性。

这个data-component将包含我们可以在组件的任何实例上使用的通用样式,即无论我们应用什么颜色变化,表格都需要的样式。特定表格组件实例的样式将包含在常规类中,使用来自通用组件的自定义属性。

[data-component="table"] {
  /* Styles needed for all table variations */
}
.c-tbl--purple {
  /* Styles for the purple variation */
}

如果我们将所有通用样式放在一个data-attribute中,我们可以使用我们想要的任何命名约定。这样,如果您的老板坚持将表的类命名为.BIGCORP__TABLE,.table-component或其他名称,我们就不必担心。

在通用组件中,每个CSS属性都指向一个自定义属性。必须在子元素上工作的属性,如border-color,在通用组件的根目录中指定:

:where([data-component="table"]) {
  /* These will will be used multiple times, and in other selectors */
  --tbl-hue: 200;
  --tbl-sat: 50%;
  --tbl-bdc: hsl(var(--tbl-hue), var(--tbl-sat), 80%);
}

/* Here, it's used on a child-node: */
:where([data-component="table"] td) {
  border-color: var(--tbl-bdc);
}

对于其他属性,决定它是否应该具有静态值,或者可以使用自己的自定义属性进行配置。如果您使用自定义属性,请记住定义一个默认值,在缺少变体类的情况下,表格可以回退到该默认值。

:where([data-component="table"]) {
  /* These are optional, with fallbacks */
  background-color: var(--tbl-bgc, transparent);
  border-collapse: var(--tbl-bdcl, separate);
}

如果您想知道我是如何命名自定义属性的,我使用的是组件前缀(例如--tbl),后跟一个Emmett缩写(例如-bgc)。在这种情况下,--tbl是组件前缀,-bgc是背景颜色,-bdcl是边框折叠。因此,例如,--tbl-bgc是表格组件的背景颜色。我只在使用组件属性时使用这个命名约定,而不是我倾向于保持更通用的全局属性。

https://codepen.io/stoumann/pen/MWEjaBv

现在,如果我们打开DevTools,我们可以使用自定义属性。例如,我们可以在HSL颜色中更改--tbl-hue为不同的色调值,设置--tbl-bdrs: 0为移除border-radius,等等。

当使用您自己的组件时,您会及时发现组件需要哪些参数(即自定义属性值)才能使事情看起来恰到好处。

我们还可以使用自定义属性来控制列对齐和宽度:

:where[data-component="table"] tr > *:nth-of-type(1)) {
  text-align: var(--ca1, initial);
  width: var(--cw1, initial);
  /* repeat for column 2 and 3, or use a SCSS-loop ... */
}

在DevTools中,选择表并将这些添加到element.styles选择器中:

element.style {
  --ca2: center; /* Align second column center */
  --ca3: right; /* Align third column right */
}

 现在,让我们使用常规类.c-tbl(在BEM用语中代表“组件表”)创建我们特定的组件样式。让我们把这个类扔到表标记中。

现在,让我们更改--tbl-hueCSS中的值,看看它是如何工作的,然后再开始处理所有属性值:

.c-tbl {
  --tbl-hue: 330;
}

请注意,我们只需要更新属性而不是编写全新的CSS!更改一个小属性会更新表格的颜色——没有新的类或覆盖级联中较低的属性。

 注意边框颜色也是如何变化的。那是因为表中的所有颜色都继承自--tbl-hue变量

我们可以编写一个更复杂的选择器,但仍然更新单个属性,以获得类似斑马条纹的东西:

.c-tbl tr:nth-child(even) td {
  --tbl-td-bgc: hsl(var(--tbl-hue), var(--tbl-sat), 95%);
}

 请记住:在哪里加载类并不重要。因为我们的通用样式正在使用:where,所以特殊性被消除了,并且无论在哪里使用,都将应用特定变体的任何自定义样式。这就是使用:where控制级联的美妙之处!

最重要的是,我们可以用几行CSS从通用样式创建各种表格组件。

 带有斑马条纹列的紫色表

 带有“noinlineborder”参数的灯台……我们将在接下来介绍

添加具有另一个数据属性的参数

到现在为止还挺好!通用表格组件非常简单。但是,如果它需要更类似于真实参数的东西怎么办?也许对于这样的事情:

  • 斑马条纹的行和列
  • 固定标题和固定列
  • 悬停状态选项,例如悬停行、悬停单元格、悬停列

我们可以简单地添加BEM样式的修饰符类,但实际上我们可以通过添加另一个数据属性来更有效地完成它。也许adata-param包含这样的参数:

然后,在我们的CSS中,我们可以使用属性选择器来匹配参数列表中的整个单词。例如,斑马条纹行:

[data-component="table"][data-param~="zebrarow"] tr:nth-child(even) td {
  --tbl-td-bgc: var(--tbl-zebra-bgc);
}

或斑马条纹列:

[data-component="table"][data-param~="zebracol"] td:nth-of-type(odd) {
  --tbl-td-bgc: var(--tbl-zebra-bgc);
}

让我们发疯,让表头和第一列都固定:

[data-component="table"][data-param~="stickycol"] thead tr th:first-child,[data-component="table"][data-param~="stickycol"] tbody tr td:first-child {
  --tbl-td-bgc: var(--tbl-zebra-bgc);
  inset-inline-start: 0;
  position: sticky;
}
[data-component="table"][data-param~="stickyrow"] thead th {
  inset-block-start: -1px;
  position: sticky;
}

这是一个允许您一次更改一个参数的演示:

演示中的默认light主题是这样的:

.c-tbl--light {
  --tbl-bdrs: 0;
  --tbl-sat: 15%;
  --tbl-th-bgc: #eee;
  --tbl-th-bdc: #eee;
  --tbl-th-c: #555;
  --tbl-th-tt: normal;
}

对应于这些样式…wheredata-param设置为noinlineborderwhich:

[data-param~="noinlineborder"] thead tr > th {
  border-block-start-width: 0;
  border-inline-end-width: 0;
  border-block-end-width: var(--tbl-bdw);
  border-inline-start-width: 0;
}

我知道我的样式和配置通用组件的data-attribute方式非常固执己见。这就是我滚动的方式,所以请随意坚持使用您最喜欢使用的任何方法,无论是BEM修改器类还是其他东西。

底线是:拥抱它们提供:where和:is的级联控制能力。并且,如果可能的话,以这样一种方式构建CSS,在创建新的组件变体时尽可能少地编写新的CSS!

级联层

我想看的最后一个级联破坏工具是“级联层”。在撰写本文时,它是CSS Cascading and Inheritance Level 5规范中定义的实验性功能,您可以通过启用该#enable-cascade-layers标志在Safari或Chrome中访问它。

 Bramus Van Damme很好地总结了这个概念:

Cascade Layers的真正威力来自其在Cascade中的独特位置:在选择器特异性和外观顺序之前。正因为如此,我们不需要担心在其他层中使用的CSS的选择器特定性,也不需要担心我们将CSS加载到这些层中的顺序——这对于大型团队或加载时会非常方便在第三方CSS中。

也许更好的是他的插图显示了级联层在级联中的位置:

 

在本文开头,我提到了ITCSS——一种通过指定通用样式、组件等的加载顺序来驯服级联的方法。级联层允许我们在给定位置注入样式表。因此Cascade Layers中这种结构的简化版本如下所示:

@layer generic, components;

通过这一行,我们已经确定了图层的顺序。首先是通用样式,然后是特定于组件的样式。

假设我们加载通用样式的时间比我们的组件样式晚得多:

@layer components {
  body {
    background-color: lightseagreen;
  }
}

/* MUCH, much later... */

@layer generic { 
  body {
    background-color: tomato;
  }
}

background-color将是lightseagreen因为我们的组件样式层设置在通用样式层之后。因此,即使它们是在图层样式之前编写的,components层中的样式也会“获胜” 。

同样,这只是另一个控制CSS级联如何应用样式的工具,使我们能够更灵活地按逻辑组织事物,而不是与特定性搏斗。

现在你可以控制了!

这里的重点是CSS级联变得更容易处理,这要归功于新功能。我们看到了伪选择器:where和:is伪选择器如何允许我们控制特异性,或者分别通过剥离整个规则集的特异性或采用最具体参数的特异性。然后我们使用CSS自定义属性来覆盖样式,而无需编写新的类来覆盖另一个。从那里开始,我们稍微绕过数据属性通道,以帮助我们增加更多的灵活性,仅通过向HTML添加参数来创建组件变体。最后,我们戳了一下Cascade Layers,它应该证明可以方便地使用@layer。

如果你从这篇文章中只得到一个收获,我希望CSS级联不再是它经常成为的敌人。我们正在获得停止与之抗争的工具,并开始更加努力。

https://css-tricks.com/dont-fight-the-cascade-control-it/

关注
打赏
1665926880
查看更多评论
立即登录/注册

微信扫码登录

0.0669s