实现特性检测
特性检测是指确定浏览器是否支持某段代码,并根据支持与否运行不同的代码,以便浏览器始终提供可用的体验,而不是在某些浏览器中崩溃/报错。本文详细介绍了如何编写自己的简单特性检测,如何使用库来加速实现,以及用于特性检测的本地特性,例如 @supports
。
预备知识 | 熟悉核心的 HTML、CSS 和 JavaScript 语言;对 跨浏览器测试 的高级原则有所了解。 |
---|---|
目标 | 了解特性检测的概念,并能够在 CSS 和 JavaScript 中实现合适的解决方案。 |
特性检测的概念
特性检测背后的思想是,你可以运行一个测试来确定当前浏览器是否支持某个特性,然后有条件地运行代码,以便在**支持**该特性的浏览器和**不支持**该特性的浏览器中都能提供可接受的体验。如果你不这样做,不支持你代码中使用的特性的浏览器可能无法正确显示你的网站,或者完全失败,从而造成糟糕的用户体验。
让我们回顾并查看我们在 JavaScript 调试和错误处理 文章中提及的示例——地理定位 API(它公开了 Web 浏览器运行设备可用的位置数据)的主要入口点是全局 Navigator 对象上可用的 geolocation
属性。因此,你可以通过使用类似以下代码来检测浏览器是否支持地理定位:
if ("geolocation" in navigator) {
navigator.geolocation.getCurrentPosition((position) => {
// show the location on a map, such as the Google Maps API
});
} else {
// Give the user a choice of static maps
}
在我们继续之前,我们想先说明一件事——不要将特性检测与**浏览器嗅探**(检测访问网站的具体浏览器)混淆——这是一种糟糕的做法,应不惜一切代价加以制止。有关更多详细信息,请参阅使用用户代理字符串进行浏览器检测(UA 嗅探)。
编写自己的特性检测测试
在本节中,我们将介绍如何在 CSS 和 JavaScript 中实现自己的特性检测测试。
CSS
你可以通过在 JavaScript 中测试 *element.style.property* 的存在(例如,paragraph.style.rotate
)来编写 CSS 特性的测试。
一个经典的例子是测试浏览器对 子网格(Subgrid) 的支持;对于支持 grid-template-columns
和 grid-template-rows
属性的 subgrid
值的浏览器,我们可以在布局中使用子网格。对于不支持的浏览器,我们可以使用普通的网格,它也能正常工作,但看起来没有那么酷。
以此为例,如果支持该值,我们可以包含一个子网格样式表;如果不支持,则包含一个常规网格样式表。为此,我们可以在 HTML 文件的头部包含两个样式表:一个用于所有样式,另一个用于在不支持子网格时实现默认布局。
<link href="basic-styling.css" rel="stylesheet" />
<link class="conditional" href="grid-layout.css" rel="stylesheet" />
这里,basic-styling.css
处理我们要提供给所有浏览器的所有样式。我们还有两个额外的 CSS 文件,grid-layout.css
和 subgrid-layout.css
,它们包含我们希望根据浏览器的支持级别选择性地应用于浏览器的 CSS。
我们使用 JavaScript 来测试对子网格值的支持,然后根据浏览器支持更新条件样式表的 href
。
我们可以在文档中添加一个 <script></script>
,其中包含以下 JavaScript:
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-rule。它的工作方式与媒体查询类似,但它不是根据分辨率、屏幕宽度或宽高比等媒体特性选择性地应用 CSS,而是根据是否支持某个 CSS 特性选择性地应用 CSS,类似于 CSS.supports()
。
例如,我们可以重写之前的示例,使用 @supports
:
@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-rule 块只有在当前浏览器支持 `grid-template-columns: subgrid;` 声明时才会应用其中的 CSS 规则。要使带有值的条件生效,你需要包含一个完整的声明(而不仅仅是属性名),并且**不**要在末尾包含分号。
@supports
还具有 AND
、OR
和 NOT
逻辑——如果子网格选项不可用,另一个块将应用常规网格布局:
@supports not (grid-template-columns: subgrid) {
/* rules in here */
}
这比之前的示例更方便——我们可以在 CSS 中完成所有特性检测,无需 JavaScript,并且我们可以在一个 CSS 文件中处理所有逻辑,减少 HTTP 请求。因此,这是确定浏览器对 CSS 特性支持的首选方法。
JavaScript
我们之前已经看到了一个 JavaScript 特性检测测试的例子。通常,此类测试通过几种常见模式之一完成。
可检测特性的常见模式包括:
- 对象的成员
-
检查特定方法或属性(通常是使用 API 或你正在检测的其他特性的入口点)是否存在于其父
Object
中。我们之前的示例使用了这种模式,通过测试
navigator
对象中是否存在geolocation
成员来检测 地理定位 支持:jsif ("geolocation" in navigator) { // Access navigator.geolocation APIs }
- 元素的属性
-
使用
Document.createElement()
在内存中创建一个元素,然后检查该元素上是否存在某个属性。此示例展示了检测 Canvas API 支持的一种方法:
jsfunction supports_canvas() { return !!document.createElement("canvas").getContext; } if (supports_canvas()) { // Create and draw on canvas elements }
注意: 上例中的双重
NOT
(!!
) 是一种强制返回值成为“正确”布尔值的方法,而不是可能导致结果偏差的 真值(Truthy)/假值(Falsy)。 - 元素上某个方法的特定返回值
-
使用
Document.createElement()
在内存中创建一个元素,然后检查该元素上是否存在某个方法。如果存在,则检查其返回值。 - 元素保留分配属性值
-
使用
Document.createElement()
在内存中创建一个元素,将一个属性设置为特定值,然后检查该值是否被保留。
请记住,有些特性是无法检测到的。在这些情况下,你需要使用不同的方法,例如使用 polyfill。
matchMedia
在此我们也想提及 Window.matchMedia
JavaScript 特性。这是一个允许你在 JavaScript 内部运行媒体查询测试的属性。它看起来像这样:
if (window.matchMedia("(width <= 480px)").matches) {
// run JavaScript in here.
}
举个例子,我们的 Snapshot 演示就利用了它,有选择地应用 Brick JavaScript 库并用它来处理 UI 布局,但仅限于小屏幕布局(480px 宽或更小)。我们首先使用 media
属性,只有当页面宽度为 480px 或更小时才将 Brick CSS 应用到页面上:
<link href="dist/brick.css" rel="stylesheet" media="(width <= 480px)" />
然后,我们在 JavaScript 中多次使用 matchMedia()
,只有在小屏幕布局下才运行 Brick 导航功能(在更宽的屏幕布局中,所有内容都可以一次性看到,因此我们不需要在不同的视图之间导航)。
if (window.matchMedia("(width <= 480px)").matches) {
deck.shuffleTo(1);
}
总结
本文详细介绍了特性检测,讲解了主要概念并展示了如何实现自己的特性检测测试。
接下来,我们将开始探讨自动化测试。