中土世界的前端

多裝置開發功能的逐步操作說明

Daniel Isaksson
Daniel Isaksson
Einar Öberg
Einar Öberg

第一篇文章中,我們介紹了 Chrome 實驗「穿越中土世界」的開發作業,並著重於行動裝置的 WebGL 開發作業。本文將討論在建立其他 HTML5 前端時遇到的挑戰、問題和解決方法。

同一個網站的三個版本

首先,我們來談談如何從螢幕大小和裝置功能的角度,調整這項實驗,讓它同時適用於電腦和行動裝置。

整個專案採用非常「電影化」的風格,我們希望在設計上維持橫向固定框架,保留電影的魔幻效果。由於專案的大部分內容都包含互動式迷你「遊戲」,因此讓這些內容溢出畫面也不合理。

我們可以以到達網頁為例,說明如何為不同大小調整設計。

鷹眼剛剛將我們帶到到達網頁。
我們剛抵達到達網頁。

網站提供三種模式:電腦、平板電腦和行動裝置。不僅是為了處理版面配置,也是因為我們需要處理在執行階段載入的素材資源,並新增各種效能最佳化功能。對於解析度高於桌上型電腦和筆記型電腦,但效能不如手機的裝置,要定義最終的規則並不容易。

我們會使用使用者代理程式資料偵測行動裝置,並透過視窗大小測試,指定其中的平板電腦 (645 像素以上)。每個不同模式其實都能算繪所有解析度,因為版面配置是根據媒體查詢或 JavaScript 的相對/百分比定位而定。

由於此案例中的設計並非以格線或規則為依據,且不同部分之間的設計也相當獨特,因此實際上取決於要使用的元素和情境,才能決定要使用哪些分段點或樣式。我們不只一次使用 SASS 混合和媒體查詢,設定完美版面配置,然後需要根據滑鼠位置或動態物件新增效果,最後在 JavaScript 中重寫所有內容。

我們也會在標頭標記中加入含有目前模式的類別,以便在樣式中使用該資訊,如以下 SCSS 範例所示:

.loc-hobbit-logo {

  // Default values here.

  .desktop & {
     // Applies only in desktop mode.
  }

 .tablet &, .mobile & {
   
   // Different asset for mobile and tablets perhaps.

   @media screen and (max-height: 760px), (max-width: 760px) {
     // Breakpoint-specific styles.
   }

   @media screen and (max-height: 570px), (max-width: 400px) {
     // Breakpoint-specific styles.
   }
 }
}

我們支援所有尺寸,但尺寸必須小於 360 x 320,這在打造身歷其境的網頁體驗時,會造成相當大的挑戰。在電腦上,我們會先設定最小尺寸,再顯示捲軸,因為我們希望您盡可能在較大的可視區域中瀏覽網站。在行動裝置上,我們決定允許橫向和直向模式,直到互動式體驗時,我們才會要求您將裝置轉為橫向模式。反對者認為直向模式的沉浸感不如橫向模式,但網站的縮放效果相當不錯,因此我們決定保留這個選項。

請注意,版面配置不應與輸入類型、裝置方向、感應器等功能偵測功能混淆。這些功能可能會出現在所有模式中,且應涵蓋所有模式。例如同時支援滑鼠和觸控操作。雖然 Retina 畫質確實不錯,但成效才是最重要的,有時品質較低反而更好。舉例來說,在 Retina 螢幕上,WebGL 體驗中的畫布解析度只有原來的一半,否則必須算繪四倍的像素數量

在開發期間,我們經常使用開發人員工具中的模擬器工具,尤其是 Chrome Canary,因為它有許多新功能和預設值。這是快速驗證設計的好方法。我們仍需要定期在實際裝置上進行測試。其中一個原因是網站會調整為全螢幕模式。在大多數情況下,含有垂直捲動的網頁會在捲動時隱藏瀏覽器 UI (iOS 7 版 Safari 目前有此問題),但我們必須讓所有內容不受此影響。我們也在模擬器中使用預設值,並變更螢幕大小設定,模擬可用空間不足的情況。如要監控記憶體用量和效能,在實際裝置上測試也是很重要的一環

處理狀態

