如何讓web返回上一頁時恢上次復瀏覽位置?

需求:
一個新聞頁面,新聞列表內容通過jsonp請求回來的,希望在載入幾頁後點入新聞,然後點擊瀏覽器的返回上一頁,能恢復到原來瀏覽的位置


問題:
由於新聞列表是動態插入的,即使瀏覽器記錄了點入新聞時滾動條的位置,也會因為返回上一頁後,頁面的新聞列表不夠點入前的新聞列表長,導致定位不準確


我的思路:
既然瀏覽器能記錄上次的位置,那我就一次性請求上次看到的頁數,比如上次看到第七頁,我就記錄到localstorage裡面,下次一次請求7頁數據,就可以恢復到原來新聞列表的長度並返回原來的位置了。這個思路在PC端能實現,但是在移動端,因為我同時使用window.name來判斷是否第一次請求頁面,在safari、chrome後退幾次後總是誤判為第一次載入(即沒有window.name)而導致功能實現有問題。另外,如果用戶一次閱讀了100頁,我也一次性請求100頁數據,就不太合理了。


參考:
經過觀察,網易新聞是可以滿足我的(只有在移動端訪問時才可以),暫時還沒研究出他們是怎麼實現的,傳送:手機網易網

希望各位大佬指導~~


提供幾個思路

1. 做單頁應用

單頁應用對於這個問題的解決辦法太多了,畢竟控制權都在你手裡,History API 也有新 spec。

在這個問題上主要麻煩的還是多頁應用,基本都有 trade offs

2. 多頁 - 利用瀏覽器:原生的 Scroll Restoration 或 開新窗口

其實現代瀏覽器都是會「爭取幫你恢復滾動位置」的,但是各家並沒有一個統一的標準。

題主舉的手機網易網的例子其實「據我目測」就是用的瀏覽器的原生恢復 + 開新窗口。

對於支持 Page Cache 的瀏覽器(Safari, QQ),所有的文章鏈接都會渲為 &,直接在當前標籤打開。返回時原生自然有位置恢復。

而對於沒有 Page Cache 的瀏覽器(比如 Android Chrome),所有的文章鏈接則會渲染為 &,相當於和有 Page Cache 一樣把上一頁留在內存里了,這樣根本沒有恢複位置的需要。

這種方法需要整理一個 UA =&> hasPageCache 的 Map,比較麻煩,而且有可能誤判。

  • 對於誤判作無 Page Cache 的,無傷大雅,(所以理論上你可以全部開新窗口……),但是 Safari 等瀏覽器對於新開一個窗口交互太浮誇了也不支持左滑 Back,體驗差。
  • 對於誤判作有 Page Cache 的,需要重新拿一遍資源是肯定的了(所以務必上 HTTP 強緩存),但最終瀏覽器能不能幫助頁面回到滾動位置這事吧,用戶的行為、你的渲染方式是阻塞非阻塞,頁面是不是夠高,都可能影響到瀏覽器 heursitic 的判斷。

如評論中所說,iOS 全認有 Page Cache(確實也都只能 Webkit),Android 上先全認無 Page Cache,然後再慢慢漸進增強 Android 上有 Page Cache 的就行。

最大的一個缺點是:開新窗口方案只能用在瀏覽器里。對於全屏 web 應用(無論是 PWA 還是 iOS 私有的全屏),開新窗口就出應用了。

3. 多頁 - 利用 JS 自己恢復

這個辦法可以作為瀏覽器原生滾動的 Fallback,實現起來也沒啥難度,無非約定一個持久化儲存的方式就好(最簡單的比如 sessionStorage)。

這個方法不會影響有 Page Cache 的瀏覽器。

而對於無 Page Cache 的瀏覽器,由於沒有 & 這樣的 trick,所以你確實會每次都要「重啟這個頁面」,這也是我在 https://medium.com/elemefe/upgrading-ele-me-to-progressive-web-app-2a446832e509 (Upgrading http://Ele.me to PWA,中文版很快就會出來了)里寫到的,任何多頁應用(無論是不是 PWA)都會遇到的一個性能問題。

也不是沒有優化的辦法,網路請求和渲染理論上都是可以做分頁分塊的,滾動也可以做虛擬化,但是這下工程成本就又上去了。

當然你還可以把 2 和 3 再包裝一層……理論上可以做到所有平台的漸進增強優雅降級,但是,還是很麻煩就是了,所以多頁模型 + 無限滾動 + Ajax 解決這個問題目前沒有銀彈,如果不計工程成本的話最好的解決方案應該是「滾動完全虛擬化 + 網路渲染全部分塊 + 懶載入 + 可回收」。實在懶得折騰的話還是別用無限滾動用傳統的分頁吧(這樣也能防止內存爆掉)

歡迎交流,有更好的辦法請務必教教我 OTZ

以上。


