@supports 的工作原理

Avatar of Chris Coyier
Chris Coyier on

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

CSS 有一个很酷的功能,它允许我们测试浏览器是否支持特定的属性或属性:值组合,然后再应用样式块 - 就像一个 @media 查询匹配时,比如浏览器窗口的宽度小于某个指定的大小,然后其中的 CSS 就会生效。同样,当正在测试的属性:值对在当前浏览器中受支持时,此功能内的 CSS 就会生效。这个功能叫做 @supports,它看起来像这样

@supports (display: grid) {
  .main {
    display: grid;
  }
}

为什么呢?好吧,这有点棘手。就我个人而言,我发现自己并没有经常用到它。CSS 有自然的回退机制,这样如果浏览器不理解属性:值组合,那么它就会忽略它,如果存在任何内容,就会使用之前声明的内容,这得益于层叠。有时这可以用来处理回退,最终的结果是更简洁。我当然不是那种希望它在所有浏览器中都保持一致的人,但我也不是那种为了让它们看起来像那种编写详细的回退的人。我通常更喜欢这种情况,即属性:值的自然故障不会造成任何破坏功能的重大影响。

也就是说,@supports 肯定有它的用例!而且当我把这篇文章整理好后,我发现很多人在很多有趣的情况下都使用了它。

一个经典的用例

我在引言中使用的示例是一个经典的示例,你将在很多关于这个主题的写作中看到它。这里稍微详细一点

/* We're gonna write a fallback up here in a minute */

@supports (display: grid) {
  .photo-layout {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    grid-gap: 2rem;
  }
}

不错的网格!重复和自动填充列是 CSS 网格 的一个很酷的功能。但是,当然,有些浏览器不支持网格,或者不支持我在上面使用的所有特定功能。

例如,iOS 在版本 10.2 中发布了对 CSS 网格的支持,但 iOS 从版本 7 开始就支持弹性盒子。对于那些使用旧版 iOS 设备的用户来说,这是一个很大的差距,他们支持弹性盒子,但不支持网格。我相信还有更多类似的差距,但你可能已经明白了。

根据要求,让它的回退为空可能可以接受。例如,垂直堆叠的块级元素,而不是多列网格布局。我通常可以接受。但是,假设它不可接受,比如一个图片库或一些绝对需要一些基本的网格状结构的东西。在这种情况下,从弹性盒子作为默认值开始,使用 @supports 在支持的地方应用网格功能可能效果更好……

.photo-layout {
  display: flex;
  flex-wrap: wrap;
  > div {
    flex: 200px;
    margin: 1rem;
  }
}

@supports (display: grid) {
  .photo-layout {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    grid-gap: 2rem;
    > div {
      margin: 0;
    }
  }
}

“回退”是 @supports 块之外的代码(在上面示例中,块上面的属性),而网格代码在里面或后面。@supports 块不会改变任何特异性,因此我们需要源代码顺序来帮助确保覆盖有效。

请注意,我不得不重置 @supports 块内部 div 的边距。这就是我发现有点烦人的地方。这两种情况之间存在足够的重叠,需要非常清楚地了解它们是如何相互影响的。

难道你不希望它能完全逻辑分离吗?……

@supports 块中“没有”逻辑,但这并不意味着它应该总是被使用

Jen Simmons 几年之前在名为 在 CSS 中使用特性查询 的文章中提出了这个示例

/* Considered a BAD PRACTICE, at least if you're supporting IE 11 and iOS 8 and older */
@supports not (display: grid) {
   /* Isolated code for non-support of grid */
}
@supports (display: grid) {
   /* Isolated code for support of grid */
}

请注意第一个块中的 not 操作符。它正在检查那些不支持网格的浏览器,以便将某些样式应用于这些浏览器。这种方法被认为是不好的做法的原因是必须考虑对 @supports 本身的浏览器支持!。这就是让它变得如此棘手的原因。

以逻辑分离的 @supports 块的方式编写代码非常吸引人,因为这样每次都从头开始,不需要覆盖之前的值,也不需要处理那些逻辑上的难题。但是,让我们回到之前考虑过的相同 iOS 情况……@supports 在 iOS 版本 9 中发布(正好在弹性盒子在 iOS 7 中发布和网格在 iOS 10.2 中发布之间)。这意味着任何使用 not 操作符检查 (display: grid) {} 支持的 @supports 块中的弹性盒子回退代码在 iOS 7 或 8 中都不会起作用,这意味着回退现在需要一个回退来在其他情况下起作用的浏览器中起作用。Whew!

使用 @supports 的主要原因是,根据功能支持,考虑非常不同的实现方式,如果代码块是分开的,那么在这些实现之间推理和区分会更容易。

我们可能会达到一个能够使用这种互斥块而无需担心的时候。说到这个……

@supports 随着时间的推移可能会变得更加有用。

一旦 @supports 在所有需要支持的浏览器中得到支持,那么就可以开始更积极地使用它,而无需考虑 @supports 本身是否受支持的复杂问题。以下是关于它的支持网格

这些浏览器支持数据来自 Caniuse,其中包含更多详细信息。数字表示浏览器从该版本开始支持该功能。

台式机

ChromeFirefoxIEEdgeSafari
2822129

移动设备/平板电脑

Android ChromeAndroid FirefoxAndroidiOS Safari
1271274.49.0-9.2

