建構跨裝置 webapps 的非回應式方法

Boris Smus
Boris Smus

媒體查詢很棒,但…

媒體查詢非常實用,網站開發人員只要對樣式表進行小幅調整,就能在各種尺寸的裝置上為使用者提供更優質的體驗。媒體查詢基本上可讓您根據螢幕大小自訂網站的 CSS。在深入瞭解本文內容之前,請先參閱回應式設計,並查看 mediaqueri.es 上的媒體查詢使用範例。

正如 Brad Frost 在先前的文章中指出,為行動版網站建構內容時,變更外觀只是眾多考量因素之一。如果您在建構行動版網站時,唯一做的就是使用媒體查詢自訂版面配置,就會發生下列情況:

  • 所有裝置都會取得相同的 JavaScript、CSS 和素材資源 (圖片、影片),導致載入時間比必要時間長。
  • 所有裝置都會取得相同的初始 DOM,這可能會迫使開發人員編寫過於複雜的 CSS。
  • 無法靈活指定專為各裝置量身打造的自訂互動。

Webapp 需要的不只是媒體查詢

別誤會我的意思,我並不討厭透過媒體查詢進行回應式設計,而且絕對認為這項技術有其用武之地。此外,上述部分問題可透過回應式圖片、動態指令碼載入等方法解決。不過,當您發現自己進行太多增量調整時,可能就比較適合提供不同版本。

隨著您建構的 UI 複雜度增加,並傾向於使用單頁網頁應用程式,您會希望針對每種裝置類型進一步自訂 UI。本文將說明如何輕鬆完成這些自訂作業。一般做法是將訪客的裝置分類到正確的裝置類別,並向該裝置提供適當的版本,同時盡可能在各版本之間重複使用程式碼。

您指定哪些裝置類別?

目前有大量連網裝置,幾乎所有裝置都有瀏覽器。複雜之處在於裝置的多樣性:Mac 筆記型電腦、Windows 工作站、iPhone、iPad、支援觸控輸入的 Android 手機、捲動滾輪、鍵盤、語音輸入、支援壓力感應的裝置、智慧手錶、烤麵包機和冰箱等等。有些裝置隨處可見,有些則非常罕見。

各種裝置。
多種裝置 (來源)。

如要打造良好的使用者體驗,您必須瞭解使用者是誰,以及他們使用的裝置。如果您為使用滑鼠和鍵盤的桌機使用者建構使用者介面,並提供給智慧型手機使用者,由於介面是為其他螢幕大小和輸入模式設計,因此使用者會感到挫敗。

方法光譜的兩端分別是:

  1. 只要建構一個版本,即可在所有裝置上運作。不同裝置有不同的設計考量,因此使用者體驗會受到影響。

  2. 為要支援的每部裝置建構版本。這會耗費很長的時間,因為您要建構太多應用程式版本。此外,每週都會推出新手機,因此您必須建立另一個版本。

這裡有基本取捨:裝置類別越多,使用者體驗越好,但設計、實作和維護工作也越多。

基於效能考量,或是想為不同裝置類別提供差異極大的版本,建議為每個裝置類別建立個別版本。否則,回應式網頁設計也是非常合理的做法。

可能的解決方案

折衷做法是將裝置分類,並為每個類別設計最佳體驗。選擇的類別取決於產品和目標使用者。以下是分類範例,涵蓋目前市面上常見的網路裝置。

  1. 小螢幕 + 觸控 (多為手機)
  2. 大螢幕 + 觸控 (多為平板電腦)
  3. 大螢幕 + 鍵盤/滑鼠 (通常是桌機/筆電)

這只是其中一種可能的細分方式,但以撰寫本文時的情況來看,這種方式相當合理。上述清單未列出沒有觸控螢幕的行動裝置 (例如功能手機、部分專用電子書閱讀器)。不過,這些使用者大多已安裝鍵盤導覽或螢幕閱讀器軟體,只要您在建構網站時考慮到無障礙功能,這些軟體就能正常運作。

特定板型規格的網頁應用程式範例

許多網站資源會針對不同板型規格提供完全不同的版本。Google 搜尋和 Facebook 都會這麼做。這包括效能 (擷取資產、算繪網頁) 和更一般的使用者體驗。

在原生應用程式的世界中,許多開發人員選擇根據裝置類別調整體驗。舉例來說,iPad 版 Flipboard 的 UI 與 iPhone 版 Flipboard 大不相同。平板電腦版專為雙手使用和水平翻轉設計,手機版則適合單手操作和垂直翻轉。許多其他 iOS 應用程式也提供手機和平板電腦版本,兩者差異顯著,例如待辦事項Showyou (社群影片),如下所示:

手機和平板電腦可大幅自訂使用者介面。
針對手機和平板電腦進行大幅 UI 自訂。