既然都分析出原因了,那解決也就不難了。比如: 可以為一組條目的容器設置個最小高度讓它恰好等於這組條目的總高度,這樣一來載入頁面不會導致高度變化,自然就能回到原位了。而只要進行合適的設計和少量折中,要做到預知高度還是挺容易的。


點擊新聞標題進入新聞詳情頁時,使用javascript記錄scrolltop(即當前瀏覽器滾動距離)到瀏覽器的sessionstorage,從詳情頁返回的時候再讀取此位置並滾到。

具體操作:

由於要記錄位置,所以返回的時候,新聞列表也要是原來的列表,所以新聞列表就做一個本地存儲。


解決過此類問題,談一下經驗。

首先思考一下可行性方案,很容易可以想到,我只要在返回時,滾動條定位到打開新聞詳情時位置就可以了。那這裡就包含兩層含義:1,在某處緩存了定位信息。2,在新聞列表初始化時,要先獲取到定位信息並還原位置。

那麼 接下來我們來實現。

首先,我們要記錄到這個位置。用緩存來處理這個值是殺雞用牛刀,html5api的history.replaceState即可解決問題,將參數寫在url上,並且不增加歷史記錄。

由於我們有分頁,所以只緩存位置是不夠的,還需要緩存當前已載入的記錄條數。這個只需要分頁載入完畢後往url上寫個值即可。

於是 位置和記錄條數 兩個信息我們都有了,直接改寫url,不管你跳轉到哪裡,只要用戶返回當前頁面,就可以把這兩個值帶回來了。那現在我們要考慮的問題就很簡單: 在新聞列表頁載入時,處理這兩個值。

這裡牽涉個緩存問題。由於我的需求要求不緩存,做法比較簡單,我只需要讀一下pagesize這個值,臨時把頁碼改為這個數,一次性把之前幾頁的數據全部載入回來,載入完畢後,寫入dom,再在任務隊列尾部加個匿名函數,讓他定位到指定位置即可。

如果需要緩存新聞數據,一次性載入就無法命中緩存,需要改個思路:
先封裝一下xhr,加個參數設置緩存有效期,下次遇到同樣請求直接讀緩存。這樣的話,我們就不臨時修改頁碼了,而是用promise.all發若干次請求,在結束後再定位到指定的位置即可。

以上是移動開發時的經驗,如果需要兼容低版本瀏覽器,其實也就是用不了history的新api而已,換個位置持久化這個值,比如寫cookie


錨點啥的不提了,單頁和多頁都差不多

如果把新聞的每一段或者每一屏記一個錨點,把這個錨點push進history或者放到sessionStorage或者隨便什麼cache里

最後用Element.scrollIntoView()

老夫曾經寫過:React 元素滾動至可見

這個介面還躺在草案里,但兼容性都還行:


自研了一個長列表react組件,支持兩個方向的無限滾動,比如你上次瀏覽到第14頁的數據,回退時只需要載入第十四頁的數據,然後向上滑載入之前的數據,向下滑載入之後的數據,目前已經在公司兩個頁面應用,等我抽離一下公司的業務後開源出來,其實這個問題localStorage也可以解決,就是可能在別的頁面會對緩存做更改,導致回退的數據不是最新數據的問題


https://zhuanlan.zhihu.com/p/27586179

這是我上次解決的方案,採用記錄原始位置,把存儲在sessionStorage的內容重新渲染在頁面上


說了我們多頁時的做法:緩存列表頁num、滾動條位置、列表數據(每一頁),每次初始化頁面時,判斷是否緩存了,有的話就直接渲染數據,定位滾動條位置,再翻頁的話,在原來列表num基礎上+1做請求,每次點擊新聞頁面時做緩存操作,這裡localStorage、sessionStorage在無痕模式下的瀏覽器記得做try..catch處理,還有你講的safari、chrome等的回退,它們是直接不刷新頁面的,那麼就直接定位到上次訪問的位置了啊~,反正做個東西,緩存一定不要做混了,加緩存、清緩存的時機一定要把握好

單頁上面大神的回答


做個單頁簽名緩存唄,緩存用戶剛剛看過的那個 list 或者 page 的簽名,只要保證每一頁有自己獨特的簽名就行了。緩存的方式有很多種。當用戶點擊返回按鈕時,在當前頁面中使用緩存頁的簽名搜索,直接定位到那裡。。。。唯一麻煩的就是看你怎麼寫搜索演算法了。。

或者用 js 維護一個 history ,返回就相當於重新請求一次那個頁面,這樣內存的壓力應該會小點。。。

我是做後端的。。但是感覺這個邏輯應該不會錯。。


推薦閱讀:

有沒有開源的音色庫(音源)?
UI設計師、網頁設計師、前端開發如何轉行?
如何完整離線保存網頁,包括網頁完整特效?
如何把禁止複製和列印的網頁上的文字存儲下來?

TAG:前端開發 | 前端工程師 |