使用特征检测、条件语句和选择器组

Avatar of Jirka Vebr
Jirka Vebr on

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

CSS 的设计方式允许相对无缝地添加新功能。 自该语言诞生以来,规范要求浏览器优雅地忽略任何不支持的属性、值、选择器或 at-rules。 因此,在大多数情况下,可以成功地使用较新的技术,而不会在较旧的浏览器中造成任何问题。

考虑相对较新的 caret-color 属性(它更改输入中光标的颜色)。 其支持度 仍然很低,但这并不意味着我们今天不应该使用它。

.myInput {
  color: blue;
  caret-color: red;
}

请注意,我们将其直接放在 color 旁边,该属性具有几乎所有浏览器都支持的通用支持; 它将在所有地方应用。 在这种情况下,我们没有明确区分现代浏览器和旧浏览器。 相反,我们只是依赖于较旧的浏览器忽略它们不支持的功能。

事实证明,这种模式在绝大多数情况下都足够强大。

何时需要特征检测

但是,在某些情况下,我们确实希望使用现代属性或属性值,其用法与回退有很大差异。 在这些情况下,@supports 可以提供帮助。

@supports 是一种特殊的 at-rule,允许我们有条件地在支持特定属性及其值的浏览器中应用任何样式。

@supports (display: grid) {
  /* Styles for browsers that support grid layout... */
}

它类似于 @media 查询,后者也只在满足特定谓词时才有条件地应用样式。

为了说明 @supports 的用法,请考虑以下示例:我们希望将用户上传的头像显示在一个漂亮的圆圈中,但我们无法保证实际文件将是正方形尺寸。 为了做到这一点,object-fit 属性将非常有用; 但是,它不受 Internet Explorer (IE) 的支持。 然后我们该怎么办?

让我们从标记开始

<div class="avatar">
  <img class="avatar-image" src="..." alt="..." />
</div>

作为一个不太漂亮的回退,我们将压缩头像中图像的宽度,代价是更宽的文件将不会完全覆盖头像区域。 相反,我们的单色背景将出现在下面。

.avatar {
  position: relative;
  width: 5em;
  height: 5em;
  border-radius: 50%;
  overflow: hidden;
  background: #cccccc; /* Fallback color */
}

.avatar-image {
  position: absolute;
  top: 50%;
  right: 0;
  bottom: 0;
  left: 50%;
  transform: translate(-50%, -50%);
  max-width: 100%;
}

您可以在此处看到此行为的实际效果

查看 CodePen 上 Jirka Vebr (@JirkaVebr) 的 object-fit 的回退演示

请注意,有一个正方形图像、一个宽图像和一个高图像。

现在,如果我们使用 object-fit,我们可以让浏览器决定放置图像的最佳方式,即拉伸宽度、高度或两者都不拉伸。

@supports (object-fit: cover) {
  .avatar-image {
    /* We no longer need absolute positioning or any transforms */
    position: static;
    transform: none;
    object-fit: cover;
    width: 100%;
    height: 100%;
  }
}

对于同一组图像尺寸,该结果在现代浏览器中效果很好

查看 CodePen 上 Jirka Vebr (@JirkaVebr) 的 @supports object-fit 演示

条件选择器支持

尽管 Selectors Level 4 规范仍然是工作草案,但它定义的一些选择器(例如 :placeholder-shown)已得到 许多浏览器支持。 如果这种趋势持续下去(并且草案保留了其当前大多数提案),那么此级别的规范将引入 更多新选择器,超过其所有以前版本。 与此同时,以及 IE 仍然存在的时候,CSS 开发人员将不得不针对更广泛且更不稳定的浏览器范围,这些浏览器对这些选择器的支持刚刚开始。

对选择器执行特征检测将非常有用。 不幸的是,@supports 仅设计用于测试属性及其值的支持,甚至其规范的 最新草案 似乎也没有改变这一点。 然而,自从它诞生以来,它就在其语法中定义了一个 特殊生产规则,其唯一目的是为潜在的向后兼容扩展提供空间,因此将来版本的规范完全可以添加对特定选择器支持进行条件判断的功能。 然而,这种可能性仍然完全是假设的。

@supports 的选择器对应部分

首先,重要的是要强调,类似于前面提到的 caret-color 示例,其中可能不需要 @supports,许多选择器也不需要明确测试。 例如,我们可以简单地尝试匹配 ::selection,而不必担心不支持它的浏览器,因为如果选择的外观保持浏览器默认值,也不会是世界末日。

然而,在某些情况下,明确地对选择器进行特征检测将非常可取。 在本文的其余部分,我们将介绍一种模式来解决这些需求,然后将其与 :placeholder-shown 一起使用来构建一个仅使用 CSS 的 Material Design 带浮动标签的文本字段 的替代方案。

选择器的基本属性组

为了避免重复,可以将几个相同的声明压缩成一个用逗号分隔的选择器列表,这称为选择器组。

因此我们可以将

.foo { color: red }
.bar { color: red }

…转换为

.foo, .bar { color: red }

但是,正如 Selectors Level 3 规范 警告的那样,这些只是等效的,因为所有涉及的选择器都是有效的。 根据规范,如果选择器组中的任何一个选择器无效,则 整个组将被忽略。 因此,选择器

..foo { color: red } /* Note the extra dot */
.bar { color: red }

…不能安全地分组,因为前一个选择器无效。 如果我们将它们分组,我们将导致浏览器忽略后者的声明。

