1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        基于DOM的骨架屏自動(dòng)生成方案

        共 6865字,需瀏覽 14分鐘

         ·

        2021-05-08 20:25

        點(diǎn)擊上方關(guān)注 TianTianUp,一起學(xué)習(xí),天天進(jìn)步

        大家好,我是TianTian

        分享的內(nèi)容是基于DOM的骨架屏自動(dòng)生成方案。

        內(nèi)容來(lái)自知乎,作者花滿樓,更多內(nèi)容點(diǎn)閱讀原文。

        什么是骨架屏?

        什么是骨架屏呢?骨架屏(Skeleton Screen)是指在頁(yè)面數(shù)據(jù)加載完成前,先給用戶展示出頁(yè)面的大致結(jié)構(gòu)(灰色占位圖),在拿到接口數(shù)據(jù)后渲染出實(shí)際頁(yè)面內(nèi)容然后替換掉。Skeleton Screen 是近兩年開始流行的加載控件,本質(zhì)上是界面加載過(guò)程中的過(guò)渡效果。

        假如能在加載前把網(wǎng)頁(yè)的大概輪廓預(yù)先顯示,接著再逐漸加載真正內(nèi)容,這樣既降低了用戶的焦灼情緒,又能使界面加載過(guò)程變得自然通暢,不會(huì)造成網(wǎng)頁(yè)長(zhǎng)時(shí)間白屏或者閃爍。這就是 Skeleton Screen !

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

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

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

        1. 使用圖片、svg 或者手動(dòng)編寫骨架屏代碼:使用 HTML + CSS 的方式,我們可以很快的完成骨架屏效果,但是面對(duì)視覺(jué)設(shè)計(jì)的改版以及需求的更迭,我們對(duì)骨架屏的跟進(jìn)修改會(huì)非常被動(dòng),這種機(jī)械化重復(fù)勞作的方式此時(shí)未免顯得有些機(jī)動(dòng)性不足;

        2. 通過(guò)預(yù)渲染手動(dòng)書寫的代碼生成相應(yīng)的骨架屏:該方案做的比較成熟的是 vue-skeleton-webpack-plugin,通過(guò) vueSSR 結(jié)合 webpack 在構(gòu)建時(shí)渲染寫好的 vue 骨架屏組件,將預(yù)渲染生成的 DOM 節(jié)點(diǎn)和相關(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è)面的骨架屏組件,然后預(yù)渲染生成骨架屏所需的 DOM 節(jié)點(diǎn),但由于該方案與 vue 相關(guān)技術(shù)直接關(guān)聯(lián),在當(dāng)今前端框架三分天下的大環(huán)境下,我們可能需要一個(gè)更加靈活、可控的方案;

        3 . 餓了么內(nèi)部的生成骨架頁(yè)面的工具:該方案通過(guò)一個(gè) webpack 插件 page-skeleton-webpack-plugin 的方式與項(xiàng)目開發(fā)無(wú)縫集成,屬于在自動(dòng)生成骨架屏方面做的非常強(qiáng)大的了,并且可以啟動(dòng) UI 界面專門調(diào)整骨架屏,但是在面對(duì)復(fù)雜的頁(yè)面也會(huì)有不盡如人意的地方,而且生成的骨架屏節(jié)點(diǎn)是基于頁(yè)面本身的結(jié)構(gòu)和 CSS,存在嵌套比較深的情況,體積不會(huì)太小,并且只支持 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}`), // 用來(lái)存儲(chǔ) shell 文件的地址
            staticDir: path.resolve(__dirname, './dist'), // 最好和 `output.path` 相同
            routes: ['/''/search'], // 將需要生成骨架屏的路由添加到數(shù)組中
          })
         ]

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

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

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

        1. 只遍歷可見(jiàn)區(qū)域可見(jiàn)的 DOM 節(jié)點(diǎn),包括:

        非隱藏元素、寬高大于 0 的元素、非透明元素、內(nèi)容不是空格的元素、位于瀏覽窗口可見(jiàn)區(qū)域內(nèi)的元素等;

        1. 針對(duì)(背景)圖片、文字、表單項(xiàng)、音頻視頻、Canvas、自定義特征的塊等區(qū)域來(lái)生成顏色塊;

        2. 頁(yè)面節(jié)點(diǎn)使用的樣式不可控,所以不可取 style 的尺寸相關(guān)的值,可通過(guò) getBoundingClientRect 獲取節(jié)點(diǎn)寬、高、距離視口距離的絕對(duì)值,計(jì)算出與當(dāng)前設(shè)備的寬高對(duì)應(yīng)的百分比作為顏色塊的單位,來(lái)適配不同設(shè)備;

        基于這套規(guī)則,我們開始生成骨架屏:

        首先,確定一個(gè) rootNode 作為入口節(jié)點(diǎn),比如 document.body,同時(shí)方便以后擴(kuò)展到生成頁(yè)面內(nèi)局部的骨架屏,由此入口進(jìn)行遞歸遍歷和篩選,初步排除不可見(jiàn)節(jié)點(diǎn)。

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

        接下來(lái)判斷元素特征,確定是否符合生成條件,對(duì)于符合條件的區(qū)域,”一視同仁”生成相應(yīng)區(qū)域的顏色塊?!币灰曂省奔磳?duì)于符合條件的區(qū)域不區(qū)分具體元素、不考慮結(jié)構(gòu)層級(jí)、不考慮樣式,統(tǒng)一根據(jù)該區(qū)域與視口的絕對(duì)距離值生成 div 的顏色塊。之所以這樣是因?yàn)樯傻墓?jié)點(diǎn)是扁平的,體積比較小,同時(shí)避免額外的讀取樣式表、通過(guò)抽離樣式維持骨架屏的外觀,這種統(tǒng)一生成的方式使得骨架屏的節(jié)點(diǎn)更可控?;谀巧鲜觥白呓輳健钡南敕?,該方法生成的骨架屏是由純 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>`);
        }

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

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

        1. init 函數(shù),在開始遍歷節(jié)點(diǎn)之前執(zhí)行,適合刪除干擾節(jié)點(diǎn)等操作。

        2. includeElement(node, draw) 函數(shù),可在遍歷到指定節(jié)點(diǎn)時(shí),調(diào) 用 draw 方法進(jìn)行自定義繪制。

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

        在瀏覽器里運(yùn)行

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

        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 自動(dòng)生成骨架屏

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

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

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

        1. 全局安裝,npm i draw-page-structure – g

        2. dps init 生成配置文件 dps.config.js

        3. 修改 dps.config.js 進(jìn)行相關(guān)配置

        4. dps start 開始生成骨架屏

        只需簡(jiǎn)單幾步,然而并沒(méi)有繁瑣的配置:

        一般來(lái)說(shuō),你需要按自己的項(xiàng)目情況來(lái)配置 dps.config.js ,常見(jiàn)的配置項(xiàng)有:

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

        詳細(xì)代碼及工具的使用請(qǐng)移步 Github;

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

        京東 PLUS 會(huì)員正式中首頁(yè):

        效果1

        * 京東 PLUS 會(huì)員正式中首頁(yè),通過(guò)該方案生成的骨架屏效果:

        * 移動(dòng)端百度首頁(yè),通過(guò)該方案生成的骨架屏效果:

        效果3

        最后

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

        面試題交流群持續(xù)開放,已經(jīng)分享了近 許多 個(gè)面經(jīng)。 

        加我微信: DayDay2021,備注面試,拉你進(jìn)群~


        我是 TianTian,我們下篇見(jiàn)~

        1. 幾張動(dòng)圖帶你回顧JS的變量提升
        2. 幾張動(dòng)圖帶你回顧event loop
        3. 看了就會(huì)的瀏覽器幀原理
        瀏覽 22
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            国产精品扒开腿做爽爽爽免费网站 | 欧美性受XXXX黑人XYX | 国产乱码色情一区二区三区四区 | ssni—392侵犯新任女教师 | 国精产品一品二品国精品69XX | 小黄片在线免费 | 成人无码另类电影 | 欧美性导航| 国产夫妻操逼视频 | 操逼的视频 |