BFCache 筆記之你怎麼都沒變啦
Back Forward Cache
部分瀏覽器(主要是 Safari、Firefox)為了在往返上下一頁有更好的效能,會使用 BFCache 的策略,會把離開前的 DOM 狀態、JavaScript 狀態都保存住。
當用戶回到上一頁或是進入下一頁時,就會使用記憶體內的 Cache,一切都會像離開前的模樣,好處是可以優化操作體驗,因為都從 Cache 拿,不需要重新 reload 頁面,操作起來就會很迅速,也不需要再藉由網路讀取資料,保留狀態也符合大部分情境的使用者預期,可以回上頁後繼續剛才未完成的操作。
聽起來很美好,但有些情況就不一定,舉個最近遇到的例子。
情境
最近遇到的例子是使用純 HTML + CSS + Vanilla JS 寫的多頁式網頁,這個網頁的 Navbar 在手機板的時候會變成常見漢堡選單,點擊漢堡 icon 就會展開 Navbar。
感覺一切都很正常,但遇到一個小 bug ,是當 Safari 的使用者在當前頁面點擊展開 Navbar 進入其他頁面,再點擊回到上一頁的按鈕時,選單依然是展開的。
為甚麼呢?原因就是因為 BFCache 保存了你離開這頁面前的狀態,也因此原本展開的選單就會保持展開,所以當你從新頁面回到原頁面時,就會看到 Navbar 依然張開開的看著你,嗚嗚,這樣的情況在使用上就會有點怪怪的,因為使用者預期應該是回到上一頁的時候 menu 會自己關起來才對,每次回到上一頁都看到開啟來的 menu 也太煩人了吧~
解法
查了一下資料,網路上有蠻多解法的,但大多數都是偵測到使用 BFCache 時,就重新讀取頁面。
解法一
有看到可以在 HTML 加上下面的 tag,設置後可以避免頁面被 cache。
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
解法二
在使用 pageshow, pagehide 兩個事件時,event 可以使用 persisted 來判斷是否使用 BFCache,當有使用 BFCache 時,event.persisted
就會回傳 true。
下面這寫法相當於在進入頁面時,會判斷是否有使用 BFCache,如果有的話就 raload 頁面。
window.onpageshow = function(event) {
if (event.persisted) {
window.location.reload()
}
};
而關於 BFCache 的處理方法,可以查到的方法大多是強迫頁面 reload 讀取頁面。
選擇了甚麼方法?
在上述情境中,我是加上一個叫 menu-open
的 class 來控制漢堡選單的開關,css 寫法可能類似這樣:
.menu {
width: 0;
margin: 0 auto;
overflow: hidden;
}
.menu.menu-open {
width: 250px;
}
總之就是加上 menu-open
的 class 時,高度上會有變化,進而達到展開關上的效果。
頁面性質也沒甚麼太複雜的操作,基本上就是呈現資訊的頁面而已,如果每個頁面都要大費周章 reload 好像不太划算,因此最後的解法是
window.addEventListener('pageshow', function(e) {
if(e.persisted) {
menu.classList.remove('menu-open')
}
})
當頁面讀取完要 show 出來時,就移除掉 menu-open 的 class,既可確保 menu 關閉,也可以不用每次都 reload,維持 BFCache 的好處。
但如果是比較複雜的情境,比如涉及到表單提交,使用者如果提交後按上一頁又會回到提交先前狀態之類的狀況(比如 Clear all fields in a form upon going back with browser back button 這篇文),或許整個 reload 就會更好。
另外每個瀏覽器狀況都不太一樣,大部分 BFCache 的問題都發生在 Firefox、Safari 瀏覽器,Chrome 先前似乎是將 BFCache 移除了(詳情可以看這裡),不過在查找資料的時候,發現 Chrome 在 2019 宣布在開發新的 bfcache 技術,預計在 2020 會應用到 Chrome 上。(這篇文章) 不過根據實測,在發佈這篇文章以前 Chrome 目前是還沒有 BFCache。