實現特性檢測

特性檢測是指確定瀏覽器是否支援某段程式碼,並根據支援與否執行不同的程式碼,以便瀏覽器始終提供可用的體驗,而不是在某些瀏覽器中崩潰/報錯。本文詳細介紹瞭如何編寫自己的簡單特性檢測,如何使用庫來加速實現,以及用於特性檢測的本地特性,例如 @supports

預備知識 熟悉核心的 HTMLCSSJavaScript 語言;對 跨瀏覽器測試 的高階原則有所瞭解。
目標 瞭解特性檢測的概念,並能夠在 CSS 和 JavaScript 中實現合適的解決方案。

特性檢測的概念

特性檢測背後的思想是,你可以執行一個測試來確定當前瀏覽器是否支援某個特性,然後有條件地執行程式碼,以便在**支援**該特性的瀏覽器和**不支援**該特性的瀏覽器中都能提供可接受的體驗。如果你不這樣做,不支援你程式碼中使用的特性的瀏覽器可能無法正確顯示你的網站,或者完全失敗,從而造成糟糕的使用者體驗。

讓我們回顧並檢視我們在 JavaScript 除錯和錯誤處理 文章中提及的示例——地理定位 API(它公開了 Web 瀏覽器執行裝置可用的位置資料)的主要入口點是全域性 Navigator 物件上可用的 geolocation 屬性。因此,你可以透過使用類似以下程式碼來檢測瀏覽器是否支援地理定位:

js
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-columnsgrid-template-rows 屬性的 subgrid 值的瀏覽器,我們可以在佈局中使用子網格。對於不支援的瀏覽器,我們可以使用普通的網格,它也能正常工作,但看起來沒有那麼酷。

以此為例,如果支援該值,我們可以包含一個子網格樣式表;如果不支援,則包含一個常規網格樣式表。為此,我們可以在 HTML 檔案的頭部包含兩個樣式表:一個用於所有樣式,另一個用於在不支援子網格時實現預設佈局。

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 來測試對子網格值的支援,然後根據瀏覽器支援更新條件樣式表的 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-rule。它的工作方式與媒體查詢類似,但它不是根據解析度、螢幕寬度或寬高比等媒體特性選擇性地應用 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-rule 塊只有在當前瀏覽器支援 `grid-template-columns: subgrid;` 宣告時才會應用其中的 CSS 規則。要使帶有值的條件生效,你需要包含一個完整的宣告(而不僅僅是屬性名),並且**不**要在末尾包含分號。

@supports 還具有 ANDORNOT 邏輯——如果子網格選項不可用,另一個塊將應用常規網格佈局:

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

這比之前的示例更方便——我們可以在 CSS 中完成所有特性檢測,無需 JavaScript,並且我們可以在一個 CSS 檔案中處理所有邏輯,減少 HTTP 請求。因此,這是確定瀏覽器對 CSS 特性支援的首選方法。

JavaScript

我們之前已經看到了一個 JavaScript 特性檢測測試的例子。通常,此類測試通過幾種常見模式之一完成。

可檢測特性的常見模式包括:

物件的成員

檢查特定方法或屬性(通常是使用 API 或你正在檢測的其他特性的入口點)是否存在於其父 Object 中。

我們之前的示例使用了這種模式,透過測試 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 (!!) 是一種強制返回值成為“正確”布林值的方法,而不是可能導致結果偏差的 真值(Truthy)/假值(Falsy)

元素上某個方法的特定返回值

使用 Document.createElement() 在記憶體中建立一個元素,然後檢查該元素上是否存在某個方法。如果存在,則檢查其返回值。

元素保留分配屬性值

使用 Document.createElement() 在記憶體中建立一個元素,將一個屬性設定為特定值,然後檢查該值是否被保留。

請記住,有些特性是無法檢測到的。在這些情況下,你需要使用不同的方法,例如使用 polyfill

matchMedia

在此我們也想提及 Window.matchMedia JavaScript 特性。這是一個允許你在 JavaScript 內部執行媒體查詢測試的屬性。它看起來像這樣:

js
if (window.matchMedia("(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="(width <= 480px)" />

然後,我們在 JavaScript 中多次使用 matchMedia(),只有在小屏幕布局下才執行 Brick 導航功能(在更寬的屏幕布局中,所有內容都可以一次性看到,因此我們不需要在不同的檢視之間導航)。

js
if (window.matchMedia("(width <= 480px)").matches) {
  deck.shuffleTo(1);
}

總結

本文詳細介紹了特性檢測,講解了主要概念並展示瞭如何實現自己的特性檢測測試。

接下來,我們將開始探討自動化測試。