到達網頁後,我們會前往中土世界地圖。你是否注意到網址有變動?這個網站是單頁應用程式,會使用 History API 處理轉送

網站的每個部分都是獨立的物件,繼承了 DOM 元素、轉場、載入素材資源、處置等常用功能。當您瀏覽網站的不同部分時,系統會啟動各個部分,並在 DOM 中新增及移除元素,以及載入目前部分的素材資源。

由於使用者隨時可以按下瀏覽器的返回按鈕,或透過選單導覽,因此您必須在某個時間點處置所有建立的內容。您必須停止並捨棄逾時和動畫,否則會導致不必要的行為、錯誤和記憶體流失。這項工作不一定容易,尤其是在截止期限將至,您需要盡快將所有內容上傳時更是如此。

展示地點

為了展示中土世界的美麗場景和角色,我們建構了圖片和文字元件的模組化系統,可讓您水平拖曳或滑動。我們並未在這裡啟用捲軸,因為我們希望在不同範圍內提供不同的速度,例如在圖片序列中,您可以將動作暫停,直到短片播放完畢。

瑟蘭迪爾大廳
Thranduil's Hall 時間軸

時間軸

開發作業開始時,我們不知道各個地點的模組內容。我們知道,我們需要以範本方式在水平時間軸上顯示不同類型的媒體和資訊,這樣我們就能自由地提供六種不同的地點呈現方式,而不必重建所有內容六次。為管理這項功能,我們建立了時間軸控制器,可根據設定和模組行為處理模組的平移。

模組和行為元件

我們新增的支援模組包括圖片序列、靜態圖片、視差效果場景、焦點轉移場景和文字。

視差景象模組含有不透明背景,並自訂圖層數量,可監聽可視區進度,以取得確切位置。

焦點轉移場景是視差桶的變化版本,但我們為每個圖層使用兩張圖片,並以淡入和淡出的方式模擬焦點變更。我們嘗試使用模糊處理器,但仍耗費過多資源,因此我們會等待 CSS 著色器。

文字模組中的內容可透過 TweenMax 外掛程式 Draggable 啟用拖曳功能。你也可以使用滾輪或雙指滑動來垂直捲動畫面。請注意 throw-props-plugin,這是在您滑動並放開時,新增彈飛式物理效果的元件。

模組也可以有不同的行為,這些行為會以一組元件的形式新增。每個目標都各自有目標選取器和設定。翻譯可用於移動元素、縮放比例、資訊重疊的熱點、用於視覺測試的偵錯指標、開始標題重疊、閃光效果層等。這些元素會附加至 DOM,或控制模組內的目標元素。

有了這個功能,我們只需使用設定檔即可建立不同的位置,定義要載入的素材資源,並設定不同類型的模組和元件。

圖片序列

從效能和下載大小的角度來看,圖片序列是效能最差的模組。您可以參考許多相關文章。在行動裝置和平板電腦上,我們會將這項功能替換為靜止圖片。如要提供不錯的行動裝置畫質,就必須解碼及儲存大量資料。我們嘗試了多種替代方案,一開始使用背景圖片和圖像片,但這會導致記憶體問題,並在 GPU 需要在圖像片之間切換時造成延遲。接著,我們嘗試交換 img 元素,但速度也太慢。從圖像片段影像板繪製影格,是效能最佳的做法,因此我們開始進行最佳化。為了節省每個影格中的運算時間,系統會透過暫時性畫布預先處理要寫入畫布的圖片資料,並透過 putImageData() 儲存至陣列,然後解碼並準備使用。接著,系統就能回收原始的圖像片集,並只在記憶體中儲存所需的資料最小值。儲存未解碼的圖片可能會減少,但以這種方式檢查序列時,效能會更好。這些影格相當小,只有 640 x 400,但只有在快轉時才會顯示。停止時,系統會載入高解析度圖片並快速淡入。

var canvas = document.createElement('canvas');
canvas.width = imageWidth;
canvas.height = imageHeight;

var ctx = canvas.getContext('2d');
ctx.drawImage(sheet, 0, 0);

var tilesX = imageWidth / tileWidth;
var tilesY = imageHeight / tileHeight;

