媒體查詢很棒,但…
媒體查詢功能非常實用,是網站開發人員的救星,因為他們可以對樣式表進行小幅調整,為使用者提供更優質的體驗,無論使用者使用的是何種尺寸的裝置都沒問題。媒體查詢基本上可讓您根據螢幕大小自訂網站的 CSS。在深入瞭解本文之前,請先進一步瞭解回應式設計,並查看 mediaqueri.es 中的一些媒體查詢使用範例。
如同 Brad Frost 在先前的文章中指出,變更外觀只是為行動網站建構時要考量的眾多因素之一。如果您在建構行動網站時,只使用媒體查詢自訂版面配置,就會發生以下情況:
- 所有裝置都會取得相同的 JavaScript、CSS 和素材資源 (圖片、影片),導致載入時間過長。
- 所有裝置都會取得相同的初始 DOM,可能會迫使開發人員編寫過於複雜的 CSS。
- 無法靈活指定針對每部裝置量身打造的自訂互動。
除了媒體查詢之外,還需要其他元素才能打造出色的 webapp
請別誤會,我並不討厭透過媒體查詢進行的回應式設計,而且肯定認為這在現實世界中大有用武之處。此外,您可以使用回應式圖片、動態指令碼載入等方法解決上述部分問題。不過,在某個時間點,您可能會發現自己進行了太多漸進式調整,因此建議您提供不同的版本。
隨著您建立的 UI 複雜度增加,並且傾向於單頁面 webapps,您可能會想進一步為各類型裝置自訂 UI。本文將說明如何以最少的工作量完成這些自訂作業。一般做法是將訪客的裝置歸類為正確的裝置類別,並為該裝置提供適當的版本,同時盡可能在各版本之間重複使用程式碼。
您指定的是哪些裝置類別?
市面上有許多可連上網際網路的裝置,幾乎所有裝置都內建瀏覽器。複雜性在於這些裝置的多樣性:Mac 筆記型電腦、Windows 工作站、iPhone、iPad、Android 手機 (搭配觸控輸入、捲動輪、鍵盤、語音輸入)、具備壓力感應功能的裝置、智慧手錶、烤麵包機和冰箱等等。其中有些裝置很常見,有些則十分罕見。
如要打造良好的使用者體驗,您必須瞭解使用者是誰,以及他們使用的是哪些裝置。如果您為使用滑鼠和鍵盤的桌上型電腦使用者建構使用者介面,並將其提供給智慧型手機使用者,由於介面是針對另一個螢幕大小和另一種輸入模式設計,使用者會感到不便。
這兩種極端的做法分別是:
請建構一個適用於所有裝置的版本。由於不同裝置的設計考量不同,因此使用者體驗會受到影響。
為每個要支援的裝置建立版本。這會耗費很長的時間,因為您會建構太多應用程式版本。此外,當下一個新款智慧型手機推出時 (大約每週一次),您必須建立另一個版本。
這裡有一個基本權衡:裝置類別越多,您提供的使用者體驗就越好,但設計、實作和維護的工作量也會隨之增加。
為每個決定的裝置類別建立個別版本,可能會因為效能因素或您要為不同裝置類別提供的版本差異甚大而成為明智之舉。否則,回應式網頁設計是相當合理的做法。
可能的解決方案
以下提供折衷做法:將裝置分類,並為每個類別設計最佳體驗。您選擇的類別取決於產品和目標使用者。以下是涵蓋目前常見網路裝置的類別分類範例。
- 小螢幕 + 觸控 (大多是手機)
- 大螢幕 + 觸控 (多為平板電腦)
- 大螢幕 + 鍵盤/滑鼠 (大多是桌上型電腦/筆記型電腦)
這只是眾多可能的細分項目之一,但在撰寫本文時,這項細分項目非常有用。上述清單中未列出的行動裝置,是指沒有觸控螢幕的裝置 (例如功能型手機、部分專用電子書閱讀器)。不過,大多數的裝置都已安裝鍵盤導覽或螢幕閱讀器軟體,只要您在建構網站時考量無障礙功能,這些裝置就能正常運作。
特定板型規格的網頁應用程式範例
許多網站資源都會為不同板型規格提供完全不同的版本。Google 搜尋和 Facebook 都會這麼做。這項考量包括效能 (擷取素材資源、算繪網頁) 和更一般性的使用者體驗。
在原生應用程式的世界中,許多開發人員會選擇根據裝置類別調整使用者體驗。舉例來說,iPad 版 Flipboard 的 UI 與 iPhone 版的 Flipboard 截然不同。平板電腦版本經過最佳化處理,可用於雙手操作和水平翻轉,而手機版本則可用於單手互動和垂直翻轉。許多其他 iOS 應用程式也提供截然不同的手機和平板電腦版本,例如 Things (待辦事項清單) 和 Showyou (社群影片),如下所示:
方法 1:伺服器端偵測
在伺服器上,我們對所處理裝置的瞭解會受到限制。最有用的線索可能是使用者代理程式字串,這是透過每個要求的 User-Agent 標頭提供。因此,同樣的通用 Analytics 嗅探方法也適用於此。事實上,DeviceAtlas 和 WURFL 專案已執行這項操作 (並提供大量有關裝置的其他資訊)。
但不幸的是,每種方法都各有難處。WURFL 非常大,包含 20 MB 的 XML,因此每個要求都可能產生大量伺服器端額外負擔。有些專案會基於效能考量而分割 XML。DeviceAtlas 並非開放原始碼,使用時必須購買付費授權。
還有其他更簡單的免費替代方案,例如 Detect Mobile Browsers 專案。當然,缺點是裝置偵測功能會變得較不全面。此外,它只會區分行動裝置和非行動裝置,僅透過臨時調整組合提供有限的平板電腦支援。
方法 2:用戶端偵測
我們可以透過功能偵測功能,瞭解使用者的瀏覽器和裝置。我們需要判斷的主要事項是,裝置是否具備觸控功能,以及螢幕大小。
我們需要劃分界線,以便區分小型和大型觸控裝置。那麼 5 吋 Galaxy Note 等極端案例呢?下圖顯示多款熱門 Android 和 iOS 裝置重疊 (並顯示對應的螢幕解析度)。星號表示裝置已提供或可提供雙倍密度的顯示效果。雖然像素密度可能會加倍,但 CSS 仍會回報相同的大小。
關於 CSS 中的像素,我們想特別說明一點:行動版網站的 CSS 像素與螢幕像素不同。iOS 的 Retina 裝置引入了將像素密度加倍的做法 (例如 iPhone 3GS 與 4、iPad 2 與 3)。為了避免網頁中斷,Retina 行動版 Safari UA 仍會回報相同的裝置寬度。其他裝置 (例如 Android) 裝置的解析度提高,它們會使用相同的裝置寬度技巧。
不過,考量到直向和橫向模式的重要性,這項決定就變得複雜了。我們不想每次重新調整裝置方向時,就重新載入網頁或載入其他指令碼,但我們可能會以不同方式轉換網頁。
在下圖中,正方形代表每部裝置的最大尺寸,這是因為疊加了直向和橫向輪廓 (並完成正方形) 的結果:
將門檻設為 650px
後,我們會將 iPhone、Galaxy Nexus 歸類為「smalltouch」,而 iPad、Galaxy Tab 則歸類為「tablet」。在這種情況下,性別不明的 Galaxy Note 會歸類為「手機」,並取得手機版面配置。
因此,合理的策略可能如下所示:
if (hasTouch) {
if (isSmall) {
device = PHONE;
} else {
device = TABLET;
}
} else {
device = DESKTOP;
}
查看功能偵測方法的簡易範例。
另一種做法是使用使用者代理程式探查來偵測裝置類型。基本上,您會建立一組經驗法則,並與使用者的 navigator.userAgent
進行比對。虛擬程式碼如下所示:
var ua = navigator.userAgent;
for (var re in RULES) {
if (ua.match(re)) {
device = RULES[re];
return;
}
}
請參閱 UA 偵測方法的範例。
關於用戶端載入的附註
如果您在伺服器上執行通用 Analytics 偵測,則可決定在收到新要求時要提供哪些 CSS、JavaScript 和 DOM。不過,如果您要進行用戶端偵測,情況就會更複雜。您可以選擇以下幾種做法:
- 重新導向至裝置類型專屬網址,其中包含此裝置類型的版本。
- 動態載入裝置類型專屬的素材資源。
第一個方法很簡單,只需要 window.location.href = '/tablet'
等重導。不過,現在位置會附加這類裝置類型資訊,因此建議您使用 History API 清理網址。很抱歉,這種做法會涉及重新導向,而重新導向可能會很慢,尤其是在行動裝置上。
第二種方法的實作方式較為複雜。您需要動態載入 CSS 和 JS 的機制,且 (視瀏覽器而定) 您可能無法執行自訂 <meta viewport>
等操作。此外,由於沒有重新導向,您只能使用原始提供的 HTML。當然,您可以使用 JavaScript 操作它,但這可能會比較慢且/或不夠優雅,具體情況視應用程式而定。
決定用戶端或伺服器
以下是這兩種方法的優缺點:
Pro 用戶:
- 以螢幕尺寸/功能而非通用 Analytics 為依據,因此更能因應未來需求。
- 不必持續更新不重複觀眾名單。
Pro 伺服器:
- 可完全控制要向哪些裝置提供哪個版本。
- 效能更佳:無需用戶端重新導向或動態載入。
我個人建議您從 device.js 和用戶端偵測開始著手。隨著應用程式不斷演進,如果您發現用戶端重新導向是效能上的重大缺點,可以輕鬆移除 device.js 指令碼,並在伺服器上實作使用者代理程式偵測功能。
介紹 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 架構。您可以輕鬆將檢視畫面移至個別檔案,為每種裝置類型建立自訂檢視畫面。接著,您可以為所有裝置提供相同的程式碼 (除了 View 層)。
您的專案可能具有下列結構 (當然,您可以根據應用程式自由選擇最合適的結構):
models/ (shared models) item.js item-collection.js
controllers/ (共用控制器) item-controller.js
versions/ (device-specific stuff) tablet/ desktop/ phone/ (phone-specific code) 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,但不符合回應式設計的規範,請採取下列做法:
- 請選取要支援的裝置類別,以及將裝置分類為不同類別的條件。
- 建構 MVC 應用程式時,請採用嚴格的關注點分離,將 View 與其他程式碼集分開。
- 使用 device.js 進行用戶端裝置類別偵測。
- 準備就緒後,請將指令碼和樣式表包裝到每個裝置類別的其中一個中。
- 如果用戶端重新導向成效不佳,請放棄 device.js,改用伺服器端使用者代理程式偵測功能。