方法 1:伺服器端偵測

在伺服器上,我們對所處理的裝置瞭解程度有限。最實用的線索可能是使用者代理程式字串,這會透過每個要求的 User-Agent 標頭提供。因此,這裡也適用相同的 UA 偵測方法。事實上,DeviceAtlas 和 WURFL 專案已執行這項操作 (並提供裝置的許多額外資訊)。

但這些方法各有缺點。WURFL 非常龐大,包含 20 MB 的 XML,每個要求都可能造成顯著的伺服器端負荷。有些專案會基於效能考量分割 XML。DeviceAtlas 並非開放原始碼,使用時需要付費授權。

此外,您也可以使用簡單的免費替代方案,例如「偵測行動瀏覽器」專案。當然,缺點是裝置偵測的全面性難免會降低。此外,這項功能只能區分行動裝置和非行動裝置,僅透過一組臨時調整提供有限的平板電腦支援。

方法 2:用戶端偵測

我們可以透過功能偵測,深入瞭解使用者的瀏覽器和裝置。我們主要需要判斷裝置是否支援觸控功能,以及螢幕大小。

我們需要劃出界線,區分小型和大型觸控裝置。如果裝置是 5 吋的 Galaxy Note 等極端案例,下圖顯示許多熱門 Android 和 iOS 裝置的重疊畫面 (以及對應的螢幕解析度)。星號表示裝置的密度是雙倍或可為雙倍。雖然像素密度可能會加倍,但 CSS 仍會回報相同大小。

先簡單介紹一下 CSS 中的像素:行動版網頁上的 CSS 像素與螢幕像素不同。iOS Retina 裝置引進了像素密度加倍的做法 (例如 iPhone 3GS 與 4、iPad 2 與 3)。為避免網頁中斷,視網膜 Mobile Safari UA 仍會回報相同的裝置寬度。與其他裝置 (例如Android) 取得更高解析度的螢幕,他們也是使用相同的裝置寬度技巧。

裝置解析度 (以像素為單位)。
裝置解析度 (以像素為單位)。

不過,這項決策的複雜之處在於,您必須同時考量直向和橫向模式。我們不希望每次重新調整裝置方向時,都重新載入網頁或載入額外指令碼,但可能希望以不同方式算繪網頁。

在下圖中,正方形代表每個裝置的最大尺寸,這是因為重疊直向和橫向輪廓 (並完成正方形) 所致:

直向和橫向解析度 (以像素為單位)
直向和橫向解析度 (以像素為單位)

將門檻設為 650px 後,我們就會將 iPhone 和 Galaxy Nexus 分類為「小型觸控裝置」,iPad 和 Galaxy Tab 則分類為「平板電腦」。中性 Galaxy Note 在這種情況下會歸類為「手機」,並採用手機版面配置。

因此,合理的策略可能如下所示:

if (hasTouch) {
  if (isSmall) {
    device = PHONE;
  } else {
    device = TABLET;
  }
} else {
  device = DESKTOP;
}

請參閱功能偵測方法的實際運作範例。

另一種做法是使用 UA 探查功能偵測裝置類型。基本上,您會建立一組啟發式方法,並與使用者的navigator.userAgent進行比對。虛擬程式碼如下所示:

var ua = navigator.userAgent;
for (var re in RULES) {
  if (ua.match(re)) {
    device = RULES[re];
    return;
  }
}

請參閱這個範例,瞭解如何實際運用 UA 偵測方法。

用戶端載入注意事項

如果您要在伺服器上進行 UA 偵測,可以決定在收到新要求時,要提供哪些 CSS、JavaScript 和 DOM。不過,如果您是進行用戶端偵測,情況會比較複雜。你可以選擇下列幾種做法:

  1. 重新導向至含有此裝置類型版本的裝置類型專屬網址。
  2. 動態載入特定裝置類型的資產。

第一種方法很簡單,只需要重新導向,例如 window.location.href = '/tablet'。不過,現在位置資訊會附加這類裝置類型資訊,因此您可能需要使用 History API 清理網址。很抱歉,這種做法會涉及重新導向,速度可能較慢,尤其是在行動裝置上。

第二種方法實作起來相當複雜,您需要動態載入 CSS 和 JS 的機制,而且 (視瀏覽器而定) 可能無法自訂 <meta viewport> 等項目。此外,由於沒有重新導向,您會停留在原始的 HTML 服務。當然,您可以使用 JavaScript 操作,但視應用程式而定,這可能很慢和/或不雅觀。

決定用戶端或伺服器

這兩種方法的優缺點如下:

專業版用戶端

  • 根據螢幕大小/功能而非 UA 進行調整,因此更具前瞻性。
  • 不必持續更新 UA 清單。

