在 CSS 中使用特性查询

CSS 中有一个你可能还不知道的工具。它很强大。它已经存在了一段时间了。而且它很可能会成为你最喜欢的 CSS 新功能之一。

瞧,@supports 规则。也称为 特性查询

使用 @supports,你可以在 CSS 中编写一个小型测试,以查看某个“特性”(CSS 属性或值)是否受支持,并根据答案应用一个代码块(或不应用)。例如

@supports (display: grid) {
   // code that will only run if CSS Grid is supported by the browser 
 }

如果浏览器理解 display: grid,那么括号内的所有样式都将被应用。否则所有样式都将被跳过。

现在,关于特性查询的用途似乎存在一些困惑。这不是一种外部验证,用于分析浏览器是否正确实现了 CSS 属性。如果你正在寻找这样的工具,请在其他地方寻找。特性查询要求浏览器自我报告某个 CSS 属性/值是否受支持,并使用答案来决定是否应用一个 CSS 代码块。如果浏览器错误地或不完整地实现了某个特性,@supports 将无法帮助你。如果浏览器错误地报告了它支持的 CSS,@supports 也无法帮助你。它不是让浏览器错误消失的魔法棒。

尽管如此,我发现 @supports 非常有用。@supports 规则多次让我比没有它时更早地使用了新的 CSS。

多年来,开发人员使用 Modernizr 来做特性查询所做的事情——但 Modernizr 需要 JavaScript。虽然脚本可能很小,但使用 Modernizr 构建的 CSS 需要下载 JavaScript 文件,执行 JavaScript 文件,并在应用 CSS 之前完成执行。涉及 JavaScript 始终比只使用 CSS 慢。需要 JavaScript 会导致出现故障的可能性——如果 JavaScript 没有执行会发生什么?此外,Modernizr 需要额外的复杂性,许多项目根本无法处理。特性查询更快、更强大且更容易使用。

你可能注意到特性查询的语法非常像媒体查询。我认为它们是表兄弟。

@supports (display: grid) {
  main {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  }
}

现在,大多数时候,你的 CSS 中不需要这样的测试。例如,你可以编写以下代码,而无需测试支持

aside {
  border: 1px solid black;
  border-radius: 1em;
}

如果浏览器理解 border-radius,那么它将在 aside 盒子上设置圆角。如果它不理解,它将跳过这一行代码并继续执行,留下盒子的边缘为方形。没有理由运行测试或使用特性查询。这就是 CSS 的工作方式。这是在 构建可靠的、渐进增强 CSS 中的一个基本原则。浏览器只是跳过它们不理解的代码,而不会抛出错误。

a screenshot of border radius effect in  old vs new browsers
大多数浏览器将在右侧显示 border-radius: 1em 的结果。然而,Internet Explorer 6、7 和 8 不会圆角,你将在左侧看到结果。请查看 codepen.io/jensimmons/pen/EydmkK 上的这个示例。

你不需要为此使用特性查询。

那么,你什么时候想使用 @supports 呢?特性查询是一种将 CSS 声明捆绑在一起的工具,以便它们在特定条件下作为一个组运行。当你想要应用旧的和新的 CSS 的混合,但仅当新的 CSS 受支持时才应用时,请使用特性查询。

让我们看一个使用首字母属性的示例。这个新的属性 initial-letter 告诉浏览器使相关的元素变大——就像首字母大写一样。在这里,段落中第一个单词的第一个字母被设置为文本四行的大小。太棒了。哦,但我也想让这个字母变粗体,并在其右侧添加一些边距,嘿,让我们把它变成一种漂亮的橙色。酷。

  p::first-letter {
     -webkit-initial-letter: 4;
     initial-letter: 4;
     color: #FE742F;
     font-weight: bold;
     margin-right: 0.5em;
  }
a screenshot of this example Initial Letter in Safari 9
以下是我们的首字母示例在 Safari 9 中的样子。

现在让我们看看在所有其他浏览器中会发生什么…

a screenshot of this Initial Letter example in other browsers
哦,不。这在所有其他浏览器中看起来都很糟糕。

