一條導航軌跡先被記了下來:vue-split-screen,把折疊螢幕的雙欄做成了介面翻譯
從 vue-split-screen 用一條導航軌跡解決雙欄路由的設計看起,觀察折疊螢幕時代 Web 介面如何被重新翻譯。
先從一個欄位看起。在 vue-split-screen 的資料結構裡,每個頁面節點只有兩個屬性:id 和 fullPath。fullPath 記錄頁面的實際地址,包含查詢參數與 hash;id 記錄這一次頁面實例在軌跡中的身分。這個拆分很小,小到容易被略過,但它解決的問題一點也不小:兩次造訪同一個地址,在軌跡裡是兩個不同的節點。路由地址與頁面實例,從此不再混為一談。
這個元件出現的背景很具體。九月九日,蘋果發表首款折疊螢幕 iPhone Duo,展開是七點六英寸內屏,合上是五點四英寸外屏,十月上市。手機螢幕從窄變寬,Web 應用跟著被追問一個問題:展開之後,使用者能不能同時看到列表與詳情。在手機上,從列表打開詳情、當前頁面被替換,是很自然的互動;到了寬螢幕,使用者期待列表留在左邊、詳情出現在右邊,而且瀏覽器的返回與前進還要照常可用。vue-split-screen 的回答方式,是把整件事還原成一條導航軌跡。
雙欄的難處,先被誠實地列出來
最直覺的做法是放兩套 RouterView,左邊一套、右邊一套。但這條路很快會撞上三個問題:兩個地址、兩份當前路由、兩套瀏覽器歷史。使用者按一次返回,究竟要退左欄還是退右欄?地址欄顯示哪個頁面?這些疑問在窄螢幕時代從不存在,因為「當前頁面」只有一個。
vue-split-screen 沒有另立一個平行路由系統,而是保留一套 Vue Router,把每次成功導航後的頁面關係記錄成一條軌跡。假設軌跡是 A、B、C,雙欄畫面只取軌跡末尾的兩個節點,顯示 B 與 C。這個取法有點像攝影裡的淺景深:歷史仍然是完整的一串,畫面只對最後兩格對焦。
軌跡的推進規則也寫得很清楚。從右側頁面 C 發起 push,新軌跡是 A、B、C、D;從左側頁面 B 發起 push,結果是 A、B、D,右側的 C 被替換掉。replace 則更激進,連發起導航的節點本身也一併換掉。這套規則由一個純 TypeScript 函式 navigateTrail 完成,與 Vue 元件、瀏覽器 API 都沒有耦合,因此可以單獨測試。把核心邏輯從框架裡剝出來,讓規則可以被驗證,這是工程設計裡一種安靜但扎實的美感。
按鈕屬於哪個面板,是一種被寫明的上下文
雙欄場景裡還有一個容易被忽略的細節:按鈕屬於哪個頁面。使用者在左側面板點擊跳轉,如果路由不知道動作來自左欄,很容易把右側頁面也當成導航起點,軌跡就算錯了。
vue-split-screen 的處理是在頁面上下文裡為 useRouter 注入面板局部的 push 與 replace,另外提供 useSplitRouter 讓這層依賴更明確。頁面元件裡呼叫 router.push,這個 push 會帶著當前節點的 id 交給歷史控制器,控制器再依節點位置算出下一條軌跡。物件形式的導航也以發起頁面作為解析基準,所以從左側頁面只修改查詢參數時,目標仍然落在正確的節點上。
還有一個配套的 useSplitRouteNode,讓頁面邏輯能拿到當前節點的 id 與 fullPath。「這個頁面屬於哪個面板」這件事,從隱含的畫面位置,變成明確寫在程式裡的宣告。介面設計的好壞,往往就藏在這種把模糊常識轉成明確契約的動作裡。
這種「把一套大螢幕互動翻譯到另一種裝置」的思路,與我們先前觀察過的原子工作臺下放直板機的介面翻譯是同一類課題:原本屬於折疊螢幕的多工介面,如何被重新設計成小螢幕上的日常操作。方向一個由大往小、一個由小往大,核心都是同一套配置如何在兩種畫面比例之間被共用。
把軌跡寫進瀏覽器的歷史條目
導航軌跡如果只放在元件的記憶體裡,使用者按下瀏覽器返回後,應用便無從得知之前顯示過哪些頁面。vue-split-screen 把軌跡序列化進 history.state,每個歷史條目都帶著一份當時的軌跡快照。Vue Router 仍然全權負責地址欄、導航守衂與 pushState 和 replaceState,這個專案只在呼叫路由方法前準備好下一條軌跡,並在 afterEach 裡確認導航結果。
這個分工值得停下來看一眼。它沒有取代路由器,也沒有在路由器外另起爐竈,而是選擇在路由器既有的生命週期縫隙裡插入自己的邏輯。對使用既有 Vue Router 的專案來說,改動成本因此被壓到很低:同一套路由配置,列表與詳情、父子頁面、折疊展開後的雙欄視圖,全部沿用。
這與近年瀏覽器廠商把「折疊狀態」暴露給 Web 平臺的方向是一致的。當裝置形態本身成為一種輸入,畫面比例的變化就得被當成介面的第一級公民來設計,而不是事後補的響應式分支。
收在比例的翻譯上
回到設計本身。vue-split-screen 做的事,本質上是一場比例的翻譯:把一條為窄螢幕設計的線性導航史,攤平成寬螢幕上並置的兩個畫面,而且不犧牲返回鍵的語意。它選擇的媒介是一條軌跡,選擇的切入點是 id 與 fullPath 的分離,選擇的落地處是 history.state。每一步都沒有發明新概念,只是把既有概念重新對位。
折疊螢幕手機問世多年,Web 端的適配討論多半停留在媒體查詢與斷點。vue-split-screen 提供了另一個層面的答案:雙欄不只是兩塊畫面並排,導航本身需要一套新的敘事 grammar,讓「從哪裡出發」與「到了哪裡」都被記住。iPhone Duo 十月才上市,這類元件會不會成為標準做法還很難說,但把導航史當成一條可以被閱讀的軌跡來設計,這個視角本身已經值得放進工具箱。
主題