實現特性檢測
特性檢測是指確定瀏覽器是否支援某段程式碼,並根據支援與否執行不同的程式碼,以便瀏覽器始終提供可用的體驗,而不是在某些瀏覽器中崩潰/報錯。本文詳細介紹瞭如何編寫自己的簡單特性檢測,如何使用庫來加速實現,以及用於特性檢測的本地特性,例如 @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);
}
總結
本文詳細介紹了特性檢測,講解了主要概念並展示瞭如何實現自己的特性檢測測試。
接下來,我們將開始探討自動化測試。