日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

Web 端如何低成本打造 Native 體驗?

 

阿里妹導讀:Web 應用在實際體驗上和 Native 應用仍然存在非常明顯的差距,那么如何低成本地把一個現有的網站改造成類 Native 的體驗呢?本文分享一種讓網站低成本漸進式實現 Native 化體驗的方式——同屏渲染。
作者: 當軒 阿里技術

Web 端體驗

在有了 PWA(Progressive web Apps) 之后,Web Application 也具備了添加到桌面和離線訪問等能力,但是實際體驗上卻總是和 Native 應用存在非常明顯的差距。

我們可以看一下 Alibaba 的 M 站和 IOS 應用的錄屏(左邊為 WEB,右邊為 iOS APP):

Web 端如何低成本打造 Native 體驗?

 


Web 端如何低成本打造 Native 體驗?

 

我們可以看到,對于 Web Applicaiton 來說,在頁面中來回跳轉時訪問的總是割裂的,從上一個頁面到下一個頁面需要等 loading ,返回時很多內存狀態又都不在了,導致無法正確定位回之前的列表位置(這一點其實和不同的瀏覽器以及列表本身的實現方式有關,也有一些方案可以規避這個問題,在這里只是其中一個 case)。

這樣對于用戶的體驗傷害非常明顯,他能明確感覺到自己在用的并非一個 Application 而是一個 Website,而且在進行復雜的操作時整個鏈路也非常容易被中斷。

而其實這種體驗差異的根源,在于 B/S(Browser/Server)和 C/S(Client/Server)的差異。ServiceWorker 雖然提供了一些方案(例如 App Shell)讓我們較低成本的增強原有的體驗,但仍然難以解決頁面之間的割裂問題,很多相同的代碼在不同頁面間重復執行,每一次訪問內存狀態就會丟失。

渲染性能

當我們在說體驗的時候會顯得有點主觀,性能相比之下就容易衡量的多,而頁面割裂帶來的最為直觀的體驗差距其實就來自于渲染性能的差異。

在 Web 端一個典型的 CSR(Client Side Rendering)要經過的流程大致如下:

Web 端如何低成本打造 Native 體驗?

 

這其中有很多不符合我們預期的地方:

  • html/JS 都等到點擊后才開始加載(這點可以通過預加載的手段解決,其中 HTML 的預加載可以通過 ServiceWorker 進行)。
  • framework 等 JS 在不同頁面間總是重復執行的。
  • 加載 API 的時機非常晚,而加載 API 一般是耗時很長而且可以并行的部分,理論上加載時機越早越好。

所以理想中的渲染流程應該是下圖這樣:

Web 端如何低成本打造 Native 體驗?

 

其實對于 Native 應用也是如此,用戶點擊時基本就會開始加載 API 并且執行下個頁面的邏輯。其實一個優化的比較好(做了 preload 等)的 SPA 也是類似的效果,我們提前加載好下個頁面的 vendor ,點擊時直接只執行下個頁面的邏輯即可。

然而實際上對于一個較大的現存站點來說(例如 m.alibaba.com ),把整個網站作為一個 SPA 來維護是不太現實的,一方面不能適應當前多人協作的現狀,另外一方面穩定性上也不能接受修改一個頁面整個網站都要發布的方案。

那么,如何低成本的把一個現有的網站改造成類 Native 的體驗呢?

同屏渲染

在有了上面的思考后,我們就在想,有沒有一個方案在不做改造的前提下,在用戶點擊后,立即開始數據的并行加載,同時把下個頁面動態的加載進來,選擇性的保留上個頁面的一些內容(例如正在加載中的數據, jsonp , framework 層的對象等)而隔絕其他部分的干擾。

于是針對我們的場景產出了一個同屏渲染的方案:LightHub,所謂同屏渲染,即渲染過程中頁面不需要被卸載,所有的渲染行為都在一個上下文中發生。

Web 端如何低成本打造 Native 體驗?

 

