您當前的位置:首頁 > 攝影

網頁骨架屏自動生成方案(dps)

作者:由 花滿樓 發表于 攝影時間:2019-07-20

什麼是骨架屏?

什麼是骨架屏呢?骨架屏(Skeleton Screen)是指在頁面資料載入完成前,先給使用者展示出頁面的大致結構(灰色佔位圖),在拿到介面資料後渲染出實際頁面內容然後替換掉。Skeleton Screen 是近兩年開始流行的載入控制元件,本質上是介面載入過程中的過渡效果。

假如能在載入前把網頁的大概輪廓預先顯示,接著再逐漸載入真正內容,這樣既降低了使用者的焦灼情緒,又能使介面載入過程變得自然通暢,不會造成網頁長時間白屏或者閃爍。這就是 Skeleton Screen !

Skeleton Screen 能給人一種頁面內容“已經渲染出一部分”的感覺,相較於傳統的 loading 效果,在一定程度上可提升使用者體驗。

骨架屏的實現方案

目前生成骨架屏的技術方案大概有三種:

1。 使用圖片、svg 或者手動編寫骨架屏程式碼:使用 HTML + CSS 的方式,我們可以很快的完成骨架屏效果,但是面對視覺設計的改版以及需求的更迭,我們對骨架屏的跟進修改會非常被動,這種機械化重複勞作的方式此時未免顯得有些機動性不足;

2。 透過預渲染手動書寫的程式碼生成相應的骨架屏:該方案做的比較成熟的是 vue-skeleton-webpack-plugin,透過 vueSSR 結合 webpack 在構建時渲染寫好的 vue 骨架屏元件,將預渲染生成的 DOM 節點和相關樣式插入到最終輸出的 html 中。

// webpack。conf。js

const

SkeletonWebpackPlugin

=

require

‘vue-skeleton-webpack-plugin’

);

plugins

//。。。

new

SkeletonWebpackPlugin

({

webpackConfig

{

entry

{

app

resolve

‘。/src/entry-skeleton。js’

}

}

})

該方案的前提同樣是編寫相應頁面的骨架屏元件,然後預渲染生成骨架屏所需的 DOM 節點,但由於該方案與 vue 相關技術直接關聯,在當今前端框架三分天下的大環境下,我們可能需要一個更加靈活、可控的方案;

3 。 餓了麼內部的生成骨架頁面的工具:該方案透過一個 webpack 外掛 page-skeleton-webpack-plugin 的方式與專案開發無縫整合,屬於在自動生成骨架屏方面做的非常強大的了,並且可以啟動 UI 介面專門調整骨架屏,但是在面對複雜的頁面也會有不盡如人意的地方,而且生成的骨架屏節點是基於頁面本身的結構和 CSS,存在巢狀比較深的情況,體積不會太小,並且只支援 history 模式。

// webpack。conf。js

const

HtmlWebpackPlugin

=

require

‘html-webpack-plugin’

const

{

SkeletonPlugin

}

=

require

‘page-skeleton-webpack-plugin’

const

path

=

require

‘path’

plugins

//。。。

new

HtmlWebpackPlugin

({

// Your HtmlWebpackPlugin config

}),

new

SkeletonPlugin

({

pathname

path

resolve

__dirname

`

${

customPath

}

`

),

// 用來儲存 shell 檔案的地址

staticDir

path

resolve

__dirname

‘。/dist’

),

// 最好和 `output。path` 相同

routes

‘/’

‘/search’

],

// 將需要生成骨架屏的路由新增到陣列中

})

我們的實現方案

後來仔細想想,骨架屏這幅樣子不是和一堆顏色塊拼起來的頁面一樣嗎?對比現有的骨架屏方案,這個想法有點“走捷徑”的感覺。再進一步思考,這些色塊基於當前頁面去分析節點來生成,不如來段 JS 分析頁面節點,一頓 DOM 操作生成顏色塊拼成骨架屏。那麼問題來了,該怎麼樣精確的分析頁面節點,不同節點又該生成什麼樣的色塊呢?

既然骨架屏代表了頁面的大致結構,那麼需要先用 js 對頁面的結構進行分析。分析之前,我們需要制定一種規則,以確定需要排除哪些節點?哪些種類的節點需要生成顏色塊?生成的顏色塊如何定位等等。我們初步定下的規則如下:

1。 只遍歷可見區域可見的 DOM 節點,包括:

非隱藏元素、寬高大於 0 的元素、非透明元素、內容不是空格的元素、位於瀏覽視窗可見區域內的元素等;

2。 針對(背景)圖片、文字、表單項、音訊影片、Canvas、自定義特徵的塊等區域來生成顏色塊;

3。 頁面節點使用的樣式不可控,所以不可取 style 的尺寸相關的值,可透過 getBoundingClientRect 獲取節點寬、高、距離視口距離的絕對值,計算出與當前裝置的寬高對應的百分比作為顏色塊的單位,來適配不同裝置;

基於這套規則,我們開始生成骨架屏:

首先,確定一個 rootNode 作為入口節點,比如 document。body,同時方便以後擴充套件到生成頁面內區域性的骨架屏,由此入口進行遞迴遍歷和篩選,初步排除不可見節點。

function

isHideStyle

node

{

return

getStyle

node

‘display’

===

‘none’

||

getStyle

node

‘visibility’

===

‘hidden’

||

getStyle

node

‘opacity’

==

0

||

node

hidden

}