專業伺服器

  • 完整控管要向哪些裝置提供哪個版本。
  • 成效更佳:不需要用戶端重新導向或動態載入。

我個人偏好從 device.js 和用戶端偵測開始。隨著應用程式演進,如果您發現用戶端重新導向會大幅降低效能,可以輕鬆移除 device.js 指令碼,並在伺服器上實作 UA 偵測。

device.js 簡介

Device.js 是以語意和媒體查詢為基礎的裝置偵測起點,不需要特別設定伺服器端,可節省剖析使用者代理程式字串所需的時間和精力。

做法是在 <head> 頂端提供搜尋引擎友善的標記 (link rel=alternate),指出要提供的網站版本。

<link rel="alternate" href="http://foo.com" id="desktop"
    media="only screen and (touch-enabled: 0)">

接下來,您可以自行進行伺服器端 UA 偵測並處理版本重新導向,也可以使用 device.js 指令碼進行以功能為準的用戶端重新導向。

詳情請參閱 device.js 專案頁面,以及使用 device.js 進行用戶端重新導向的虛擬應用程式

建議:MVC,並搭配特定板型規格的檢視畫面

到目前為止,您可能認為我建議您為每種裝置類型建構三個完全不同的應用程式。可惡!共用代碼是關鍵。

希望您一直使用類似 MVC 的架構,例如 Backbone、Ember 等。如果您一直使用這類架構,應該很熟悉關注點分離原則,也就是 UI (檢視層) 應與邏輯 (模型層) 分離。如果您是第一次使用,請先參閱這些 MVC 資源JavaScript 中的 MVC

跨裝置故事可完美融入現有的 MVC 架構。您可以輕鬆將檢視區塊移至個別檔案,為每種裝置類型建立自訂檢視區塊。然後,您可以將相同的程式碼提供給所有裝置,但檢視層除外。

跨裝置 MVC。
跨裝置 MVC。

您的專案可能具有下列結構 (當然,您可以根據應用程式選擇最合適的結構):

models/ (共用模型) item.js item-collection.js

controllers/ (共用控制器) item-controller.js

versions/ (裝置專屬內容) tablet/ desktop/ phone/ (手機專屬程式碼) style.css index.html views/ item.js item-list.js

由於您為每部裝置提供自訂 HTML、CSS 和 JavaScript,因此可以完全控管每個版本載入的素材資源。這項功能非常強大,可讓您以最精簡、效能最高的方式開發跨裝置網頁,不必依賴自動調整圖片等技巧。

執行您慣用的建構工具後,所有 JavaScript 和 CSS 都會串連並壓縮成單一檔案,以加快載入速度,而正式版 HTML 則會類似下列內容 (適用於手機,使用 device.js):

<!doctype html>
<head>
  <title>Mobile Web Rocks! (Phone Edition)</title>

  <!-- Every version of your webapp should include a list of all
        versions. -->
  <link rel="alternate" href="http://foo.com" id="desktop"
      media="only screen and (touch-enabled: 0)">
  <link rel="alternate" href="http://m.foo.com" id="phone"
      media="only screen and (max-device-width: 650px)">
  <link rel="alternate" href="http://tablet.foo.com" id="tablet"
      media="only screen and (min-device-width: 650px)">

  <!-- Viewport is very important, since it affects results of media
        query matching. -->
  <meta name="viewport" content="width=device-width">

  <!-- Include device.js in each version for redirection. -->
  <script src="device.js"></script>

  <link rel="style" href="phone.min.css">
</head>
<body>
  <script src="phone.min.js"></script>
</body>

請注意,(touch-enabled: 0) 媒體查詢並非標準查詢 (僅在 Firefox 中實作,且位於 moz 供應商前置字元後方),但 device.js 會正確處理這項查詢 (這要歸功於 Modernizr.touch)。

版本覆寫

裝置偵測有時會出錯,而且在某些情況下,使用者可能偏好在手機上查看平板電腦版面配置 (例如使用 Galaxy Note),因此請務必讓使用者選擇要使用的網站版本 (如果他們想手動覆寫)。

一般做法是從行動版提供電腦版連結。這項功能很容易實作,但 device.js 支援使用 device GET 參數。

結尾

總而言之,如果建構的跨裝置單頁面 UI 無法完全符合回應式設計,請採取下列做法:

  1. 挑選要支援的裝置類別組合,以及將裝置分類的條件。
  2. 建構 MVC 應用程式時,請將檢視區塊與其餘程式碼集分開,以明確區分關注事項。
  3. 使用 device.js 進行用戶端裝置類別偵測。
  4. 準備就緒後,請將指令碼和樣式表封裝到每個裝置類別中。
  5. 如果用戶端重新導向效能有問題,請捨棄 device.js,改用伺服器端 UA 偵測。