這里我們需要幾個東西:

  • 能直接附著到現有頁面上的沙箱,用于把頁面還原到初始狀態(同時允許保留部分共享的部分)
  • 過渡動畫
  • API 并行加載
  • 按照瀏覽器行為渲染 HTML
  • 按照瀏覽器行為觸發事件
Web 端如何低成本打造 Native 體驗?

 

沙箱

我們需要一個低成本把頁面還原會初始狀態、并且允許保留部分對象的沙箱機制,而且最好這個機制是可以直接低成本部署到現有頁面上的。其實這里的訴求和微前端碰到的問題類似,我們受 qiankun 的沙箱機制啟發,只需要在頁面的 <head> 中插入一小段內聯 JS 記錄:

  • window 上的全局變量
  • window/document 的 eventListener
  • 定時器:setInterval/setTimeout/requestAnimationFrame/requestIdleCallback
  • MutationObserver

在我們需要時我們只需要清空頁面的 DOM,還原變化的全局變量(這里和 qiankun 一樣采用的淺拷貝),eventListener,定時器和 MutationObserver,就能把頁面還原到初始狀態。

同時,記錄的狀態也能封存到一個對象中,當用戶從下個頁面 back 到上個頁面時,我們可以直接把狀態還原到頁面上。

這里就需要在清空頁面狀態時選擇性的保留一些需要保留的對象:例如公共的 Framework,JSONP 請求的標簽等。

過渡動畫

這一點其實就沒有多復雜了,在頁面不需要被卸載和重新加載后,我們可以在用戶點擊后立即展示一個動畫。目前采用的只是一個簡單的從右側 slide-in 的動畫。

需要注意的是,由于在繪制動畫的過程中我們往往正在執行下個頁面的邏輯,我們需要注意使用 GPU 來繪制動畫,從而確保動畫不會被 JS 執行阻塞。這一點對于低端機尤為關鍵。

API 并行加載

其實在有了上面的沙箱機制后,API 的并行加載就不是難事了,需要注意的是我們需要保護 API 并行加載本身的過程中產生的狀態(例如 setTimeout ),我們需要實現一個 runInSharedContext 確保這其中的定時器不會在頁面切換時被卸載。

  •  
  •  
  •  
  •  