好吧,这是不可接受的。我们不想更改字母的颜色,或者添加边距,或者使它变粗体,除非它也要通过首字母属性变大。我们需要一种方法来测试浏览器是否理解 initial-letter,并且仅在它理解的情况下才应用颜色、粗细和边距的更改。特性查询就应运而生了。

@supports (initial-letter: 4) or (-webkit-initial-letter: 4) {
  p::first-letter {
     -webkit-initial-letter: 4;
     initial-letter: 4;
     color: #FE742F;
     font-weight: bold;
     margin-right: 0.5em;
  }
}

请注意,你需要测试包含属性和值的完整字符串。这最初让我感到困惑。为什么我要测试 initial-letter: 4 ?值 4 重要吗?如果我输入 17 会怎么样?它需要与我在代码中进一步使用的值匹配吗?

@supports 规则测试一个包含属性和值的字符串,因为有时需要测试的是属性,有时需要测试的是值。对于 initial-letter 示例,你为值输入什么并不重要。但请考虑 @supports (display: grid),你会看到需要同时测试两者。每个浏览器都理解 display。只有实验性的浏览器理解 display: grid(目前)。

回到我们的示例:目前 initial-letter 仅在 Safari 9 中受支持,并且需要前缀。因此,我写了前缀,确保也包含无前缀版本,并且我写了测试来查找其中一个或另一个。是的,你可以在特性查询中使用 orandnot 语句。

以下是新的结果。理解 initial-letter 的浏览器显示了一个巨大的粗体橙色首字母。其他浏览器则表现得好像首字母不存在一样——就像我等待更多浏览器支持这个特性后再使用它一样。(顺便说一句,我们目前正在 Firefox 中实现首字母。)

a before and after comparison
左侧的屏幕截图来自 Safari 9。所有其他浏览器都显示右侧的结果。你可以在 codepen.io/jensimmons/pen/ONvdYL 上查看此代码的实际运行效果。

组织你的代码

现在,你可能很想使用这个工具将你的代码干净地分成两个分支。“嘿,浏览器,如果你理解视窗单位,就执行这个,如果你不理解,就执行另一个”。感觉很好,很整洁。

@supports (height: 100vh) {
  // my layout that uses viewport height
}
@supports not (height: 100vh) {
  // the alternative layout for older browsers
}
// WE MIGHT WISH. BUT THIS IS BAD CODE.

这不是一个好主意——至少现在不是。你看到了问题吗?

好吧,并非所有浏览器都支持特性查询。而且不理解 @supports 的浏览器将跳过这两个代码块。这可能很糟糕。

这意味着我们必须等到 100% 的浏览器支持特性查询才能使用它们吗?不。我们可以,我们应该现在就使用特性查询。只是不要像最后一个示例那样编写代码。

我们应该如何正确地做到这一点?好吧,就像我们使用媒体查询之前还没有 100% 支持一样。实际上,在过渡期使用特性查询比使用媒体查询更容易。你只需要聪明一点。

你需要在知道最旧的浏览器不支持特性查询或你正在测试的特性的情况下构建你的代码。我将向你展示如何做到这一点。

(当然,在遥远的未来,一旦 100% 的浏览器都支持特性查询,我们就可以更频繁地使用 @supports not 并以这种方式组织我们的代码。但要实现这一点还需要很多年。)

特性查询的支持

那么,特性查询从哪个版本开始支持呢?

好吧,@supports 从 2013 年年中开始就在 Firefox、Chrome 和 Opera 中运行。它在 Edge 的每个版本中也都能运行。Safari 在 2015 年秋季的 Safari 9 中发布了它。任何版本的 Internet Explorer、Opera Mini、Blackberry Browser 或 UC Browser 均不支持特性查询。

a screenshot from Can I Use showing support for Feature Queries
在 Can I Use 上查找对 特性查询 的支持

你可能认为 Internet Explorer 不支持特性查询是一个大问题。实际上,通常不是这样。我将稍后向你解释原因。我认为最大的障碍是 Safari 8。我们需要密切关注那里发生的事情。

