CSS attr() 函数与自定义属性相比差远了

Avatar of Chris Coyier
Chris Coyier

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

通常,CSS 和 HTML 之间的连接是 CSS 选择器匹配 HTML 元素,而 CSS 对它们进行样式设置。 CSS 不了解 HTML 中的实际内容。 但确实存在一种方法,CSS 可以获取 HTML 中的数据,只要该数据位于该 HTML 元素的属性中。

就是这样

div::after {
  content: attr(data-whatever);
}

这当然很有趣。 例如,您可以将其用于(相当不可访问的)工具提示

<button data-tooltip="Information only mouse-having sighted people will see.">
  Button
</button>
button:hover::after {
  content: attr(data-tooltip);
  /* positioned and styled and whatnot */
  /* ya, a :focus style would buy you a tad more a11y */
}

但是您不能将 HTML 放入属性值中,因此这些工具提示仅限于字符串值,并且不能在其中包含标题、链接或任何其他类似内容。

这是一个更好的用例。 存在一个旧的打印样式表要点,您可以在其中使用 attr() 添加链接的 URL,以便您实际上可以查看链接的链接目标

@media (print) {
  a[href]::after {
    content: " (" attr(href) " )";
  }
}

真聪明。 但是还有什么呢? 你能传递一种颜色吗?

<h2 data-color="#f06d06">
  Custom Colored Header
</h2>

这不是无效的,但没有用。

h2 {
  /* Not gonna work */
  color: attr(data-color);
}

attr() 中的值是字符串。 即使该字符串与十六进制代码的格式相同,它也不会用作十六进制代码。

您也不能传递一个可以在 background-image() 等中使用的 URL。 您也不能传递像 320px4rem0.8vw 这样的单位。

CSS 的 attr() 函数是字符串,而字符串真正用作 content,而 content(不可选择且有一定程度的不可访问性)本身并不是特别有用。 例如,您不能选择伪内容的文本,也不能搜索它,这使得它相当不可访问。

您知道什么可以传递任何类型的值并且与属性一样易于实现吗?

CSS 自定义属性!

您可以将它们直接放入任何元素的 style 属性中。 现在这些值对该元素可用

<button 
  style="
    --tooltip-string: 'Ug. Tooltips.';
    --tooltip-color: #f06d06;
    --tooltip-font-size: 11px;
    --tooltip-top: -10px
  "
>
  Button
</button>

我们在上面将字符串传递给 CSS,但传递了颜色和长度值。 这些值可以立即按原样使用

button::after {
  content: var(--tooltip-string);
  color: var(--tooltip-color);
  font-size: var(--tooltip-font-size);
}

这是该演示,其中包含一些繁琐的“逻辑”(需要大量改进才能真正有用)以允许变体

查看 CodePen 上 Chris Coyier 的 CSS 自定义属性比 attr() 更好 创作。

实际上,这并没有提高可访问性。 如果我要真正实现工具提示,我可能会 仔细阅读这篇博文

attr() 的其他“好”用例呢?

经常出现的一个用例是响应式数据表。 想象一个表格,其中标题位于顶行,数据行位于下方

<table>
  <thead>
  <tr>
    <th>First Name</th>
    <th>Last Name</th>
    ....
  </tr>
  </thead>
  <tbody>
  <tr>
    <td>Chris</td>
    <td>Coyier</td>
    ...
  </tr>
  ...
  </tbody>
</table>

这样的数据行在小屏幕上可能会出现问题(太宽)。 因此,在响应式数据表中,我们可能会隐藏该顶行,并改为显示每个单元格的标签。

@media (max-width: 500px) {
  thead {
    display: none;
  }
  /* Need to reveal another label now that we've hidden the normal labels */
}

该标签从哪里来? 我们可以做…

 . ...
  <tr>
    <td data-label="First Name">Chris</td>
    <td data-label="Last Name">Coyier</td>
    ...
  </tr>

然后

td::before { 
  content: attr(data-label);
  /* Also display: block things and such */ 
}

这是一个相当不错的用例。 如果我们对 <thead> 使用某种可访问的隐藏方法,它甚至可能会通过 a11y 检查。

但是,同样的操作也可以使用 CSS 自定义属性来完成……

 . ...
  <tr>
    <td style="--label: 'First Name';">Chris</td>
    <td style="--label: 'Last Name';">Chris</td>
    ...
  </tr>
td::before { 
  content: var(--label);
  ...
}

Eric Bidelman 向我指出了一种使用伪内容显示输入值的方法。

<style>
  input {
    vertical-align: middle;
    margin: 2em;
    font-size: 14px;
    height: 20px;
  }
  input::after {
    content: attr(data-value) '/' attr(max);
    position: relative;
    left: 135px;
    top: -20px;
  }
</style>

<input type="range" min="0" max="100" value="25">

<script>
  var input = document.querySelector('input');

  input.dataset.value = input.value; // Set an initial value.

  input.addEventListener('change', function(e) {
    this.dataset.value = this.value;
  });
</script>

我觉得这有点危险,因为我认为伪内容不应该对像 <input> 这样的替换元素起作用。 这可能是 output 的工作,而 JavaScript 本质上是相同的。 您可以使用额外的元素来使用伪内容,但实际上没有必要。


利用伪内容不可复制的事实也很巧妙。 例如,GitHub 使用 data-line-number=""::before { content: attr(data-line-number); } 进行代码块行号。

当他们尝试复制代码时,没有人喜欢选择行号! 这里很好用(可能比 CSS 计数器更灵活),但同样,CSS 自定义属性也可以处理。

<td style="--line-num: 5"> ... </td>

您可以争辩说这更好,因为如果您确实想要使用 CSS 计数器,您可以使用第一个值来启动,而不需要在每一行上使用它。

查看 CodePen 上 Chris Coyier 的 行号 创作。

与涉及在 CSS 中复制文本以进行样式设置的印刷技巧相同。 查看 Mandy Michael 使用 attr()酷炫演示。 我相信您可以想象 --heading: "Fracture"; 如何在那里发挥作用。

CSS3 值规范(在候选推荐中)有一种方法可以使 attr() 有用

我不确定这是否很重要,因为我认为 CSS 自定义属性几乎完全替代了 attr(),但规范确实 专门介绍了这一点,可能是为了使其更有用。

想法是在 CSS 中获取值时设置值类型。

<div data-color="red">Some Words</div>
div {
  color: attr(data-color color);
}

或者…

<span data-size="50">span</span>
span {
  font-size: attr(data-size px);
}

但据我所知,没有浏览器支持此功能。