runInSharedContext(() => {  // 這里的 setTimeout 不能是被記錄 & 清除的 setTimeout    setTimeout(() => window.sharedfetchDataPromise = fetch(res));});

而在下個頁面消費的只需要 window.sharedfetchDataPromise || fetch(url) 就能直接復用并行加載的 API 請求。

在我們的場景下為了讓這個問題更加開發者無感,封裝了一個叫做 redfox 的工具庫,在同一個頁面環境執行多次相同配置的請求會自動復用,不需要開發者手動判斷。

按照瀏覽器行為渲染 HTML

這可能是其中最復雜的部分了,在我們抓到下個頁面的 HTML 后,不能只是簡單的 document.innerHTML = nextHTML ,這樣會導致和普通的瀏覽器行為完全不一致,樣式加載會導致閃屏,腳本的執行順序不符合預期等等。

Web 端如何低成本打造 Native 體驗?

 

所以我們需要自己實現一個 renderHTML ,將抓到的 HTML 解析后模擬瀏覽器的行為進行渲染。

  • 播放動畫
  • 先通過樣式隱藏 body
  • 異步將 css append 到頁面,等到 head 中的 CSS 加載完成并且動畫播放完成后取消 body 的隱藏
  • 把 JS 按照類型和順序 append 到頁面
  • inline & 正常的阻塞后續 DOM 和 js 的 append
  • defer 丟到 defer 隊列中
  • async 異步執行,不阻塞后續
  • 按次序執行 defer 隊列

這個部分的行為比較復雜,需要在較多的場景進行測試,以及有相應的單元測試保障邏輯的正確性。

Web 端如何低成本打造 Native 體驗?

 

按瀏覽器行為觸發事件

其實和上面渲染 HTML 相似,在渲染的過程中需要按照瀏覽器的行為觸發相應的事件。

例如上個頁面卸載時依次觸發 beforeunload => pagehide => unload ,在下個頁面加載時先把 readyState 重置,然后按照次序觸發 domInteractive defer 的執行和 DOMContentLoaded 。

同樣的,單元測試在這個環節是必須的。

分析

Timeline 分析

從 Chrome 最后的 Timeline 看執行邏輯基本是符合我們預期的,點擊后的瞬間 API 開始加載并且基本上就開始全力執行下個頁面的渲染邏輯。

Framework 層的代碼基本也不需要再重復執行。

Web 端如何低成本打造 Native 體驗?

 

內存壓力

對于這種不卸載頁面的方案來說最容易引起擔憂的可能就是內存泄露問題,其實按照上面的沙箱機制,只要我們確保 DOM、全局變量、定時器、時間監聽等能夠被正確清除,與之相關的閉包等就不會賴在內存中不走。

從我們本地多次頻繁點擊切換頁面的反應看,內存隨著頁面的切換返回也會一次次回到初始狀態,從理論上不存在直接導致內存泄露的缺陷。

Web 端如何低成本打造 Native 體驗?

 

然而,由于我們允許在頁面間保留一部分的公共區域(上面稱之為 Service Layer),另外沙箱本身是一個約定沙箱而非安全沙箱(例如往 Element.prototype.xxx 屬性寫東西就無法被攔截),對于一些不規范的寫法仍然存在內存泄露的風險。

這一點可能需要和 Native 端類似的內存壓力監控等方式來長期觀察。

分階段打點

由于整個 HTML 渲染過程都是我們自己實現的,所以整個渲染的各個階段可以自己打點記錄一些時間,下面就是一個例子:API 從 JS 請求到拿到耗時 124ms ,而實際上整個取數據(提前并行取的)花了 350ms 。每一個 script 開始執行和執行耗時也可以通過這種方式打上來。

Web 端如何低成本打造 Native 體驗?

 

這也可以為我們的頁面優化提供一些指導,例如 JS 的執行時間是不是過晚,某段 JS 的執行時間是不是過長。

效果

最終的對比效果如下,左為同屏渲染,右為正常跳轉,從線上的數據看性能提升大約從 2.8s => 1.8s 。

Web 端如何低成本打造 Native 體驗?

 

除了異步渲染的頁面外,我們針對一些原先是 SSR 的頁面也做了非常低成本的接入(不需要改造頁面,但是享受到的受益相對也更有限)。

Web 端如何低成本打造 Native 體驗?

 

但僅僅是上面這種跳轉體驗和返回體驗的改善,就讓我們的 Just For U 模塊的曝光屏數有穩定 3% 的增長。

總結

總結一下:

  • 類似 SPA 體驗的客戶端渲染可以讓 Web 的體驗更接近 Native。
  • 同屏渲染是一種讓網站低成本漸進式實現 Native 化體驗的方式。
  • 更加沉浸的體驗確實會讓用戶有意愿進行更多地瀏覽。

局限

上面的方案仍然存在一些局限性,例如前面提到的需要開發者防范內存泄露的問題,同時因為 History API 的限制,頁面必須是同域的,否則跳轉的 URL 無法滿足預期。

未來

關注 Chrome 動態的同學也會了解到 Chrome 最近也退出了一個新的提案:Portal API,就是旨在解決我們上面提到的 Web 體驗割裂的問題。

能夠提供一個類似 iframe 的沙箱,以較低的成本實現頁面間的跳轉過渡等。在未來 Protal 普及后(至少 Chrome 發布, Safari 跟進后),我們就可以在新版本的瀏覽器中拋棄現在使用 JS 實現的沙箱機制,使用更加安全(且炫酷)的 Portal API 來實現同屏渲染。

在 Protal API 的支持下,我們也可以克服無法跨域的問題,按照目前的草案,Portal 是支持跨域跳轉的。

拓展閱讀

[1]qiankun (https://github.com/umijs/qiankun)

[2]Hands-on with Portals: seamless navigation on the Web

(https://web.dev/hands-on-portals/)

分享到:
標簽:Native
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定