一個簡易的預渲染自動骨架屏方案

blank

一個簡易的預渲染自動骨架屏方案

前言

我們都知道,目前傳統的SPA 網頁在完成腳本加載後,通常還需要進行接口請求,拿到遠端數據後才能進行完整地內容呈現

而在接口請求的過程中,為了過渡無數據的空白場景,並提示用戶“數據請求中”,常用的方法為做一個loading 動畫效果

而在用戶胃口越來越刁的今天,一個簡單的loading 效果已經不太能安撫用戶了,而骨架屏就是一種安撫用戶的進階方案

最終成品鏈接(懶人用): auto-skeleton-plugin

什麼是骨架屏?

簡單來說,骨架屏就是在還未產生可閱讀內容時,先將網頁的大致結構框架呈現給用戶,以達到安撫用戶等待過程中的不耐煩心理、提升用戶存留的效果

blank

骨架屏的實現,通常有兩種方式

  1. 手動書寫骨架
  2. 自動生成骨架

手動寫骨架的方式,好處是可以做出高定制性的骨架效果,缺點是開發成本大,效率低,但本文不對此方式進行展開

那麼如何實現自動骨架屏的效果呢?一個簡單的方式是:將已有內容的樣式進行調整,生成對應的骨架效果,例如以下代碼,可以將所有文字內容,變成骨架條塊

functiongenerateSkeleton(){//文字節點;[...document.querySelectorAll('*')].filter((node)=>!['script','style','html','body','head','title'].includes(node.tagName.toLowerCase())).map((node)=>[...node.childNodes].filter((node)=>nodeinstanceofText)).flat(Infinity).forEach((node)=>{letspan=document.createElement('span')node.parentNode.insertBefore(span,node)span.appendChild(node)span.style=`background: #f2f2f2;color: transparent !important;`})}
blank

這樣,只要我們完善不同內容如圖片、圖標等元素的骨架化過程,就可以得到一個相對可用的內容骨架化效果

自動骨架化的好處是,生成骨架的效率高,開發成本很低,但缺點是定制性相對較差,需要根據已有內容來確定骨架效果

但這有一個問題,我們期望是在應用剛打開時,還未請求數據前就呈現骨架,目前顯然是做不到的

而我們可以藉助“預渲染”來實現期望的效果

什麼是預渲染?

預渲染類似服務端渲染,它的過程大概是這樣的:在應用完成打包後,立刻啟動一個headless瀏覽器進行頁面訪問,再將訪問的結果輸出成html文件的渲染過程

通俗地說就是:打包完後本地先訪問看一看,看到啥就“截個屏”存起來,然後輸出一個html 文件,覆蓋原本構建生成的index.html

這樣,用戶訪問打包好的index.html 時,看到的就是一個有內容的網頁

那麼,借助預渲染,我們可以將上述自動骨架屏的過程,放在headless瀏覽器加載出網頁內容後,具備內容後再將內容骨架化,再輸出成html,就可以實現用戶訪問時,還未請求數據前,先呈現骨架的效果

自動骨架屏的過程實現

我們可以參考一個常用的預渲染的webpack插件prerender-spa-plugin來實現這個過程

查閱源碼可知,這個插件並未實現核心渲染過程,其實只是將prerenderer包裝成了webpack插件的形式,並承擔了將最終結果輸出成html產物文件的功能

關鍵源碼: github.com/chrisvfritz/

...constPrerenderer=require('@prerenderer/prerenderer')...functionPrerenderSPAPlugin(...args){...constafterEmit=(compilation,done)=>{constPrerendererInstance=newPrerenderer(this._options)PrerendererInstance.initialize().then(()=>{returnPrerendererInstance.renderRoutes(this._options.routes||[])})...}...}...module.exports=PrerenderSPAPlugin

prerenderer承擔的則是使用headless瀏覽器訪問網頁,並輸出訪問結果的功能,其官方內置了兩種可選的headless瀏覽器: puppeteerjsdom

由於puppeteer需要下載的內容較大,我們考慮使用較輕量的jsdom來完成這個效果

在翻閱了部分renderer-jsdom的源碼後,可以找到headless瀏覽器採集網頁內容的部分

關鍵源碼: github.com/JoshTheDerf/

我們只需要在採集網頁內容前,對內容進行骨架化,就可以得到期望的效果

constJSDOM=require('jsdom/lib/old-api.js').jsdom...constgetPageContents=function(window,options,originalRoute){...returnnewPromise((resolve,reject)=>{...functioncaptureDocument(){//此處可在輸出html結果前,先對網頁內容進行骨架化// generateSkeleton就是上邊咱們整理出來的dom操作實現自動骨架化過程generateSkeleton(window)constresult={...html:serializeDocument(window.document)}...returnresult}...}...}classJSDOMRenderer{...asyncrenderRoutes(routes,Prerenderer){...constresults=Promise.all(routes.map(route=>limiter(()=>{returnnewPromise((resolve,reject)=>{JSDOM.env({url:`http://127.0.0.1:${rootOptions.server.port}${route}`,...})}).then(window=>{returngetPageContents(window,this._rendererOptions,route)})})))...returnresults}...}module.exports=JSDOMRenderer

至此,簡易自動骨架屏效果的方案已經敘述完成,整個過程,需要我們自己動手的主要是骨架化過程的部分,其餘之處,都可通過參考已有過程實現來完成,那麼具體過程實現,此處就不再繼續展開了,動手能力強的小伙伴,大概可以自己一把梭出來

結尾

預渲染方案待展開的功能還是有不少的,例如

  1. 如何內聯樣式? (這條比較容易做到,借助jsdom 自身的resourceLoader 足矣)
  2. 如何保留關鍵樣式,去除無用樣式? (有一定難度,可參考uncss ,配合postcss實現)
  3. 預渲染性能是否充足,能否用來做SSR? (jsdom渲染速度較快,此處進行了實踐santi

以下是上述方案的自動骨架插件實現,目前自動骨架化的過程比較簡陋,只具備了基礎的可用性,也希望能得到大家的幫助,共同完善自動骨架化的過程

What do you think?

Written by marketer

blank

swc-node, 最快的TypeScript/JavaScript compiler

blank

前端+ AI ——從圖片識別UI樣式