让我们看另一个示例。假设我们有一些我们想要应用的布局代码,它需要使用 object-fit: cover 才能正常工作。对于不理解 object-fit 的浏览器,我们想要应用不同的布局 CSS。

a screenshot from Can I Use showing support for Object-fit
在 Can I Use 上查找对 Object Fit 的支持

所以让我们编写

div {
  width: 300px;
  background: yellow;
  // some complex code for a fallback layout
}
@supports (object-fit: cover) {
  img {
    object-fit: cover;
  }
  div {
    width: auto;
    background: green;
   // some other complex code for the fancy new layout
  }
}

那么会发生什么?特性查询要么支持,要么不支持,并且新特性 object-fit: cover 要么支持,要么不支持。将它们组合起来,我们得到四种可能性

特性查询支持? 特性支持? 会发生什么? 这是我们想要的结果吗?
支持特性查询 支持所讨论的特性
支持特性查询 不支持所讨论的特性
不支持特性查询 不支持所讨论的特性
不支持特性查询 支持所讨论的特性

情况 1:支持特性查询且支持所讨论特性的浏览器

Firefox、Chrome、Opera 和 Safari 9 都支持 object-fit 并且支持 @supports,因此此测试将正常运行,并且将应用此代码块中的代码。我们的图像将使用 object-fit: cover 进行裁剪,并且我们的 div 背景将变为绿色。

情况 2:支持特性查询但不支持所讨论特性的浏览器

Edge 不支持 object-fit,但它支持 @supports,因此此测试将运行并失败,从而阻止应用此代码块。图像将不会应用 object-fit,并且 div 将具有黄色背景。

这就是我们想要的。

情况 3:不支持特性查询也不支持所讨论特性的浏览器

这就是我们的老对手 Internet Explorer 出现的地方。IE 不理解 @supports 并且它不理解 object-fit。你可能认为这意味着我们无法使用特性查询——但这并不正确。

想想我们想要的结果。我们希望 IE 跳过整个代码块。而这正是会发生的事情。为什么?因为当它到达 @supports 时,它不识别语法,并且跳到结尾。

它可能“因为错误的原因”跳过了代码——它跳过了代码是因为它不理解 @supports,而不是因为它不理解 object-fit——但这有什么关系!我们仍然得到了我们想要的确切结果。

对于 Blackberry Browser 和 UC Browser for Android 也是如此。它们不理解 object-fit,也不理解 @supports,所以一切都很好。结果非常棒。

底线——只要你不支持特性查询的浏览器也不支持你正在测试的特性,那么你随时可以在不支持特性查询的浏览器中使用特性查询,这都没有问题。

仔细思考代码的逻辑。问问自己,当浏览器跳过这段代码时会发生什么?如果这就是你想要的结果,那么一切都已就绪。

情况 4:支持功能查询但支持该功能的浏览器

问题在于这第四种组合——当功能查询提出的测试没有运行时,但浏览器确实支持该功能并且应该运行该代码。

例如,object-fit 受 Safari 7.1(在 Mac 上)和 8(Mac 和 iOS)支持——但这两个浏览器都不支持功能查询。Opera Mini 也是如此——它将支持 object-fit 但不支持 @supports

会发生什么?这些浏览器会进入这段代码块,而不是使用代码将 object-fit:cover 应用于图像并将 div 的背景颜色改为绿色,而是跳过整个代码块,使背景颜色保持黄色。

这不是我们真正想要的。

特性查询支持? 特性支持? 会发生什么? 这是我们想要的结果吗?
支持特性查询 支持所讨论的特性 CSS 应用
支持特性查询 不支持所讨论的特性 CSS 未应用
不支持特性查询 不支持所讨论的特性 CSS 未应用
不支持特性查询 支持所讨论的特性 CSS 未应用 否,可能不会。

当然,这取决于具体的用例。也许这是我们可以接受的结果。旧浏览器获得了为旧浏览器设计的体验。网页仍然可以工作。