var canvasPaste = canvas.cloneNode(false);
canvasPaste.width = tileWidth;
canvasPaste.height = tileHeight;

var i, j, canvasPasteTemp, imgData, 
var currentIndex = 0;
var startIndex = index * 16;
for (i = 0; i < tilesY; i++) {
  for (j = 0; j < tilesX; j++) {
    // Store the image data of each tile in the array.
    canvasPasteTemp = canvasPaste.cloneNode(false);
    imgData = ctx.getImageData(j * tileWidth, i * tileHeight, tileWidth, tileHeight);
    canvasPasteTemp.getContext('2d').putImageData(imgData, 0, 0);

    list[ startIndex + currentIndex ] = imgData;

    currentIndex++;
  }
}

系統會使用 Imagemagick 產生 Sprite 工作表。以下是 GitHub 上的簡單範例,說明如何為資料夾內的所有圖片建立精靈板。

為模組製作動畫

為了將模組放到時間軸上,系統會在螢幕外顯示時間軸的隱藏表示法,以追蹤「播放頭」和時間軸的寬度。這項操作可以只使用程式碼完成,但在開發和偵錯時,使用視覺化表示法會更方便。實際執行時,系統只會在調整大小時更新至設定的尺寸。有些模組會填滿檢視區,有些則有自己的比例,因此在所有解析度中縮放及定位所有內容,讓所有內容都看得到且不會過度裁剪,就變得有些棘手。每個模組都有兩個進度指標,一個用於顯示畫面上的可見位置,另一個用於顯示模組本身的時間長度。製作視差動畫時,通常很難計算物件的起始和結束位置,以便在視窗中顯示預期的位置。建議您確切瞭解模組何時進入檢視畫面、播放內部時間軸,以及何時再次從檢視畫面離開。

每個模組頂端都有一個微妙的黑色圖層,可調整模組不透明度,讓模組在置中時完全透明。這有助於您一次專心處理一個模組,進而提升使用體驗。

網頁效能

從功能原型轉移至無卡頓的發布版本,代表您從猜測轉為瞭解瀏覽器中的運作情形。這時 Chrome 開發人員工具就是您最好的幫手。

我們花了相當長的時間進行網站最佳化。強制硬體加速是打造流暢動畫的最重要工具之一。但也要尋找 Chrome 開發人員工具中的彩色欄和紅色矩形。網路上有很多相關的優質文章,建議您全部閱讀。移除跳過影格後,觀眾會立即感受到改善,但如果問題又再度發生,他們也會感到相當沮喪。而且他們會這麼做。這是一個持續進行的過程,需要不斷疊代。

我喜歡使用 Greensock 的 TweenMax 來處理補間屬性、轉換和 CSS。以容器為思考單位,在新增新層時以圖像化方式呈現結構。請注意,現有的轉換作業可能會遭新轉換作業覆寫。如果您只使用 2D 值進行補間,系統會將 CSS 類別中強制硬體加速的 translateZ(0) 替換為 2D 矩陣。如要讓圖層在這些情況下維持在加速模式,請在轉場效果中使用「force3D:true」屬性,建立 3D 矩陣,而非 2D 矩陣。您很容易忘記如何結合 CSS 和 JavaScript 轉場效果來設定樣式。

請勿在不需要硬體加速的情況下強制啟用硬體加速。當您想要為許多容器進行硬體加速時,GPU 記憶體可能會迅速用盡,並導致不必要的結果,特別是在記憶體受限的 iOS 上。載入較小的素材資源,並使用 CSS 放大這些素材資源,以及在行動裝置模式中停用部分特效,這些做法都大幅改善了效能。

記憶體外洩是另一個我們需要加強的領域。在不同 WebGL 體驗之間瀏覽時,系統會建立許多物件、材質、紋理和幾何圖形。如果您在移除該區段時,這些物件尚未準備好進行垃圾收集,可能會導致裝置在記憶體用盡後過一段時間就異常終止。

使用失敗的 dispose 函式退出區段。
使用失敗的 dispose 函式退出區段。
這樣好多了!
好多了!

