实现特性检测

特性检测涉及确定浏览器是否支持某个代码块,并根据浏览器是否支持(或不支持)运行不同的代码,以便浏览器始终能够提供正常的工作体验,而不是在某些浏览器中崩溃/出错。本文详细介绍了如何编写自己的简单特性检测、如何使用库来加速实现,以及用于特性检测的原生特性,例如@supports

先决条件 熟悉核心HTMLCSSJavaScript语言;了解跨浏览器测试原则的高级概念。
目标 了解特性检测的概念,并能够在 CSS 和 JavaScript 中实现合适的解决方案。

特性检测的概念

特性检测背后的思想是,您可以运行测试以确定当前浏览器是否支持某个特性,然后有条件地运行代码以在支持该特性的浏览器和不支持该特性的浏览器中提供可接受的体验。如果不这样做,不支持代码中使用的特性的浏览器可能无法正确显示您的网站,或者可能完全失败,从而导致糟糕的用户体验。

让我们回顾一下,并看看我们在处理常见 JavaScript 问题中提到的示例——地理位置 API(它公开了 Web 浏览器正在运行的设备的可用位置数据)将其使用作为主要入口点,其全局Navigator对象上有一个geolocation属性可用。因此,您可以使用如下所示的内容来检测浏览器是否支持地理位置:

js
if ("geolocation" in navigator) {
  navigator.geolocation.getCurrentPosition(function (position) {
    // show the location on a map, such as the Google Maps API
  });
} else {
  // Give the user a choice of static maps
}

在继续之前,我们想预先说明一点——不要将特性检测与浏览器嗅探(检测访问网站的具体浏览器)混淆——这是一种糟糕的做法,应不惜一切代价避免。请参阅不要浏览器嗅探以获取更多详细信息。

编写你自己的特性检测测试

在本节中,我们将探讨如何在 CSS 和 JavaScript 中实现自己的特性检测测试。

CSS

您可以通过测试element.style.property(例如paragraph.style.rotate)在 JavaScript 中是否存在来编写 CSS 特性的测试。

一个经典的例子可能是测试浏览器是否支持Subgrid;对于支持grid-template-columnsgrid-template-rowssubgrid值的浏览器,我们可以在我们的布局中使用 subgrid。对于不支持的浏览器,我们可以使用常规网格,它工作良好,但外观不如酷炫。

以这个为例,如果支持该值,我们可以包含一个 subgrid 样式表,如果不支持,则包含一个常规网格样式表。为此,我们可以在 HTML 文件的头部包含两个样式表:一个用于所有样式,另一个在不支持 subgrid 时实现默认布局。

html
<link href="basic-styling.css" rel="stylesheet" />
<link class="conditional" href="grid-layout.css" rel="stylesheet" />

这里,basic-styling.css处理我们想要赋予每个浏览器的所有样式。我们还有两个额外的 CSS 文件,grid-layout.csssubgrid-layout.css,其中包含我们想要根据浏览器的支持级别有选择地应用于浏览器的 CSS。

我们使用 JavaScript 测试对 subgrid 值的支持,然后根据浏览器支持更新条件样式表的href

我们可以向我们的文档添加一个<script></script>,其中包含以下 JavaScript 代码

js
const conditional = document.querySelector(".conditional");
if (CSS.supports("grid-template-columns", "subgrid")) {
  conditional.setAttribute("href", "subgrid-layout.css");
}

在我们的条件语句中,我们使用CSS.supports()测试grid-template-columns属性是否支持subgrid值。

@supports

CSS 具有原生特性检测机制:@supports at-规则。它的工作方式类似于媒体查询,只不过它不是根据分辨率、屏幕宽度或纵横比等媒体特性有选择地应用 CSS,而是根据 CSS 特性是否受支持有选择地应用 CSS,类似于CSS.supports()

例如,我们可以重写前面的示例以使用@supports