接下來判斷元素特徵,確定是否符合生成條件,對於符合條件的區域,”一視同仁”生成相應區域的顏色塊。”一視同仁”即對於符合條件的區域不區分具體元素、不考慮結構層級、不考慮樣式,統一根據該區域與視口的絕對距離值生成 div 的顏色塊。之所以這樣是因為生成的節點是扁平的,體積比較小,同時避免額外的讀取樣式表、透過抽離樣式維持骨架屏的外觀,這種統一生成的方式使得骨架屏的節點更可控。基於那上述“走捷徑”的想法,該方法生成的骨架屏是由純 DOM 顏色塊拼成的。

生成顏色塊的方法:

const

blocks

=

[];

// width,height,top,left 都是算好的百分比

function

drawBlock

({

width

height

top

left

zIndex

=

9999999

background

radius

}

=

{})

{

const

styles

=

‘position: fixed’

‘z-index: ’

+

zIndex

‘top: ’

+

top

+

‘%’

‘left: ’

+

left

+

‘%’

‘width: ’

+

width

+

‘%’

‘height: ’

+

height

+

‘%’

‘background: ’

+

background

];

radius

&&

radius

!=

‘0px’

&&

styles

push

‘border-radius: ’

+

radius

);

// animation && styles。push(‘animation: ’ + animation);

blocks

push

`

${

styles

join

‘;’

}

”>

`

);

}

繪製顏色塊並不難,繪製之前的分析確認才是這個方案真正的核心和難點。比如,對於頁面結構比較複雜或者大圖片比較多的頁面,由圖片拼接的區域沒有邊界,生成的顏色塊就會緊挨著,出現不盡如人意的地方。再比如,一個包含很多符合生成條件的小塊的 card 塊區域,是以 card 塊為準還是以裡面的小塊為準來生成顏色塊呢?如果以小塊為準,繪製結果可能給人的感覺壓根就不是一個 card 塊,再加上佈局方式和樣式的可能性太多,大大增加了不確定因素。而如果以 card 塊為準生成顏色塊的話還要對 card 塊做專門的規則。

目前來說,對於頁面結構不是特別複雜,不是滿屏圖片的,不是佈局方式特別“飄逸“的場景,該方式已經可以生成比較理想的骨架屏了。而對於那些與預期相差較遠的情況,我們提供了兩個鉤子函式可供微調:

1。 init 函式,在開始遍歷節點之前執行,適合刪除干擾節點等操作。

2。 includeElement(node, draw) 函式,可在遍歷到指定節點時,調 用 draw 方法進行自定義繪製。

透過以上步驟就能夠直接在瀏覽器中生成骨架屏程式碼了。

在瀏覽器裡執行

由於我們的方案出發點是透過單純的 DOM 操作,遍歷頁面上的節點,根據制定的規則生成相應區域的顏色塊,最終形成頁面的骨架屏,所以核心程式碼完全可以直接跑在瀏覽器端;

const

createSkeletonHTML

=

require

‘draw-page-structure/evalDOM’

createSkeletonHTML

({

// 。。。

background

‘red’

animation

‘opacity 1s linear infinite;’

})。

then

skeletonHTML

=>

{

console

log

skeletonHTML

})。

catch

e

=>

{

console

error

e

})

結合 Puppeteer 自動生成骨架屏

雖然該方式已經可以生成骨架屏程式碼了,但是還是不夠自動化,為了讓生成的骨架屏程式碼自動載入進指定頁面。於是,我們開發了一個配套的 CLI 工具。這個工具透過 Puppeteer 執行頁面,並把 evalDOM。js 指令碼注入頁面自動執行,執行的結果是生成的骨架屏程式碼被插入到應用頁面。

我們的方案大概思路如下:

網頁骨架屏自動生成方案(dps)

接下來看看如何使用 CLI 工具生成骨架屏,最多隻需如下四步:

1。 全域性安裝,npm i draw-page-structure – g

2。 dps init 生成配置檔案 dps。config。js

3。 修改 dps。config。js 進行相關配置

4。 dps start 開始生成骨架屏

只需簡單幾步,然而並沒有繁瑣的配置:

網頁骨架屏自動生成方案(dps)

一般來說,你需要按自己的專案情況來配置 dps。config。js ,常見的配置項有:

url: 待生成骨架屏的頁面地址

output。filepath: 生成的骨架屏節點寫入的檔案

output。injectSelector: 骨架屏節點插入的位置,預設 #app

background: 骨架屏主題色

animation: css3 動畫屬性

rootNode: 真對某個模組生成骨架屏

device: 裝置型別,預設 mobile

extraHTTPHeaders: 新增請求頭

init: 開始生成之前的操作

includeElement(node, draw): 定製某個節點如何生成

writePageStructure(html, filepath): 回撥的骨架屏節點

詳細程式碼及工具的使用請移步 [Github]( famanoder/dps);

初步實現的效果:

* 京東 PLUS 會員正式中首頁:

網頁骨架屏自動生成方案(dps)

* 京東 PLUS 會員正式中首頁,透過該方案生成的骨架屏效果:

網頁骨架屏自動生成方案(dps)

* 移動端百度首頁,透過該方案生成的骨架屏效果:

網頁骨架屏自動生成方案(dps)

總結

以上就是基於 DOM 的骨架屏自動生成方案,其核心是 evalDOM 函式。這個方案在很多場景下的表現還是令人滿意的。不過,網頁佈局和樣式組合的可能性太多,想要在各種場景下都獲得理想的效果,還有很長的路要走,但既然已經在路上,就勇敢的向前吧!

歡迎 star,歡迎提 PR !

標簽: 骨架  生成  頁面  節點  webpack