值得指出的是,就浏览器而言,无效的选择器与根据规范的较新版本才有效的选择器或浏览器不认识的选择器之间没有区别。 对浏览器来说,两者都只是无效的。

我们可以利用此属性来测试对特定选择器的支持。 我们只需要一个我们能保证不匹配任何内容的选择器。 在我们的示例中,我们将使用 :not(*)

.foo { color: red }

:not(*):placeholder-shown,
.foo {
  color: green
}

让我们分解一下这里发生了什么。 较旧的浏览器将成功应用第一条规则,但在处理其余规则时,它会发现该组中的第一个选择器无效,因为它不知道 :placeholder-shown,因此它将忽略整个选择器组。 因此,所有匹配 .foo 的元素将保持 red。 相反,虽然较新的浏览器可能会在遇到 :not(*)(它从不匹配任何内容)时翻白眼,但它不会丢弃整个选择器组。 相反,它将覆盖之前的规则,因此所有匹配 .foo 的元素将变为 green

请注意它在使用方式上与 @supports(或任何 @media 查询)的相似之处。 我们首先指定回退,然后为满足谓词的浏览器覆盖它,在这种情况下,谓词是对特定选择器的支持——尽管是用一种有点复杂的方式写成的。

查看 CodePen 上 Jirka Vebr (@JirkaVebr) 的 @supports 用于选择器

真实世界的例子

我们可以将此技术用于带浮动标签的输入,以区分支持 :placeholder-shown 的浏览器和不支持 :placeholder-shown 的浏览器,这是一个对该示例绝对至关重要的伪类。 为了相对简单起见,尽管遵循最佳 UI 实践,我们还是会选择回退仅为实际占位符。

让我们从标记开始

<div class="input">
  <input class="input-control" type="email" name="email" placeholder="Email" id="email" required />
  <label class="input-label" for="email">Email</label>
</div>

如前所述,关键是首先为旧版浏览器添加样式。 我们隐藏标签并设置占位符的颜色。

.input {
  height: 3.2em;
  position: relative;
  display: flex;
  align-items: center;
  font-size: 1em;
}

.input-control {
  flex: 1;
  z-index: 2; /* So that it is always "above" the label */
  border: none;
  padding: 0 0 0 1em;
  background: transparent;
  position: relative;
}

.input-label {
  position: absolute;
  top: 50%;
  right: 0;
  bottom: 0;
  left: 1em; /* Align this with the control's padding */
  z-index: 1;
  display: none; /* Hide this for old browsers */
  transform-origin: top left;
  text-align: left;
}

对于现代浏览器,我们可以通过将占位符的 color 设置为 transparent 来有效地禁用它。 我们还可以根据 占位符是否显示 来对齐 inputlabel 的相对位置。 为此,我们还可以使用同级选择器来根据 input 的状态为 label 设置样式。

.input-control:placeholder-shown::placeholder {
  color: transparent;
}

.input-control:placeholder-shown ~ .input-label {
  transform: translateY(-50%)
}

.input-control:placeholder-shown {
  transform: translateY(0);
}

最后,诀窍! 正如上面一样,我们覆盖了现代浏览器中 labelinput 的样式以及 占位符未显示 的状态。 这包括将 label 移开并稍微缩小它。

:not(*):placeholder-shown,
.input-label {
  display: block;
  transform: translateY(-70%) scale(.7);

}
:not(*):placeholder-shown,
.input-control {
  transform: translateY(35%);
}

将所有部分放在一起,以及与该示例正交的更多样式和配置选项,您可以查看完整的演示

查看 CodePen 上 Jirka Vebr 的作品 CSS-only @supports for selectors demo (@JirkaVebr),发布在 CodePen 上。

此技术的可靠性和局限性

从根本上说,此技术需要一个不匹配任何内容的选择器。为此,我们一直在使用 :not(*);但是,它的支持也很有限。通配符选择器 * 即使是 IE 7 也支持,而 :not 伪类直到 IE 9 才实现,因此这是此方法起作用的最古老的浏览器。较旧的浏览器会由于错误的原因拒绝我们的选择器组——它们不支持 :not!或者,我们可以使用类选择器,例如 .foo,或类型选择器,例如 foo,从而支持最古老的浏览器。然而,这些会使代码的可读性降低,因为它们没有传达它们永远不应该匹配任何内容的信息,因此对于大多数现代网站来说,:not(*) 似乎是最佳选择。

至于我们一直在利用的选择器组的属性在较旧的浏览器中是否也成立,其行为在 CSS 1 部分关于前向兼容解析的示例中得到了 说明。此外,CSS 2.1 规范 明确规定 此行为。为了将此规范的年代放在背景中,这是引入了 :hover 的规范。简而言之,虽然此技术尚未在最古老或最模糊的浏览器中进行广泛测试,但它的支持范围应该非常广泛。

最后,对于 Sass 用户(Sass,不是 SCSS)有一个小问题:遇到 :not(*):placeholder-shown 选择器后,编译器会被引导冒号迷惑,尝试将其解析为属性,并在遇到错误时建议开发者将选择器转义为 \:not(*):placeholder-shown,这看起来不太美观。一个更好的解决方法可能是用另一个通配符选择器替换反斜杠以获得 *:not(*):placeholder-shown,因为根据 规范,在这种情况下,它本来就是隐含的。