但大多数情况下,我们希望浏览器能够使用它支持的任何功能。这就是为什么在功能查询方面,Safari 8 可能是最大的问题,而不是 Internet Explorer。Safari 8 支持许多较新的属性——例如 Flexbox。你可能不想阻止 Safari 8 使用这些属性。这就是为什么我很少在 Flexbox 中使用 @supports,或者当我使用它时,我至少在我的代码中写了三个分支,其中一个带有 not。(这很快就会变得复杂,所以我甚至不会在这里解释。)

如果你使用的是在旧浏览器中比功能查询支持更好的功能,那么在编写代码时要考虑所有组合。确保不要阻止浏览器获得你希望它们获得的东西。

与此同时,使用最新 CSS 功能(例如 CSS Grid 和 Initial Letter)很容易使用 @supports。任何浏览器都不会在不支持功能查询的情况下支持 CSS Grid。我们不必担心使用最新功能时出现的第四个有问题的组合,这使得功能查询在我们前进的道路上变得非常有用。

这意味着,虽然 IE11 很可能在未来几年内继续存在,但我们可以将功能查询广泛地应用于 CSS 的最新进展。

最佳实践

现在我们意识到为什么不能这样写代码

@supports not (display: grid) {
    // code for older browsers
    // DO NOT COPY THIS EXAMPLE
}
@supports (display: grid) {
    // code for newer browsers
    // DID I SAY THIS IS REALLY BAD?
}

如果我们这样做,我们将阻止旧浏览器获得它们所需的代码。

相反,请像这样构建代码

// fallback code for older browsers

@supports (display: grid) {
    // code for newer browsers
    // including overrides of the code above, if needed
}

这正是我们在支持旧版 IE 时使用媒体查询所采用的策略。这种策略催生了“移动优先”这一说法。

我预计 CSS Grid 将在 2017 年登陆浏览器,并且我相信我们在实现未来布局时会经常使用功能查询。这比使用 JavaScript 要轻松得多,速度也快得多。@supports 将使我们能够为支持 CSS Grid 的浏览器做一些有趣而复杂的事情,同时为不支持的浏览器提供布局选项。

功能查询从 2013 年中期开始存在。随着 Safari 10 的即将发布,我认为现在是时候将 @supports 添加到我们的工具箱中了。

关于 Jen Simmons

Jen Simmons 是 Mozilla 的设计师和开发者倡导者,专门从事网页上的 CSS 和布局设计。她是 CSS 工作组的成员,并在许多会议上发表演讲,包括 An Event Apart、SXSW、Fluent、Smashing Conf 等。

更多 Jen Simmons 的文章…