css
@supports (grid-template-columns: subgrid) {
  main {
    display: grid;
    grid-template-columns: repeat(9, 1fr);
    grid-template-rows: repeat(4, minmax(100px, auto));
  }

  .item {
    display: grid;
    grid-column: 2 / 7;
    grid-row: 2 / 4;
    grid-template-columns: subgrid;
    grid-template-rows: repeat(3, 80px);
  }

  .subitem {
    grid-column: 3 / 6;
    grid-row: 1 / 3;
  }
}

此 at-规则块仅在当前浏览器支持grid-template-columns: subgrid;声明时才应用其中的 CSS 规则。要使带值的条件起作用,您需要包含完整的声明(不仅仅是属性名称)并且不要在末尾包含分号。

@supports还提供了ANDORNOT逻辑——如果 subgrid 选项不可用,则另一个块将应用常规网格布局。

css
@supports not (grid-template-columns: subgrid) {
  /* rules in here */
}

这比前面的示例更方便——我们可以在 CSS 中完成所有特性检测,无需 JavaScript,并且可以在单个 CSS 文件中处理所有逻辑,从而减少 HTTP 请求次数。因此,它是确定浏览器对 CSS 特性支持的首选方法。

JavaScript

我们之前已经看到了 JavaScript 特性检测测试的示例。通常,此类测试通过几种常见模式之一完成。

可检测特性的常见模式包括

对象的成员

检查其父Object中是否存在特定方法或属性(通常是使用您正在检测的 API 或其他特性的入口点)。

我们之前的示例使用此模式通过测试navigator对象是否存在geolocation成员来检测地理位置支持。

js
if ("geolocation" in navigator) {
  // Access navigator.geolocation APIs
}
元素的属性

使用Document.createElement()在内存中创建元素,然后检查其上是否存在属性。

此示例显示了一种检测Canvas API支持的方法。

js
function supports_canvas() {
  return !!document.createElement("canvas").getContext;
}

if (supports_canvas()) {
  // Create and draw on canvas elements
}

注意:以上示例中的双NOT!!)是一种强制返回值成为“正确”布尔值的方法,而不是可能歪曲结果的真值/假值

元素上方法的特定返回值

使用Document.createElement()在内存中创建元素,然后检查其上是否存在方法。如果存在,请检查它返回的值。有关此模式的示例,请参阅深入了解 HTML 视频格式检测中的特性测试。

元素保留分配的属性值

使用Document.createElement()在内存中创建元素,将属性设置为特定值,然后检查该值是否保留。有关此模式的示例,请参阅深入了解 HTML <input> 类型检测中的特性测试。

请记住,但是,已知某些特性是无法检测的。在这些情况下,您需要使用不同的方法,例如使用polyfill

matchMedia

我们还想在此处提及Window.matchMedia JavaScript 特性。这是一个允许您在 JavaScript 中运行媒体查询测试的属性。它看起来像这样

js
if (window.matchMedia("(max-width: 480px)").matches) {
  // run JavaScript in here.
}

例如,我们的Snapshot演示利用它有选择地应用 Brick JavaScript 库并使用它来处理 UI 布局,但仅适用于小屏幕布局(480px 宽或更小)。我们首先使用media属性仅在页面宽度为 480px 或更小时将 Brick CSS 应用于页面。

html
<link
  href="dist/brick.css"
  rel="stylesheet"
  media="all and (max-width: 480px)" />

然后,我们在 JavaScript 中多次使用matchMedia(),仅在小屏幕布局上运行 Brick 导航功能(在较宽的屏幕布局中,所有内容都可以一次看到,因此我们不需要在不同的视图之间导航)。

js
if (window.matchMedia("(max-width: 480px)").matches) {
  deck.shuffleTo(1);
}

总结

本文以合理的详细程度介绍了特性检测,介绍了主要概念并向您展示了如何实现自己的特性检测测试。

接下来,我们将开始探讨自动化测试。