<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          骨架屏生成方案

          共 6679字,需瀏覽 14分鐘

           ·

          2021-03-05 18:34

          作者:花滿樓
          https://juejin.cn/post/6844903893525069838

          什么是骨架屏

          什么是骨架屏呢?骨架屏(Skeleton Screen)是指在頁面數(shù)據(jù)加載完成前,先給用戶展示出頁面的大致結(jié)構(gòu)(灰色占位圖),在拿到接口數(shù)據(jù)后渲染出實際頁面內(nèi)容然后替換掉。Skeleton Screen 是近兩年開始流行的加載控件,本質(zhì)上是界面加載過程中的過渡效果。假如能在加載前把網(wǎng)頁的大概輪廓預(yù)先顯示,接著再逐漸加載真正內(nèi)容,這樣既降低了用戶的焦灼情緒,又能使界面加載過程變得自然通暢,不會造成網(wǎng)頁長時間白屏或者閃爍。這就是Skeleton Screen!

          Skeleton Screen 能給人一種頁面內(nèi)容“已經(jīng)渲染出一部分”的感覺,相較于傳統(tǒng)的 loading 效果,在一定程度上可提升用戶體驗。

          骨架屏的實現(xiàn)方案

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

          1. 使用圖片、svg 或者手動編寫骨架屏代碼:使用 HTML + CSS 的方式,我們可以很快的完成骨架屏效果,但是面對視覺設(shè)計的改版以及需求的更迭,我們對骨架屏的跟進修改會非常被動,這種機械化重復(fù)勞作的方式此時未免顯得有些機動性不足;
          2. 通過預(yù)渲染手動書寫的代碼生成相應(yīng)的骨架屏:該方案做的比較成熟的是 vue-skeleton-webpack-plugin,通過 vueSSR 結(jié)合 webpack 在構(gòu)建時渲染寫好的 vue 骨架屏組件,將預(yù)渲染生成的 DOM 節(jié)點和相關(guān)樣式插入到最終輸出的 html 中。
           // webpack.conf.js
           const SkeletonWebpackPlugin = require('vue-skeleton-webpack-plugin');

           plugins: [
            //...
            new SkeletonWebpackPlugin({
              webpackConfig: {
                entry: {
                  app: resolve('./src/entry-skeleton.js')
                }
              }
            })
           ]

          該方案的前提同樣是編寫相應(yīng)頁面的骨架屏組件,然后預(yù)渲染生成骨架屏所需的 DOM 節(jié)點,但由于該方案與 vue 相關(guān)技術(shù)直接關(guān)聯(lián),在當(dāng)今前端框架三分天下的大環(huán)境下,我們可能需要一個更加靈活、可控的方案;

          1. 餓了么內(nèi)部的生成骨架頁面的工具:該方案通過一個 webpack 插件 page-skeleton-webpack-plugin 的方式與項目開發(fā)無縫集成,屬于在自動生成骨架屏方面做的非常強大的了,并且可以啟動 UI 界面專門調(diào)整骨架屏,但是在面對復(fù)雜的頁面也會有不盡如人意的地方,而且生成的骨架屏節(jié)點是基于頁面本身的結(jié)構(gòu)和 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'], // 將需要生成骨架屏的路由添加到數(shù)組中
            })
           ]

          我們的實現(xiàn)方案

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

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

          1. 只遍歷可見區(qū)域可見的 DOM 節(jié)點,包括:非隱藏元素、寬高大于 0 的元素、非透明元素、內(nèi)容不是空格的元素、位于瀏覽窗口可見區(qū)域內(nèi)的元素等;
          2. 針對(背景)圖片、文字、表單項、音頻視頻、Canvas、自定義特征的塊等區(qū)域來生成顏色塊;
          3. 頁面節(jié)點使用的樣式不可控,所以不可取 style 的尺寸相關(guān)的值,可通過 getBoundingClientRect 獲取節(jié)點寬、高、距離視口距離的絕對值,計算出與當(dāng)前設(shè)備的寬高對應(yīng)的百分比作為顏色塊的單位,來適配不同設(shè)備;

          基于這套規(guī)則,我們開始生成骨架屏:首先,確定一個 rootNode 作為入口節(jié)點,比如 document.body,同時方便以后擴展到生成頁面內(nèi)局部的骨架屏,由此入口進行遞歸遍歷和篩選,初步排除不可見節(jié)點。

          function isHideStyle(node{
              return getStyle(node, 'display') === 'none' || 
                  getStyle(node, 'visibility') === 'hidden' || 
                  getStyle(node, 'opacity') == 0 ||
                  node.hidden;
          }

          接下來判斷元素特征,確定是否符合生成條件,對于符合條件的區(qū)域,”一視同仁”生成相應(yīng)區(qū)域的顏色塊。”一視同仁”即對于符合條件的區(qū)域不區(qū)分具體元素、不考慮結(jié)構(gòu)層級、不考慮樣式,統(tǒng)一根據(jù)該區(qū)域與視口的絕對距離值生成 div 的顏色塊。之所以這樣是因為生成的節(jié)點是扁平的,體積比較小,同時避免額外的讀取樣式表、通過抽離樣式維持骨架屏的外觀,這種統(tǒng)一生成的方式使得骨架屏的節(jié)點更可控。基于那上述“走捷徑”的想法,該方法生成的骨架屏是由純 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(`<div style="${ styles.join(';') }"></div>`);
          }

          繪制顏色塊并不難,繪制之前的分析確認才是這個方案真正的核心和難點。比如,對于頁面結(jié)構(gòu)比較復(fù)雜或者大圖片比較多的頁面,由圖片拼接的區(qū)域沒有邊界,生成的顏色塊就會緊挨著,出現(xiàn)不盡如人意的地方。再比如,一個包含很多符合生成條件的小塊的 card 塊區(qū)域,是以 card 塊為準還是以里面的小塊為準來生成顏色塊呢?如果以小塊為準,繪制結(jié)果可能給人的感覺壓根就不是一個 card 塊,再加上布局方式和樣式的可能性太多,大大增加了不確定因素。而如果以 card 塊為準生成顏色塊的話還要對 card 塊做專門的規(guī)則。

          目前來說,對于頁面結(jié)構(gòu)不是特別復(fù)雜,不是滿屏圖片的,不是布局方式特別“飄逸“的場景,該方式已經(jīng)可以生成比較理想的骨架屏了。而對于那些與預(yù)期相差較遠的情況,我們提供了兩個鉤子函數(shù)可供微調(diào):

          1. init 函數(shù),在開始遍歷節(jié)點之前執(zhí)行,適合刪除干擾節(jié)點等操作。
          2. includeElement(node, draw) 函數(shù),可在遍歷到指定節(jié)點時,調(diào) 用 draw 方法進行自定義繪制。

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

          在瀏覽器里運行

          由于我們的方案出發(fā)點是通過單純的 DOM 操作,遍歷頁面上的節(jié)點,根據(jù)制定的規(guī)則生成相應(yīng)區(qū)域的顏色塊,最終形成頁面的骨架屏,所以核心代碼完全可以直接跑在瀏覽器端;

          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)
              })

          結(jié)合 Puppeteer 自動生成骨架屏

          雖然該方式已經(jīng)可以生成骨架屏代碼了,但是還是不夠自動化,為了讓生成的骨架屏代碼自動加載進指定頁面。于是,我們開發(fā)了一個配套的 CLI 工具。這個工具通過 Puppeteer 運行頁面,并把 evalDOM.js 腳本注入頁面自動執(zhí)行,執(zhí)行的結(jié)果是生成的骨架屏代碼被插入到應(yīng)用頁面。

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

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

          1. 全局安裝,npm i draw-page-structure –g
          2. dps init 生成配置文件 dps.config.js
          3. 修改 dps.config.js 進行相關(guān)配置
          4. dps start 開始生成骨架屏

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

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

          • url: 待生成骨架屏的頁面地址
          • output.filepath: 生成的骨架屏節(jié)點寫入的文件
          • output.injectSelector: 骨架屏節(jié)點插入的位置,默認 #app
          • background: 骨架屏主題色
          • animation: css3動畫屬性
          • rootNode: 真對某個模塊生成骨架屏
          • device: 設(shè)備類型,默認 mobile
          • extraHTTPHeaders: 添加請求頭
          • init: 開始生成之前的操作
          • includeElement(node, draw): 定制某個節(jié)點如何生成
          • writePageStructure(html, filepath): 回調(diào)的骨架屏節(jié)點

          詳細代碼及工具的使用請移步 Github;

          初步實現(xiàn)的效果:

          • 京東PLUS會員正式中首頁:
          • 京東PLUS會員正式中首頁,通過該方案生成的骨架屏效果:
          • 移動端百度首頁:
          • 移動端百度首頁,通過該方案生成的骨架屏效果:

          總結(jié)

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


          瀏覽 73
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  2025天天操 | 亚洲国产中文字幕在线播放 | 乱伦电影毛片 | 四位少妇按摩欧美三级 | 欧美成人生活片一区三区 |