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

          使用Chrome擴(kuò)展程序生成網(wǎng)頁(yè)骨架屏

          共 3920字,需瀏覽 8分鐘

           ·

          2020-08-07 15:21

          來(lái)源:橙紅年代

          https://juejin.im/post/6856784900775739400

          對(duì)于依賴接口渲染的頁(yè)面,在拿到數(shù)據(jù)之前頁(yè)面往往是空白的,為了提示用戶當(dāng)前正在加載中,往往會(huì)使用進(jìn)度條、loading圖標(biāo)或骨架屏的方式。對(duì)于前兩種方案而言,實(shí)現(xiàn)比較簡(jiǎn)單;本文主要研究骨架屏的應(yīng)用及實(shí)現(xiàn),并給出一種使用Chrome擴(kuò)展工具快速生成骨架屏的方案。

          首先看看效果 先放一個(gè)動(dòng)圖展示

          掘金首頁(yè)

          百度首頁(yè)

          知乎首頁(yè)

          安裝插件后訪問(wèn)任意網(wǎng)頁(yè),基本上均可以一鍵生成骨架屏(由于時(shí)間關(guān)系,部分節(jié)點(diǎn)并沒(méi)有完全實(shí)現(xiàn),不過(guò)目前展示Demo應(yīng)該是夠了

          本文所有代碼均放在https://github.com/tangxiangmin/web-skeleton-extension上面了,時(shí)間關(guān)系代碼寫的比較潦草~

          (寫完準(zhǔn)備提交Chrome應(yīng)用商店的時(shí)候才發(fā)現(xiàn),居然有一個(gè)類似的應(yīng)用:skeleton extention~囧,就當(dāng)瞎折騰一番了

          骨架屏方案

          目前有幾種比較常見(jiàn)的骨架屏方案

          1. 使用圖片、SVG實(shí)現(xiàn)骨架屏效果,可以讓設(shè)計(jì)在提供頁(yè)面設(shè)計(jì)稿的時(shí)候同步一份當(dāng)前頁(yè)面的骨架屏圖片資源,這種方案開(kāi)發(fā)成本較低,缺點(diǎn)是不太靈活,如果頁(yè)面迭代比較頻繁,會(huì)導(dǎo)致設(shè)計(jì)同事的工作量增大
          2. 手寫HTML+CSS實(shí)現(xiàn)骨架屏,較第一種圖片方案而言,可以更靈活地定制骨架屏UI和動(dòng)畫效果,缺點(diǎn)也很明顯,開(kāi)發(fā)和維護(hù)成本都較高;目前可以使用諸如react-content-loader等現(xiàn)有的骨架屏庫(kù),通過(guò)配置快速生成對(duì)應(yīng)的骨架屏,但對(duì)于某些高度定制的頁(yè)面也不能很好地滿足業(yè)務(wù)需求
          3. 餓了么前端團(tuán)隊(duì)提供的一套方案:page-skeleton-webpack-plugin,其大致原理是使用puppeteer向指定頁(yè)面注入代碼,遍歷頁(yè)面節(jié)點(diǎn),根據(jù)節(jié)點(diǎn)的類型渲染對(duì)應(yīng)的骨架屏樣式,然后結(jié)合webpack將返回的HTML自動(dòng)注入到項(xiàng)目模板文件中,傳送門:一種自動(dòng)化生成骨架屏的方案:https://github.com/Jocs/jocs.github.io/issues/22

          在最開(kāi)始調(diào)研骨架屏方案的時(shí)候也注意到了page-skeleton-webpack-plugin,體驗(yàn)了一下發(fā)現(xiàn)并不能完全滿足我的需求,對(duì)于骨架屏方案,我的期望是

          • 使用方便,能夠根據(jù)給定頁(yè)面,自動(dòng)生成對(duì)應(yīng)的骨架屏,不需要安裝額外的工具(不用安裝puppeteer)
          • 能夠靈活地處理骨架屏生成的HTML文件,支持首屏或者部分區(qū)域使用骨架屏(不需要webapck自動(dòng)注入模板文件,反之,需要實(shí)現(xiàn)為項(xiàng)目單個(gè)或多個(gè)頁(yè)面配置對(duì)應(yīng)骨架屏)
          • 能夠自定義骨架屏各個(gè)區(qū)塊的樣式,能夠指定包含或者忽略某些節(jié)點(diǎn),生成的代碼體積要足夠?。ü羌芷敛⒉恍枰耆€原原本頁(yè)面)

          恰好調(diào)研骨架屏方案的那段時(shí)間正在處理Chrome擴(kuò)展程序的工單,發(fā)現(xiàn)page-skeleton-webpack-plugin借助puppeteer執(zhí)行頁(yè)面腳本的方案,完全可以通過(guò)擴(kuò)展程序的content.js實(shí)現(xiàn),這樣,不需要借助本地開(kāi)發(fā)環(huán)境也可以渲染骨架屏了

          骨架屏實(shí)現(xiàn)原理

          何為骨架?

          首先需要保留節(jié)點(diǎn)的布局信息,這樣就可以復(fù)用節(jié)點(diǎn)原有的樣式,不會(huì)破壞整體布局,最后生成的骨架屏樣式就可以與原本的頁(yè)面保持一致。

          然后為了保證生成樣式的統(tǒng)一,需要覆蓋節(jié)點(diǎn)原本的UI信息,包括背景色、圖片、邊框等。

          由于骨架屏僅僅作為數(shù)據(jù)返回之前的占位,并不需要完全復(fù)原原本的頁(yè)面,因此為了使整個(gè)骨架屏看起來(lái)比較簡(jiǎn)潔,可以忽略不必要的節(jié)點(diǎn)。

          那么要處理哪些節(jié)點(diǎn)、忽略哪些節(jié)點(diǎn)呢?因此針對(duì)不同節(jié)點(diǎn),我們需要進(jìn)行判斷并分別處理。

          區(qū)分不同節(jié)點(diǎn)

          這部分大量參考了一種自動(dòng)化生成骨架屏的方案:https://github.com/Jocs/jocs.github.io/issues/22 這篇文章的思路,不妨移步閱讀,下面簡(jiǎn)單整理一下各種不同節(jié)點(diǎn)的處理方案,并增加了一些額外的節(jié)點(diǎn)處理策略。

          由于涉及大量的節(jié)點(diǎn)操作,因此使用了jQuery

          文字

          把文字占據(jù)的空間看做上行高、內(nèi)容、下行高,

          • 行高部分用透明色,
          • 內(nèi)容部分用灰色

          這樣就可以展示原本的文字區(qū)域了,對(duì)于多行文字而言,顯示的就是斑馬狀條紋形式的骨架屏結(jié)構(gòu)

          .line?{
          ????background-image:?linear-gradient(red?25%,blue?25%,?blue?75%,?red?75%);
          ????/*background-image:?linear-gradient(red?25%,blue?0,?blue?75%,?red?0);?//?與上面等價(jià)*/
          }
          復(fù)制代碼

          這里需要計(jì)算元素的行高、字體大小等信息

          renderText($dom)?{
          ????let?fontSize?=?parseFloat($dom.css("font-size"));
          ????let?lineHeight?=?$dom.css("line-height");

          ????//?todo?處理瀏覽器默認(rèn)行高、包含繼承、自定義等屬性
          ????if?(lineHeight?===?"normal")?{
          ????????lineHeight?=?fontSize?*?1.4;
          ????}?else?{
          ????????lineHeight?=?parseFloat(lineHeight);
          ????}

          ????const?textHeightRatio?=?fontSize?/?lineHeight;
          ????const?firstColorPoint?=?(((1?-?textHeightRatio)?/?2)?*?100).toFixed(2);
          ????const?secondColorPoint?=?(((1?-?textHeightRatio)?/?2?+?textHeightRatio)?*?100).toFixed(2);

          ????const?style?=?`--fp:${firstColorPoint}%;--sp:${secondColorPoint}%;--lh:${lineHeight}px;`;
          ????$dom.addClass('sk-text');
          ????$dom.attr("style",?style);
          }
          復(fù)制代碼

          因?yàn)槊總€(gè)文本節(jié)點(diǎn)的字體大小和行高可能不一樣,如果全在style標(biāo)簽上添加樣式,則生成的HTML文件可能比較大,因此這里使用了CSS Var,然后統(tǒng)一在sk-text的樣式類中處理背景色

          .sk-text?{
          ????--c:?#eee;
          ????--fp:?0%;
          ????--sp:?0%;
          ????--lh:?0;

          ????display:?inline-block;
          ????background-origin:?content-box?!important;
          ????background-clip:?content-box?!important;
          ????background-color:?transparent?!important;
          ????background-repeat:?repeat-y?!important;
          ????background-image:?linear-gradient(transparent?var(--fp),?var(--c)?0,?var(--c)?var(--sp),?transparent?0);
          ????background-size:?100%?var(--lh);
          ????color:?transparent?!important;
          }
          復(fù)制代碼

          圖片

          獲取原始圖片節(jié)點(diǎn)的寬高,然后使用1像素的base64灰色圖片替換原始節(jié)點(diǎn)的src屬性

          renderImg($img)?{
          ????let?width?=?$img.width()
          ????let?height?=?$img.height()
          ????//?一像素灰色圖片
          ????let?emptyImage?=?"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
          ????$img.attr("src",?emptyImage);

          ????$img.css({
          ????????background:?"#eee",
          ????????width:?width?+?"px",
          ????????height:?height?+?"px"
          ????})
          }
          復(fù)制代碼

          對(duì)于包含背景圖片的區(qū)塊而言,只需要將其背景覆蓋成灰色即可。

          按鈕、input

          使用灰色背景的塊占據(jù)

          邊框

          替換對(duì)應(yīng)邊框的顏色為骨架屏灰色

          列表

          列表元素是骨架屏中一個(gè)比較常見(jiàn)的區(qū)塊,列表元素可以由上面這些區(qū)塊組成,但是為了保證生成規(guī)整的骨架屏,采取的策略是使用移除多余的元素,使用第一個(gè)元素克隆占位

          function?renderList($dom)?{
          ????$dom.addClass("sk-list")

          ????let?$children?=?$dom.children()
          ????let?$child?=?$children.first()
          ????let?len?=?$children.length

          ????//?列表元素子節(jié)點(diǎn)統(tǒng)一,保證頁(yè)面骨架整齊
          ????for?(let?i?=?1;?i?????????$children.eq(i).remove()
          ????}
          ????for?(let?i?=?1;?i?????????let?tmp?=?$child.clone(true)
          ????????$dom.append(tmp)
          ????}
          }
          復(fù)制代碼

          遍歷DOM樹(shù)

          當(dāng)確定了不同類型節(jié)點(diǎn)的處理策略之后,就可以從入口節(jié)點(diǎn)遍歷整個(gè)DOM樹(shù)執(zhí)行處理方法了

          function?preorder($dom)?{
          ????//?...獲取節(jié)點(diǎn)類型,執(zhí)行相關(guān)策略放啊
          ????
          ????//?遍歷子節(jié)點(diǎn)
          ????$dom.children().each(function?()?{
          ????????const?$this?=?$(this)
          ????????preorder($this)
          ????});
          }
          復(fù)制代碼

          在遍歷期間還需要處理一些特殊情況

          • 不可見(jiàn)的元素及其子節(jié)點(diǎn)應(yīng)該停止遍歷
          • 對(duì)于某些節(jié)點(diǎn)而言,可能存在一些定制操作
            • 由用戶指定節(jié)點(diǎn)類型,而不是默認(rèn)根據(jù)節(jié)點(diǎn)nodeType推斷
            • 忽略某些節(jié)點(diǎn)的處理,如一個(gè)ul標(biāo)簽我們可能并不希望將其轉(zhuǎn)換成列表
            • 直接隱藏節(jié)點(diǎn),如某些小圖標(biāo)等,為了保證骨架屏簡(jiǎn)潔,可能需要直接隱藏節(jié)點(diǎn)

          基于這些場(chǎng)景,引入了skeleton-type的概念,對(duì)應(yīng)上面章節(jié)提到的節(jié)點(diǎn)類型,在遍歷時(shí)會(huì)優(yōu)先讀取節(jié)點(diǎn)上的該屬性值,只有當(dāng)屬性值不存在時(shí),才會(huì)根據(jù)nodeType自行推斷相關(guān)的類型;

          • 對(duì)于需要隱藏的節(jié)點(diǎn),直接指定skeleton-typeignore
          • 對(duì)于忽略節(jié)點(diǎn)類型而言,則使用skeleton-exclude-type
          ?let?type?=?$dom.attr(KEY)?||?getNodeSkeletonType($dom)??//?自動(dòng)檢測(cè)節(jié)點(diǎn)類型,并附上type
          let?excludeType?=?$dom.attr(KEY_EXCLUDE)

          if?(!excludeType?||?type?!==?excludeType)?{
          ????let?handlers?=?{
          ????????[TEXT]:?renderText,
          ????????[IMAGE]:?renderImg,
          ????????[BLOCK]:?renderBlock,
          ????????[BORDER]:?renderBorder,
          ????????[BUTTON]:?renderButton,
          ????????[LIST]:?renderList,
          ????????[BACKGROUND_IMAGE]:?renderBackgroundImage,
          ????????[INPUT]:?renderInput,
          ????????[IGNORE]:?renderIgnore
          ????}

          ????let?handler?=?handlers[type]
          ????handler?&&?handler($dom)
          }
          復(fù)制代碼

          這樣相當(dāng)于暴露了skeleton-typeskeleton-exclude-type兩個(gè)HTML屬性,在開(kāi)發(fā)頁(yè)面的時(shí)候就可以直接指定骨架屏區(qū)塊類型,方便定制業(yè)務(wù)需要的骨架屏。

          當(dāng)然逐個(gè)去配置skeleton-type也會(huì)顯得比較繁瑣,在getNodeSkeletonType中會(huì)盡可能地推斷并生成比較符合要求的骨架屏效果;此外,基于Chrome擴(kuò)展程序的工具還暴露了一個(gè)配置參數(shù)

          ?renderSkeleton("body",?{
          ????ignore:?'',
          ????selector:?{
          ????????[key]:?{include:?'',?exclude:?''}
          ????},
          })
          復(fù)制代碼

          用掘金首頁(yè)試一下

          原本的效果

          如果不過(guò)濾的話,因?yàn)轫敳繉?dǎo)航欄使用的是ul,會(huì)默認(rèn)識(shí)別成list,導(dǎo)致展示出現(xiàn)問(wèn)題

          自定義配置后的效果

          {
          ????ignore:?['.banner?.label'].join(','),?//?隱藏右側(cè)廣告欄的提示按鈕
          ????selector:?{
          ????????block:?{
          ????????????//?將表單轉(zhuǎn)換成一個(gè)BLOCK
          ????????????include:?['.add-group?.more',?'.search-form'].join(',')
          ????????},
          ????????list:?{
          ????????????//?排除導(dǎo)航欄的ul標(biāo)簽
          ????????????exclude:?['.nav-list'].join(',')
          ????????},
          ????},
          }
          復(fù)制代碼

          此外,我們也可以只傳入某個(gè)節(jié)點(diǎn)而非body的選擇器,這樣就只會(huì)生成對(duì)應(yīng)節(jié)點(diǎn)的HTML

          將骨架屏嵌入應(yīng)用

          骨架屏有兩種比較常見(jiàn)的應(yīng)用場(chǎng)景

          • 用于首屏或某個(gè)頁(yè)面打開(kāi)時(shí)等待數(shù)據(jù)返回時(shí)的占位
          • 用于滾動(dòng)加載等列表頁(yè)面占位

          接下來(lái)研究在這兩種場(chǎng)景下如何嵌入骨架屏

          獲取骨架屏代碼

          在前面遍歷入口節(jié)點(diǎn)DOM樹(shù)渲染骨架屏之后,我們只需要獲取對(duì)應(yīng)節(jié)點(diǎn)的HTML代碼即可。由于沒(méi)有修改原本布局信息,因此這段HTML代碼再同一個(gè)頁(yè)面是完全可以展示的。我們要做的就是拿到對(duì)應(yīng)的骨架屏HTML代碼。

          由于整個(gè)操作都是在當(dāng)前頁(yè)面上執(zhí)行的,拿到當(dāng)前頁(yè)面上某個(gè)節(jié)點(diǎn)的的html內(nèi)容就變得十分簡(jiǎn)單了

          • 通過(guò)控制臺(tái),找到節(jié)點(diǎn)然后訪問(wèn)innerHTML
          • 通過(guò)調(diào)試工具Elements面板,然后Copy HTML

          上面的方式需要手動(dòng)去操作,我們可以使用擴(kuò)展程序在骨架屏渲染結(jié)束之后自動(dòng)保存對(duì)應(yīng)的HTML代碼

          //?chromeMsg是自己封裝的一個(gè)`chrome.runtime.onMessage`工具
          chromeMsg.on("createSkeleton",?(params)?=>?{
          ????const?{config}?=?params
          ????//?默認(rèn)頁(yè)面根節(jié)點(diǎn),可以導(dǎo)出某個(gè)dom容器的骨架屏結(jié)構(gòu)
          ????let?content?=?renderSkeleton(".page",?config)
          ????//?處理對(duì)應(yīng)的骨架屏HTML代碼,這里只是簡(jiǎn)單打印,也可以直接保存文件到本地,或者通過(guò)HTTP接口調(diào)用某些鉤子服務(wù)完成自動(dòng)注入等功能
          ????console.log(content)
          })
          復(fù)制代碼

          整頁(yè)應(yīng)用

          對(duì)于整頁(yè)骨架屏,我們需要先使用測(cè)試數(shù)據(jù)生成對(duì)應(yīng)的骨架屏,然后將得到的HTML放入頁(yè)面中即可。

          以Vue等單頁(yè)項(xiàng)目而言,在整個(gè)應(yīng)用初始化之前,頁(yè)面上只存在

          根節(jié)點(diǎn),用戶看見(jiàn)的是一片空白,對(duì)于這種首屏應(yīng)用,我們可以將得到的骨架屏代碼直接嵌入到根節(jié)點(diǎn)中,這樣當(dāng)頁(yè)面加載至應(yīng)用初始化之前,用戶都能看見(jiàn)完整的骨架屏效果。

          對(duì)于其他單頁(yè)應(yīng)用其他頁(yè)面而言,也可以采用類似的原理,等待接口返回前展示骨架屏

          <SkeletonFrame?:frame="html"?:loading="isLoading">
          ????<RealPage>RealPage>
          SkeletonFrame>
          復(fù)制代碼

          比如我們可以封裝一個(gè)SkeletonFrame組件,其接口包括

          • frameloading兩個(gè)props,表示骨架屏HTML和是否正在加載,當(dāng)loading為true時(shí)展示骨架屏
          • default slot需要真實(shí)渲染的頁(yè)面組件,當(dāng)loading為false時(shí)展示真正頁(yè)面組件

          局部骨架屏

          對(duì)于一些需要持續(xù)加載的數(shù)據(jù),如滾動(dòng)加載等,可以先獲取對(duì)應(yīng)區(qū)塊的骨架屏HTML代碼(不需要整個(gè)頁(yè)面的骨架屏代碼),然后將其封裝成待展示組件,與上面SkeletonFrame邏輯比較類似

          小結(jié)

          本文整理了骨架屏的實(shí)現(xiàn)原理,然后提供了一種通過(guò)Chrome擴(kuò)展程序生成任意網(wǎng)頁(yè)的骨架屏方案,主要包括

          • 將頁(yè)面按節(jié)點(diǎn)類型拆分成不同區(qū)塊
          • 支持自定義節(jié)點(diǎn)類型、忽略或隱藏節(jié)點(diǎn)
          • 導(dǎo)出并使用骨架屏HTML代碼

          當(dāng)然,上面只是介紹了大概的實(shí)現(xiàn)思路,并給出了一個(gè)簡(jiǎn)單的Demo,距離實(shí)際應(yīng)用還有一些距離,等后面有時(shí)間再進(jìn)一步完善吧。本文所有代碼均放在github上面了,歡迎提PR和issue。

          —————END—————



          喜歡本文的朋友,歡迎關(guān)注公眾號(hào)?達(dá)達(dá)前端,收看更多精彩內(nèi)容



          點(diǎn)個(gè)[在看],是對(duì)達(dá)達(dá)最大的支持!



          瀏覽 119
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  中文字幕免费无码视频 | 亚洲色婷 | 大香蕉久久依人网站 | 音彰先锋成人无码视频 | 欧美在线观看网址 |