如要找出記憶體外洩,只要在開發人員工具中記錄時間軸並擷取堆積快照,就能輕鬆完成這項工作。如果有特定物件 (例如 3D 幾何圖形或特定程式庫) 可供篩選,就會比較容易。在上例中,3D 場景仍在,且儲存幾何圖形的陣列未清除。如果您發現很難找出物件所在位置,可以使用「保留路徑」這項實用功能來查看。只要在堆積圖快照中按一下要檢查的物件,即可在下方面板中取得相關資訊。使用較好的結構搭配較小的物件,有助於找出參考資料。

在 EffectComposer 中參照了該場景。
EffectComposer 會參照該場景。

一般來說,在操作 DOM 前請三思,請務必考量效益。盡量不要在遊戲迴圈中操控 DOM。將參照儲存在變數中以便重複使用。如果您需要搜尋元素,請使用最短路徑,也就是儲存關鍵容器的參照,並在最近的祖系元素中搜尋。

如果您遇到版面配置錯誤,請延遲讀取新增元素的尺寸,或在移除/新增類別時延遲讀取尺寸。或者,請確認版面配置已觸發。有時瀏覽器會批次變更樣式,且不會在下一個版面配置觸發事件後更新。這有時可能會造成嚴重問題,但這項功能的存在是有原因的,因此請試著瞭解幕後運作方式,這樣就能獲得許多收穫。

全螢幕

如有支援,您可以透過 Fullscreen API 在選單中將網站設為全螢幕模式。但在裝置上,瀏覽器也會決定將畫面設為全螢幕。iOS 版 Safari 先前有一個小技巧可讓您控制這項功能,但現在已無法使用,因此在製作不捲動的頁面時,您必須準備好不含這項功能的設計。由於這項功能已導致許多網頁應用程式無法運作,因此我們可能會在日後的更新中提供相關更新。

素材資源

實驗的動畫操作說明。
實驗的動畫操作說明。

在整個網站中,我們使用了許多不同類型的素材資源,包括圖片 (PNG 和 JPEG)、可擴充向量圖形 (內嵌和背景)、圖像片段影像 (PNG)、自訂圖示字型和 Adobe Edge 動畫。我們會在元素無法以向量圖形繪製的情況下,為素材資源和動畫 (圖塊區塊) 使用 PNG,否則會盡可能使用 SVG。

向量格式可讓您縮放圖片,而不會降低圖片品質。所有裝置皆適用 1 個檔案。

  • 檔案大小較小。
  • 我們可以分別為每個部分製作動畫 (非常適合進階動畫)。舉例來說,我們會在縮小霍比特人標誌 (Smaug 的荒蕪) 時,隱藏「副標題」。
  • 可嵌入為 SVG HTML 標記,或用作背景圖片,無需額外載入 (與 HTML 網頁同時載入)。

在可擴充性方面,圖示字型與 SVG 具有相同的優點,因此在圖示等小型元素上,我們只需要能夠變更顏色 (懸停、啟用等),就會使用圖示字型取代 SVG。圖示也非常容易重複使用,只需設定元素的 CSS「content」屬性即可。

動畫

在某些情況下,使用程式碼為可擴充向量圖形元素製作動畫可能會耗費許多時間,尤其是在設計過程中需要大量變更動畫時。為改善設計師和開發人員之間的工作流程,我們使用 Adobe Edge 製作部分動畫 (遊戲前的指示)。動畫工作流程與 Flash 非常相似,這對團隊有幫助,但也有一些缺點,尤其是在資產載入程序中整合 Edge 動畫,因為 Edge 動畫有自己的載入器和實作邏輯。

我仍認為,我們必須走很長一段路,才能在網路上建立完美的資產和手繪動畫處理工作流程。我們期待看到 Edge 等工具的演進。歡迎在留言中提供其他動畫工具和工作流程的建議。

結論

如今,專案的所有部分都已發布,我們也看到最終結果,我必須說,我們對現今行動瀏覽器的狀態感到相當滿意。在開始這項專案時,我們對這項服務的期待值很低,認為這項服務的整合和效能無法達到預期水準。這對我們來說是難得的學習經驗,我們花費大量時間進行重複測試,進而更瞭解新式瀏覽器的運作方式。因此,如果我們想縮短這類專案的製作時間,就必須從猜測轉為確知。