16 条评论

  1. Flimm

    这是否支持嵌套?你可以在 @supports 块中使用媒体查询吗?

    2016 年 8 月 17 日 下午 08:11

    1. Jen Simmons

      在我写这篇文章的时候,我想知道嵌套是否有效——将一个 @supports 嵌套在另一个 @supports 中。然而,我没有花时间测试它,以找出答案。我也没有测试将 @supports 嵌套在 @media 中(或者将 @media 嵌套在 @supports 中)。我预计它们都会工作。但是需要测试。

      2016 年 8 月 18 日 上午 09:03

  2. Daniel Furze

    Jen,这篇帖子太棒了,写得很好 :)

    我一直使用 @supports 来实现基本的网格布局并提供回退,所以我真的很喜欢你展示如何做到这一点的方式。我不知道 Safari 的这些怪癖以及你可能会遇到的第四种情况!

    谢谢!

    2016 年 8 月 18 日 上午 01:22

    1. Jen Simmons

      感谢 Daniel!我认为 @supports 在我们明年过渡到 CSS Grid 时将被大量使用。而且很高兴看到 Safari 8 逐渐消失!

      2016 年 8 月 18 日 上午 09:04

  3. Ahmed Mohaisen

    很棒的帖子!

    结构清晰,易于理解,最重要的是(有意义)。请继续你的精彩作品 :)

    谢谢!

    2016 年 8 月 18 日 下午 22:02

  4. hans spiess

    从生产的角度来看,为什么你希望实现两种解决方案(回退 + 部分不支持的解决方案),而不是在项目放弃对不支持特定功能的浏览器的支持时重写你的 CSS?我觉得膨胀的代码超过了使用一些闪亮的新工具带来的好处。

    2016 年 8 月 20 日 下午 14:46

    1. Jen Simmons

      我总是以旧浏览器为目标编写我的 CSS。我希望我的设计能为尽可能多的受众服务。在十多年的 CSS 编写经验中,我从未回到过去的项目中删除代码,因为旧浏览器不再存在。我不担心“膨胀”。这里和那里的几千字节不会有任何影响。我也不会费心去弄清楚何时“放弃”对旧浏览器的支持。我可能不会在新的项目中花太多心思去考虑它在 IE6 中的工作方式,但为什么要花时间回到旧的网站中删除为 IE6 编写的代码?仍然*有* IE6 用户。

      这就是我在工作中使用的理念。能够使用“新”CSS 对我来说很重要。尤其是当“新”CSS 可能是五年前出现的。新的 CSS 属性在第一个或两个浏览器中出现,到最后不支持该属性的浏览器逐渐消失之间的时间通常为 5-10 年。渐进增强技术使人们能够同时使用和不使用某些东西——同时在支持和不支持该功能的浏览器中运行。这是打造优秀网站的关键。

      我想另一种选择是,在所有浏览器都支持之前,永远不要使用任何 CSS。但说实话,如果我必须这样做,我就不想从事这份职业了。你将只能使用 7-10 年前的 CSS。太糟糕了。为什么呢?

      2016 年 8 月 22 日 上午 10:52

  5. Ricardo Zea

    但是,不可能知道所有浏览器支持和不支持的所有属性和值。

    这是 Autoprefixer + Caniuse.com 解决的问题。

    写得很好,感谢分享 :)

    2016 年 8 月 21 日 上午 08:18

  6. Colin Lord

    喜欢这篇文章。感谢你写出来!

    2016 年 8 月 24 日 上午 11:49

  7. Patrik Illy

    你知道 autoprefixer 能否在 @support 条件中管理前缀吗?感谢你写出这篇好文章。

    2016 年 8 月 25 日 上午 00:47

    1. Jen Simmons

      我不知道。这是一个好问题。幸运的是,前缀正在消失——我们不会再引入带前缀的新 CSS 属性——这将使事情变得更容易。(例如,CSS Grid 现在没有任何浏览器以带前缀的形式发布新版本的规范。)

      2016 年 8 月 28 日 下午 13:36

      1. Patrik Illy

        我在 github 上查看了 autoprefixer 的仓库,有一些关于 @support 和前缀的问题。所以我想 autoprefixer 也管理这些条件。

        2016 年 8 月 28 日 下午 14:16

  8. Tinny

    我不知道……如何检查 @support 是否受支持?这难道不是在转移问题吗?

    2016 年 9 月 6 日 上午 03:26

    1. Jen Simmons

      你可以查看 Can I Use,了解在任何浏览器中支持哪些功能,包括 @supports。功能查询并不是为了找出其他无法知道的信息。它们是为了将仅在特定功能支持的情况下才会运行的 CSS 捆绑在一起。所以,这并不是在转移问题。它是在解决一个不同的问题。

      如果你需要帮助确定哪些 CSS 在哪些浏览器中受支持,Can I Use 是一个很好的资源:https://caniuse.cn

      2016 年 9 月 6 日 上午 06:45

      1. Tinny

        是的,但以不支持 @support 功能的 IE11 为例。
        在 IE11 中使用 @support 条件会发生什么?会被忽略吗?还是会解析?

        2016 年 9 月 6 日 下午 08:23

        1. Jen Simmons

          任何不理解 @supports 的浏览器都会忽略花括号内的所有内容——它会跳过所有代码。我在我的文章中详细解释了这一点。你应该去阅读它。

          2016 年 9 月 6 日 下午 10:56

本文评论已关闭。