用純 DOM 的方式結合 Puppeteer 自動生成網頁骨架屏

我花滿樓發表於2018-10-28

  骨架屏是在頁面資料尚未載入完成前先給使用者展示出頁面的大致結構,直到請求資料返回後再顯示真正的頁面內容;隨著單頁應用( SPA )的越來越流行,單頁應用的使用者體驗也越來越得到前端開發者的關注;為了優化使用者體驗,在資料到達使用者之前,往往會在頁面上加上 loading 的效果,而現在,越來越多的場景傾向於使用頁面的骨架替代單一的 loading 效果;

為什麼需要自動生成骨架屏?

  1. 提高效率,節約單獨編寫骨架屏程式碼的時間
  2. 替換原來單一的 loading 圖片效果
  3. 可以優化使用者體驗,在頁面資料尚未載入完成前先給使用者展示出頁面的大致結構,配合動畫效果,給使用者一種平滑切換的感覺

常見的方案:

  1. 手動編寫骨架屏程式碼
  2. 通過預渲染手動書寫的程式碼生成相應的骨架屏 比如:vue-skeleton-webpack-plugin
  3. 餓了麼內部的生成骨架頁面的工具 page-skeleton-webpack-plugin
  4. ..

a. 前兩者的前提都是需要開發者自己編寫骨架屏程式碼

b. 餓了麼的做的比較強大了,還有 UI 介面專門調整骨架屏

  • 對於複雜的頁面也會有不盡如人意的地方
  • 生成的骨架屏節點是基於頁面本身的結構和 CSS,存在巢狀比較深的情況,體積不會太小
  • 只支援 history 模式.

我們的方案是:用純 DOM 的方式結合 Puppeteer 自動生成網頁骨架屏

  • 編寫操作 DOM 的 Javascript 指令碼
    • 遍歷可見區域可見的 DOM 節點 包括:非隱藏元素、寬高大於 0 的元素、非透明元素、內容不是空格的元素、位於瀏覽視窗可見區域內的元素
    • 針對(背景)圖片、文字、表單項、音訊視訊等區域,算出其所佔區域的寬、高、距離視口的絕對距離等資訊
    • 對於符合生成條件的區域,一視同仁,生成相應區域的顏色塊
    • “一視同仁”即對於符合條件的區域,不區分具體元素,不用考慮結構層級,不考慮樣式,統一生成 div 的顏色塊
    • 該指令碼的執行環境決定了獲取到的元素尺寸與相關距離單位不可控,可能需要做轉換,比如用的 rem、em、vh 等;我們採用比較簡單的方式,不取 style 的尺寸相關的值,而是通過 getBoundingClientRect 獲取寬、高、距離視口距離的絕對值,與當前裝置的寬高,計算出相應的百分比作為顏色塊的單位,這樣來適配不同裝置
    • 對於頁面結構比較複雜或者大圖片比較多的頁面,會出現不盡如人意的地方,我們通過 includeElement(node, draw)和 init 兩個鉤子函式來支援自定義的微調
    • 以上就能夠直接跑在瀏覽器生成骨架屏程式碼了,手動新增到應用頁面
    const createSkeletonHTML = require('DrawPageStructure/evalDOM')

    createSkeletonHTML({
        // ...
        background: 'red',
        animation: 'opacity 1s linear infinite;'
    }).then(skeletonHTML => {
        console.log(skeletonHTML)
    }).catch(e => {
        console.error(e)
    })
複製程式碼

  直接在瀏覽器端執行,在控制檯列印當前頁面骨架屏節點,複製新增到應用頁面,但是該方式不夠自動化,我們該讓骨架屏自動生成並新增到應用頁面

  • Puppeteer

Puppeteer 是谷歌官方出品的一個可以控制 headless Chrome 的 Node 庫。可以通過 Puppeteer 的提供的 api 直接控制 Chrome 模擬大部分使用者操作來進行 UI Test 或者作為爬蟲訪問頁面來收集資料。

Puppeteer 提供執行環境和匯出方式

  1. 使用 puppeteer 執行需要生成骨架屏的頁面
  2. 將之前編寫的 Javascript 指令碼通過 puppeteer 提前注入到該頁面,這樣即可執行該指令碼,並生成骨架屏所需的 DOM 節點
  3. 將自動生成的骨架屏 DOM 片段插入到應用頁面的入口節點
const evalDOM = require('../evalDOM');

await page.goto(url, {waitUntil: 'networkidle0'});
const skeletonHTML = await page.evaluate.call(page, evalDOM, ...args);
複製程式碼

小結

  1. 核心在於 DOM 操作,puppeteer 僅提供執行環境和匯出方式
  2. 只要能訪問的頁面都能生成,history 與 hash 模式無限制
  3. 不受專案和框架的限制,vue 和 react 等專案零修改即可複用
  4. 生成色塊的單位為百分比,不同裝置自適應
  5. 不需要 css-tree 來提取樣式,不依賴頁面本身的佈局結構,生成扁平的 DOM 節點體積特別小
  6. 支援自定義生成方式與匯出方式

還有很多細節優化中,歡迎感興趣的小夥伴一起加入!

詳細程式碼和使用方式請移步: github.com/famanoder/D…

歡迎 star !,歡迎提 PR !

相關文章