基本上,IE 11 和任何仍然使用 iOS 8 的 iOS 设备都是痛点。如果你的需求已经超过了这些,那么你可以更自由地使用 @supports

讽刺的是,还没有出现太多具有明确 @supports 用例的 CSS 功能,但有一些!显然,可以测试一些新的花哨功能,比如 Houdini

(我不确定在 @supports 块中放入什么来做到这一点。还有其他人做过吗?)

当 @supports 没有做任何有用的事情时

我见过很多 @supports 的使用,最终的结果和不使用它完全一样。例如……

@supports (transform: rotate(5deg)) {
  .avatar {
    transform: rotate(5deg);
  }
}

在某种程度上,这在逻辑上是完全合理的。如果支持变换,就使用它们。但是,如果在不支持的情况下没有发生任何不同的事情,那么它是没有必要的。在这种情况下,变换可以在没有 @supports 块的情况下失败,结果是一样的。

这是 另一个示例

有一些浏览器扩展可以用来玩 @supports

有两个!

它们都是围绕这样一个想法设计的,即我们可以在 CSS 中编写 @supports 块,然后像在支持或不支持该功能的浏览器中查看代码的渲染一样,打开或关闭它们。

这是一个使用弹性盒子回退的网格场景的 Keith 工具的视频

这很有趣,而且技术也很棒。但是在这个确切的场景中,如果我能用弹性盒子完美地实现布局,那么我可能会直接这样做,并节省这少量的 技术债务

Ire 的工具(她在文章 创建特性查询管理器开发工具扩展 中写了关于这个工具的文章)采用了 slightly different approach,它显示了在 CSS 中实际编写的特性查询,并提供切换按钮来打开和关闭它们。我认为它不能通过 iframe 工作,所以我打开了 调试模式 来在 CodePen 上使用它。

更多现实世界中@supports 的用例

这是来自 Erik Vorhes 的一个。他正在 这里为一些自定义复选框和单选按钮设置样式,但将所有内容都包装在一个 @supports 块中。除非块通过支持检查,否则不会应用任何样式。

@supports (transform: rotate(1turn)) and (opacity: 0) {
  /* all the styling for Erik's custom checkboxes and radio buttons */
}

以下是我遇到的其他几个例子

  • Joe WrightTiago Nunes 提到了将它用于 position: sticky;。我希望能看到这里的演示!就像你在 position: sticky; 上,但必须做一些不同的事情,而不是让它在不支持的浏览器中失败。
  • Keith GrantMatthias Ott 提到了将它用于 object-fit: contain;。Matthias 有一个演示,其中使用定位技巧来使图像填充容器,当该属性可用时,它会通过该属性变得更容易和更好。
  • Ryan Filler 提到 将它用于 mix-blend-mode。他的示例在元素上设置了更高的不透明度,但是如果支持 mix-blend-mode,它会使用更低的透明度和该属性,这会导致通过自身元素看到的效果。
  • .thing {
      opacity: 0.5;
    }
    @supports (mix-blend-mode: multiply) {
      .thing {
        mix-blend-mode: multiply;
        opacity: 0.75;
      }
    }
  • Rik Schennink 提到backdrop-filter 属性。他说,“当它被支持时,背景颜色的不透明度通常需要一些微调。”
  • Nour Saud 提到 它可以通过特定供应商前缀属性来检测 Edge:@supports (-ms-ime-align:auto) { }
  • Amber Weinberg 提到 将它用于 clip-path,因为调整元素的大小或填充将适应剪裁不可用时的状况。
  • Ralph Holzmann 提到 将它用于测试 “notch” 相关内容(环境变量)。
  • Stacy Kvernmo 提到 将它用于下降帽字符所需的各种属性。Jen Simmons 也在 她的文章 中提到了这个用例。有一个 initial-letter CSS 属性,它对于下降帽非常棒,但与其他属性一起使用,如果你不支持 initial-letter(或者有完全不同的备用方案),你可能不想完全应用这些属性。

这是来自 Nick Colley 的一个额外内容,不是 @supports,而是 @media!精神是一样的。它可以防止这样的触摸设备上的“卡住”悬停状态

@media (hover: hover) {
  a:hover {
    background: yellow;
  }
}

@supports 中的逻辑

基本

@supports (initial-letter: 4) {

}

@supports not (initial-letter: 4) {

}

@supports (initial-letter: 4) and (transform: scale(2)) {

}

@supports (initial-letter: 4) or (-webkit-initial-letter: 4) {

}

组合

@supports ((display: -webkit-flex) or
          (display: -moz-flex) or
          (display: flex)) and (-webkit-appearance: caret) {

}

JavaScript 变体

JavaScript 有一个 API 可以实现这一点。要测试它是否存在……

if (window.CSS && window.CSS.supports) {
  // Apparently old Opera had a weird implementation, so you could also do:
  // !!((window.CSS && window.CSS.supports) || window.supportsCSS || false)
}

要使用它,要么将属性作为第一个参数传递给它,将值作为另一个参数传递给它

const supportsGrid = CSS.supports("display", "grid");

……或者将所有内容都放在一个字符串中,反映 CSS 语法

const supportsGrid = CSS.supports("(display: grid)");

选择器测试

在撰写本文时,只有 Firefox 支持这种测试(在实验性标志后面),但有一种方法可以使用 @supports 来测试对选择器的支持。 MDN 的演示

@supports selector(A > B) {
}

你呢?

当然,我们希望看到评论中 @supports 用例的 Pens。所以分享吧!