sponsored links

一種自動化生成骨架屏的方案

今天分享的主題是:「一種自動化生成骨架屏的方案」, 先看下市場上常見的骨架屏最佳化效果。

一種自動化生成骨架屏的方案

一種自動化生成骨架屏的方案


淘寶 CP端

一種自動化生成骨架屏的方案


京東 PC端

一種自動化生成骨架屏的方案

一種自動化生成骨架屏的方案


微博 APP

今天的分享主要分為三個部分:

1. 首屏載入狀態演進

2. 如何構建骨架屏

3. 將骨架屏打包的專案中

首屏載入的演進

我們先來看一些權威機構所做的研究報告。

一份是 [Akamai](http://www.akamai.com/html/about/press/releases/2009/press_091409.html) 這份究報告,當時總共採訪了大約 1048 名網上購物者,得出了這樣的結論:

  • 大約有 47% 的使用者期望他們的頁面在兩秒之內載入完成。
  • 如果頁面載入時間超過 3s,大約有 40% 的使用者選擇離開或關閉頁面。

一種自動化生成骨架屏的方案

這是 TagMan 和眼鏡零售商 Glasses Direct 合作進行的測試,研究頁面載入速度和最終轉化率的關係:

在這份測試報告中,發現了網頁載入速度和轉化率呈現明顯的負相關性,在頁面載入時間為1~2 秒時的轉化率是最高的,而當載入時間繼續增長,轉化率開始呈現一個下降的趨勢,大約頁面載入時間每增加 1s 轉化率下降6.7個百分點。

另外一份研究報告是 MIT 神經科學家在 2014 年做的研究,人類可以在 13ms 內感知到離散圖片的存在,並將圖片的大概資訊傳輸到我們的大腦中,在接下來的 100 到 140ms 之間,大腦會決定我們的眼睛具體關注圖片的什麼位置,也就是獲取圖片的關注焦點。從另一個角度來看,如果使用者進行某項互動(比如點選某按鈕),要讓使用者感知不到延遲或者資料載入,我們大概有 200 ms 的時間來準備新的介面資訊呈現給使用者。

在 200ms 到 1s 之間,使用者似乎還感知不到自己處在互動等待狀態,當一秒鐘後依然得不到任何反饋,使用者將會把其關注的焦點移到其他地方,如果等待超過 10s,使用者將對網站失去興趣,並瀏覽其他網站。

那麼我們需要做些什麼來留住使用者呢?

通常方案,我們會在首屏、或者獲取資料時,在頁面中展現一個進度條,或者轉動的 Spinner。

  • 進度條:明確知道互動所需時間,或者知道一個大概值的時候我們選擇使用進度條。
  • Spinner:無法預測獲取資料、或者開啟頁面的時長。

有了進度條或者 Spinner,至少告訴了使用者兩點內容:

  • 你所進行的操作需要等待一段時間。
  • 其次,安撫使用者,讓其耐心等待。

除此之外,進度條和 Spinner 並不能帶來其他任何作用,既無法讓使用者感知到頁面載入得更快,也無法給使用者一個焦點,讓使用者將關注集中到這個焦點上,並且知道這個焦點即將呈現使用者感興趣的內容。

那麼有沒有比進度條和 Spinner 更好的方案呢?也許我們需要的是骨架屏。

一種自動化生成骨架屏的方案

其實,骨架屏(Skeleton Screen)已經不是什麼新奇的概念了,Luke Wroblewski 早在 2013 年就首次提出了骨架屏的概念,並將這一概念成功得運用到他當時的產品「Polar app」中,2014 年,「Polar」加入 Google,Luke Wroblewski 本人也成為了Google 的一位產品總監。

A skeleton screen is essentially a blank version of a page into which information is gradually loaded.

他是這樣定義骨架屏的,他認為骨架屏是一個頁面的空白版本,透過這個空白版本傳遞資訊,我們的頁面正在漸進式的載入過程中。

蘋果公司已經將骨架屏寫入到了 iOS Human Interface Guidelines(https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/LaunchImages.html), 只是在該手冊中,其用了一個新的概念「launch images」。在該手冊中,其推薦在應用首屏中包含文字或者元素基本的輪廓。

2015 年,Facebook 也首次在其移動端 App 中使用了骨架屏的設計來預覽頁面的載入狀態。

一種自動化生成骨架屏的方案

隨後,Twitter,Medium,YouTube 也都在其產品設計中添加了骨架屏,骨架屏一時成為了首屏載入的新趨勢,國內一些公司也緊隨其後,餓了麼、知乎、掘金、騰訊新聞等也都在其 PC 端或者移動端加入了骨架屏設計。

為什麼需要骨架屏?

  • 在最開始關於 MIT 2014 年的研究中已有提到,使用者大概會在 200ms 內獲取到介面的具體關注點,在資料獲取或頁面載入完成之前,給使用者首先展現骨架屏,骨架屏的樣式、佈局和真實資料渲染的頁面保持一致,這樣使用者在骨架屏中獲取到關注點,並能夠預知頁面什麼地方將要展示文字什麼地方展示圖片,這樣也就能夠將關注焦點移到感興趣的位置。當真實資料獲取後,用真實資料渲染的頁面替換骨架屏,如果整個過程在 1s 以內,使用者幾乎感知不到資料的載入過程和最終渲染的頁面替換骨架屏,而在使用者的感知上,出現骨架屏那一刻資料已經獲取到了,而後只是資料漸進式的渲染出來。這樣使用者感知頁面載入更快了。
  • 再看看現在的前端框架, React 、Vue 、Angular 已經佔據了主導地位,市面上大多數前端應用也都是基於這三個框架或庫完成,這三個框架有一個共同的特點,都是 JS 驅動,在 JS 程式碼解析完成之前,頁面不會展示任何內容,也就是所謂的白屏。使用者是極其不喜歡看到白屏的,什麼都沒有展示,使用者很有可能懷疑網路或者應用出了什麼問題。 拿 Vue 來說,在應用啟動時,Vue 會對元件中的 data 和 computed 中狀態值透過 Object.defineProperty 方法轉化成 set、get 訪問屬性,以便對資料變化進行監聽。而這一過程都是在啟動應用時完成的,這也勢必導致頁面啟動階段比非 JS 驅動(比如 jQuery 應用)的頁面要慢一些。

如何構建骨架屏

餓了麼移動 web 頁面在 2016 年開始引入骨架屏,是完全透過 HTML 和 CSS 手寫的,手寫骨架屏當然可以完全復刻頁面的真實樣式,但也有弊端:

舉個例子,突然有一天,產品經理跑到了我面前,這個頁面佈局需要調整一下,然後這一塊推廣內容可以去掉了,我當時的心情可能是這樣的。

一種自動化生成骨架屏的方案

手寫骨架屏帶來的問題就是,每次需求的變更我們不僅需要修改業務程式碼, 同時也要去修改骨架屏的樣式和佈局,這往往是比較機械重複的工作,手寫骨架屏增加了維護成本。

因此餓了麼前端團隊一直在尋找一種更好、更快的將資料呈現到使用者面前的方案。

在選擇骨架屏之前,我們也調研了其他兩種備選方案:服務端渲染(ssr)和預渲染(prerender)。

一種自動化生成骨架屏的方案

現在,前端領域,不同框架下,服務端渲染的技術已經相當成熟,開箱即用的方案也有,比如 Vue 的 Nuxt.js。那麼為什麼不直接使用服務端渲染來加快內容展現?

首先我們瞭解到,服務端渲染主要有兩個目的,一是 SEO,二是加快內容展現。在帶來這兩個好處的同時,我們也需要評估服務端渲染的成本,首先我們需要服務端的支援,因此涉及到了到了服務構建、部署等,同時我們的 web 專案是一個流量較大的網站,也需要考慮伺服器的負載,以及相應的快取策略,特別是一些外賣行業,由於地理位置的不同,不同使用者看到的頁面也是不一樣的,也就是所謂的千人千面,這也為快取造成了一定困難。

一種自動化生成骨架屏的方案

其次,預渲染(prerender),所謂預渲染,就是在專案的構建過程中,透過一些渲染機制,比如 puppeteer 或則 jsdom 將頁面在構建的過程中就渲染好,然後插入到 html 中,這樣在頁面啟動之前首先看到的就是預渲染的頁面了。但是該方案最終也拋棄了,預渲染渲染的頁面資料是在構建過程中就已經打包到了 html 中, 當真實訪問頁面的時候,真實資料可能已經和預渲染的資料有了很大的出入,而且預渲染的頁面也是一個不可互動的頁面,在頁面沒有啟動之前,使用者無法和預渲染的頁面進行任何互動,預渲染頁面中的資料反而會影響到使用者獲取真實的資訊,當涉及到一些價格、金額、地理位置的地方甚至會導致使用者做出一些錯誤的決定。因此我們最終沒有選擇預渲染方案。

生成骨架屏基本方案

透過 puppeteer 在服務端操控 headless Chrome 開啟開發中的需要生成骨架屏的頁面,在等待頁面載入渲染完成之後,在保留頁面佈局樣式的前提下,透過對頁面中元素進行刪減或增添,對已有元素透過層疊樣式進行覆蓋,這樣達到在不改變頁面佈局下,隱藏圖片和文字,透過樣式覆蓋,使得其展示為灰色塊。然後將修改後的 HTML 和 CSS 樣式提取出來,這樣就是骨架屏了。

下面我將透過 page-skeleton-webpack-plugin 工具中的程式碼,來展示骨架屏的具體生成過程。

正如上面基本方案所描述的那樣,我們將頁面分成了不同的塊:

  • 文字塊:僅包含文字節點(NodeType 為 Node.TEXTNODE)的元素(NodeType 為 Node.ELEMENTNODE),一個文字塊可能是一個 p 元素也可能是 div 等。文字塊將會被轉化為灰色條紋。
  • 圖片塊:圖片塊是很好區分的,任何 img 元素都將被視為圖片塊,圖片塊的顏色將被處理成配置的顏色,形狀也被修改為配置的矩形或者圓型。
  • 按鈕塊:任何 button 元素、 type 為 button 的 input 元素,role 為 button 的 a 元素,都將被視為按鈕塊。按鈕塊中的文字塊不在處理。
  • svg 塊:任何最外層是 svg 的元素都被視為 svg 塊。
  • 偽類元素塊:任何偽類元素都將視為偽類元素塊,如 ::before 或者 ::after。

首先,我們為什麼要把頁面劃分為不同的塊呢?

將頁面劃分為不同的塊,然後分別對每個塊進行處理,這樣不會破壞頁面整體的樣式和佈局,當我們最終生成骨架屏後,骨架屏的佈局樣式將和真實頁面的佈局樣式完全一致,這樣就達到了複用樣式及頁面佈局的目的。

在所有分開處理之前,我們需要完成一項工作,就是將我們生成骨架屏的指令碼,插入到 puppeteer 開啟的頁面中,這樣我們才能夠執行指令碼,並最終生成骨架屏。

值得慶幸的是,puppeteer 在其生成的 page 例項中提供了一個原生的方法。

  page.addScriptTag(options)
    options<Object>
        url
        path
        content
        type(Use 'module' in order to load a Javascript ES6 module.)

有了這種方法,我們可以插入一段 js 指令碼的 url 或者是相對/絕對路徑,也可以直接是 js 指令碼的內容,在我們的實踐過程中,我們直接插入的指令碼內容。

  async makeSkeleton(page) {
    const { defer } = this.options
    await page.addScriptTag({ content: this.scriptContent })
    await sleep(defer)
    await page.evaluate((options) => {
      Skeleton.genSkeleton(options)
    }, this.options)
  }

有了上面插入的指令碼,並且我們在指令碼中提供了一個全域性物件 Skeleton,這樣我們就可以直接透過 page.evaluate 方法來執行指令碼內容並最終生成骨架頁面了。

由於時間有限,這兒不會對每個塊的生成骨架結構進行詳盡分析,這兒可能會重點闡述下文字塊、圖片塊、svg 塊如何生成骨架結構的,然後再談談如何對骨架結構進行最佳化。

文字塊的骨架結構生成

文字塊可以算是骨架屏生成中最複雜的一個區塊了,正如上面也說的,任何只包含文字節點的元素都將視為文字塊,在確定某個元素是文字塊後,下一步就是透過一些 CSS 樣式,以及元素的增減將其修改為骨架樣式。

一種自動化生成骨架屏的方案

在這張圖中,圖左邊虛線框內是一個 p 元素,可以看到其內部有 4 行文字,右圖是一個已經生成好的帶有 4 行文字的骨架屏。在生成文字塊骨架屏之前,我們首先需要了解一些基本的引數。

  • 單行文字內容的高度,可以透過 fontSize 獲取到。
  • 單行文字內容加空白間隙的高度,可以透過 lineHeight 獲取到。
  • p 元素總共有多少行文字,也就是所謂行數,這個可以透過 p 元素的 (height - paddingTop - paddingBottom)/ lineHeight 大概算出。
  • 文字的 textAlign 屬性。

在這些引數中,fontSize、lineHeight、paddingTop、paddingBottom 都可以透過 getComputedStyle 獲取到,而元素的高度 height 可以透過 getBoundingClientRect 獲取到,有了這些引數後我們就能夠繪製文字塊的骨架屏了。

一種自動化生成骨架屏的方案

相信很多人都讀過 @Lea Verou 的 CSS Secrets 這本書,書中有一篇專門闡述怎麼透過線性漸變生成條紋背景的文章,而在繪製文字塊骨架屏方案,正是受到了這篇文章的啟發,文字塊的骨架屏也是透過線性漸變來繪製的。核心簡化程式碼:

const textHeightRatio = parseFloat(fontSize, 10) / parseFloat(lineHeight, 10)
const firstColorPoint = ((1 - textHeightRatio) / 2 * 100).toFixed(decimal)
const secondColorPoint = (((1 - textHeightRatio) / 2 + textHeightRatio) * 100).toFixed(decimal)

const rule = `{
  background-image: linear-gradient(
    transparent ${firstColorPoint}%, ${color} 0%,
    ${color} ${secondColorPoint}%, transparent 0%);
  background-size: 100% ${lineHeight};
  position: ${position};
  background-origin: content-box;
  background-clip: content-box;
  background-color: transparent;
  color: transparent;
  background-repeat: repeat-y;
}`

我們首先計算了 lineHeight 和 fontSize 等一些樣式引數,透過這些引數我們計算出了文字佔整個行高的比值,也就是 textHeightRadio,有了這一比值,就可以知道灰色條紋的分界點,正如 @Lea Verou 所說:

摘自:CSS Secrets “If a color stop has a position that is less than the specied position of any color stop before it in the list, set its position to be equal to the largest speci ed position of any color stop before it.” — CSS Images Level 3 (http://w3.org/TR/css3-images)

也就是說,線上性漸變中,如果我們將線性漸變的起始點設定小於前一個顏色點的起始值,或者設定為0 %,那麼線性漸變將會消失,取而代之的將是兩條顏色分明的條紋,也就是說不再有線性漸變。

在我們繪製文字塊的時候,backgroundSize 寬度為 100%, 高度為 lineHeight,也就是灰色條紋加透明條紋的高度是 lineHeight。雖然我們把灰色條紋繪製出來了,但是,我們的文字依然顯示,在最終骨架樣式效果出現之前,我們還需要隱藏文字,設定 color:‘transparent’ 這樣我們的文字就和背景色一致,最終顯示得也就是灰色條紋了。

根據 lineCount 我們可以判斷文字塊是單行文字還是多行,在處理單行文字的時候,由於文字的寬度並沒有整行寬度,因此,針對單行文字,我們還需要計算出文字的寬度,然後設定灰色條紋的寬度為文字寬度,這樣骨架樣式的效果才能夠更加接近文字樣式。

圖片塊的骨架生成

圖片塊的繪製比文字塊要相對簡單很多,但是在訂方案的過程中也踩了一些坑,這兒簡單分享下采坑經歷。

最初訂的方案是透過一個 DIV 元素來替換 IMG 元素,然後設定 DIV 元素背景為灰色,DIV 的寬高等同於原來 IMG 元素的寬高,這種方案有一個嚴重的弊端就是,原來透過元素選擇器設定到 IMG 元素上的樣式無法運用到 DIV 元素上面,導致最終圖片塊的骨架效果和真實的圖片在頁面樣式上有出入,特別是沒法適配不同的移動端裝置,因為 DIV 的寬高被硬編碼。

接下來我們又嘗試了一種看似「高階」的方法,透過 Canvas 來繪製和原來圖片大小相同的灰色塊,然後將 Canvas 轉化為 dataUrl 賦予給 IMG 元素的 src 特性上,這樣 IMG 元素就顯示成了一個灰色塊了,看似完美,當我們將生成的骨架頁面生成 HTML 檔案時,一下就傻眼了,檔案大小盡然有 200 多 kb,我們做骨架頁面渲染的一個重要原因就是希望使用者在感知上感覺頁面載入快了,如果骨架頁面都有 200 多 kb,必將導致頁面載入比之前要慢一些,違背了我們的初衷,因此該方案也只能夠放棄。

最終方案,我們選擇了將一張1 * 1 畫素的 gif 透明圖片,轉化成 dataUrl ,然後將其賦予給 IMG 元素的 src 特性上,同時設定圖片的 width 和 height 特性為之前圖片的寬高,將背景色調至為骨架樣式所配置的顏色值,完美解決了所有問題。

// 最小 1 * 1 畫素的透明 gif 圖片 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'

這是1 * 1畫素的 base64 格式的圖片,總共只有幾十個位元組,明顯比之前透過 Canvas 繪製的圖片小很多。

程式碼:

function imgHandler(ele, { color, shape, shapeOpposite }) {
  const { width, height } = ele.getBoundingClientRect()
  const attrs = {
    width,
    height,
    src
  }

  const finalShape = shapeOpposite.indexOf(ele) > -1 ? getOppositeShape(shape) : shape

  setAttributes(ele, attrs)

  const className = CLASS_NAME_PREFEX + 'image'
  const shapeName = CLASS_NAME_PREFEX + finalShape
  const rule = `{
    background: ${color} !important;
  }`
  addStyle(`.${className}`, rule)
  shapeStyle(finalShape)

  addClassName(ele, [className, shapeName])

  if (ele.hasAttribute('alt')) {
    ele.removeAttribute('alt')
  }
}

svg 塊骨架結構

svg 塊處理起來也比較簡單,首先我們需要判斷 svg 元素 hidden 屬性是否為 true,如果為 true,說明該元素不展示的,所以我們可以直接刪除該元素。

if (width === 0 || height === 0 || ele.getAttribute('hidden') === 'true') {
  return removeElement(ele)
}

如果不是隱藏的元素,那麼我們將會把 svg 元素內部所有元素刪除,減少最終生成的骨架頁面體積,其次,設定svg 元素的寬、高和形狀等。

const shapeClassName = CLASS_NAME_PREFEX + shape
shapeStyle(shape)

Object.assign(ele.style, {
  width: px2relativeUtil(width, cssUnit, decimal),
  height: px2relativeUtil(height, cssUnit, decimal),
})

addClassName(ele, [shapeClassName])

if (color === TRANSPARENT) {
  setOpacity(ele)
} else {
  const className = CLASS_NAME_PREFEX + 'svg'
  const rule = `{
    background: ${color} !important;
  }`
  addStyle(`.${className}`, rule)
  ele.classList.add(className)
}

一些最佳化的細節

  • 首先,由上面一些程式碼可以看出,在我們生成骨架頁面的過程中,我們將所有的共用樣式透過 addStyle 方法快取起來,最後在生成骨架屏的時候,統一透過 style 標籤插入到骨架屏中。這樣保證了樣式儘可能多的複用。
  • 其次,在處理列表的時候,為了生成骨架屏儘可能美觀,我們對列表進行了同化處理,也就是說將 list 中所有的 listItem 都是同一個 listItem 的克隆。這樣生成的 list 的骨架屏樣式就更加統一了。
  • 還有就是,正如前文所說,骨架屏僅是一種載入狀態,並非真實頁面,因此其並不需要完整的頁面,其實只需要首屏就好了,我們對非首屏的元素進行了刪除,只保留了首屏內部元素,這樣也大大縮減了生成骨架屏的體積。
  • 刪除無用的 CSS 樣式,只是我們只提取了對骨架屏有用的 CSS,然後透過 style 標籤引入。

關鍵程式碼大致是這樣的:

const checker = (selector) => {
  if (DEAD_OBVIOUS.has(selector)) {
    return true
  }
  if (/:-(ms|moz)-/.test(selector)) {
     return true
  }
  if (/:{1,2}(before|after)/.test(selector)) {
    return true
  }
  try {
    const keep = !!document.querySelector(selector)
    return keep
  } catch (err) {
    const exception = err.toString()
    console.log(`Unable to querySelector('${selector}') [${exception}]`, 'error')
    return false
  }
}

可以看出,我們主要透過 document.querySelector 方法來判斷該 CSS 是否被使用到,如果該 CSS 選擇器能夠選擇上元素,說明該 CSS 樣式是有用的,保留。如果沒有選擇上元素,說明該 CSS 樣式沒有用到,所以移除。

在後面的一些 slides 中,我們來聊聊怎講將構建骨架屏和 webpack 開發、打包結合起來,最終將我們的骨架屏打包到實際專案中。

透過 webpack 將骨架屏打包到專案中

在上一個部分,我們分析了怎麼去生成骨架屏,在這一部分,我們將探討如何透過 webpack 將骨架屏打包的專案中。在這過程中,思考了以下一些問題:

為什麼在開發過程中生成骨架屏?

其主要原因還是為了骨架屏的可編輯。

在上一個部分,我們透過一些樣式和元素的修改生成了骨架屏頁面,但是我們並沒有馬上將其寫入到配置的輸出資料夾中,在寫入骨架頁面到專案之前。我們透過 memory-fs 將骨架屏寫入到記憶體中,以便我們能夠透過預覽頁面進行訪問。同時我們也將骨架屏原始碼傳送到了預覽頁面,這樣我們就可以透過修改原始碼,對骨架屏進行二次編輯。

正如這張圖片,這張圖是外掛開啟的骨架屏的預覽頁面,從左到右依次是開發中的真實頁面、骨架屏、骨架屏可編輯原始碼。

一種自動化生成骨架屏的方案

這樣我們就可以在開發過程中對骨架屏進行編輯,修改部分樣式,中部骨架屏可以進行實時預覽,這之間的通訊都是透過websocket 來完成的。當我們對生成的骨架屏滿意後,並點選右上角寫入骨架屏按鈕,將骨架屏寫入到專案中,在最後專案構建時,將骨架屏打包到專案中。

如果我們同時在構建的過程中生成骨架屏,並打包到專案中,這時的骨架屏我們是無法預覽的,因此我們對此時的骨架屏一無所知,也不能夠做任何修改,這就是我們在開發中生成骨架屏的原因所在。

演講最開始已經提到,目前流行的前端框架基本都是 JS 驅動,也就是說,在最初的 index.html 中我們不用寫太多的 html 內容,而是等框架啟動完成後,透過執行時將內容填充到 html 中,通常我們會在 html 模板中新增一個根元素:

<div id="app"></div>

當應用啟動後,會將真實的內容填充到上面的元素中。這也就給了我們一個展示骨架屏的機會,我們將骨架屏在頁面啟動之前新增到上面元素內:

<div id="app"><!-- shell.html --></div>

怎樣將骨架屏打包到專案中

Webpack 是一款優秀的前端打包工具,其也提供了一些豐富的 API 讓我們可以自己編寫一些外掛來讓 webpack 完成更多的工作,比如在構建過程中,將骨架屏打包到專案中。

Webpack 在整個打包的過程中提供了眾多生命週期事件,比如compilation 、after-emit 等,比如我們最終將骨架屏插入到 html 中就是在after-emit 鉤子函式中進行的,簡單的程式碼:

SkeletonPlugin.prototype.apply = function (compiler) {
  // 其他程式碼
  compiler.plugin('after-emit', async (compilation, done) => {
    try {
      await outputSkeletonScreen(this.originalHtml, this.options, this.server.log.info)
    } catch (err) {
      this.server.log.warn(err.toString())
    }
    done()
  })
  // 其他程式碼
}

我們再來看看 outputSkeletonScreen 是如何將骨架屏插入到原始的 HTML 中,並且寫入到配置的輸入資料夾的。

const outputSkeletonScreen = async (originHtml, options, log) => {
  const { pathname, staticDir, routes } = options
  return Promise.all(routes.map(async (route) => {
    const trimedRoute = route.replace(/\//g, '')
    const filePath = path.join(pathname, trimedRoute ? `${trimedRoute}.html` : 'index.html')
    const html = await promisify(fs.readFile)(filePath, 'utf-8')
    const finalHtml = originHtml.replace('<!-- shell -->', html)
    const outputDir = path.join(staticDir, route)
    const outputFile = path.join(outputDir, 'index.html')
    await fse.ensureDir(outputDir)
    await promisify(fs.writeFile)(outputFile, finalHtml, 'utf-8')
    log(`write ${outputFile} successfully in ${route}`)
    return Promise.resolve()
  }))
}

更多思考

Page Skeleton webpack 外掛在我們內部團隊已經開始使用,在使用的過程中我們也得到了一些反饋資訊。

首先是對 SPA 多路由的支援,其實現在外掛已經支援多路由了,只是還沒有用到真實專案中,我們針對每一個路由頁面生成一個單獨的 index.html,也就是靜態路由。然後將每個路由生成的骨架屏插入到不同的靜態路由的 html 中。

其次,玩過服務端渲染的同學都知道,在 React 和 Vue 服務端渲染中有一種稱為 Client-side Hydration 的技術,指的是在 Vue 在瀏覽器接管由服務端傳送來的靜態 HTML,使其變為由 Vue 管理的動態 DOM 的過程。

在我們構建骨架屏的過程中,其 DOM 結構和真實頁面的 DOM 結構基本相同,只是添加了一些行內樣式和 classname,我們也在思考這些 DOM 能夠被複用,也就是在應用啟動時重新建立所有 DOM。我們只用啟用這些骨架屏 DOM,讓其能夠相應資料的變化,這似乎就可以使骨架屏和真實頁面更好的融合。

還有,在頁面啟動後,我們可能還是會透過 AJAX 獲取後端資料,這時候我們也可以透過 骨架屏 來作為一種載入狀態。也就是說,其實我們可以在「非首屏骨架屏」上做一些工作。

最後,在專案中可能會有一些效能監控的需求,比如骨架屏什麼時候建立,什麼時候被銷燬,這些我們可能都希望透過一些效能監控的工具記錄下來,以便將來做一些效能上面的分析。因此將來也會提供一些骨架屏的生命週期函式,或者提供相應的自定義事件,在生命週期不同階段,呼叫相應的生命週期鉤子函式或監聽相應事件,這樣就可以將骨架屏的一些資料記錄到效能監控軟體中。

本文摘自:一種自動化生成骨架屏的方案(http://github.com/Jocs/jocs.github.io/issues/22)

推薦閱讀

  • page-skeleton-webpack-plugin外掛地址 (https://github.com/ElemeFE/page-skeleton-webpack-plugin)
  • 如何讓網頁“看起來”展現地更快?骨架屏二三事 (https://zhuanlan.zhihu.com/p/48601348)
  • 美團網頁首屏最佳化 (https://mp.weixin.qq.com/s/wi52VIfteEXGift95wzkWQ)
  • Vue頁面骨架屏注入實踐 (https://segmentfault.com/a/1190000014832185)
  • Vue專案骨架屏注入實踐 (https://juejin.cn/post/6844903661726859272)
  • vue-cli專案新增骨架屏多種方式,自動生成骨架屏 (https://blog.csdn.net/zhouzuoluo/article/details/100216255)
  • 小程式構建骨架屏的探索 (https://segmentfault.com/a/1190000015876164)
分類: 軍事
時間: 2021-12-17

相關文章

俄研究:全球近四分之一出口全脂奶粉被中國買入;美穀物貿易商ADM在浙江開設調味料生產廠;蒙牛提名4名妙可藍多非獨董候選人

俄研究:全球近四分之一出口全脂奶粉被中國買入;美穀物貿易商ADM在浙江開設調味料生產廠;蒙牛提名4名妙可藍多非獨董候選人
01 俄研究:全球近四分之一出口全脂奶粉被中國買入 日前,俄羅斯農業產品出口中心的研究顯示,中國成為2020年全脂奶粉的主要進口國,全球23%的出口全脂奶粉被中國買入.全球全脂奶粉出口總量為2745. ...

中國開始出口高超音速武器!6馬赫+攻頂突防,宙斯盾都不一定管用

中國開始出口高超音速武器!6馬赫+攻頂突防,宙斯盾都不一定管用
從近年來的趨勢來看,高超音速技術已經開始成為引領軍備技術發展的一大潮流,此類武器平臺的突防能力得到了很大提升,讓現有的一系列防空探測和攔截技術遭遇到了空前的挑戰.目前,中國方面已經給火箭軍成批列裝以東 ...

中國汽車出口創歷史新高,新能源車出口主力是國產特斯拉

中國汽車出口創歷史新高,新能源車出口主力是國產特斯拉
前八月汽車出口131.8萬輛,新能源車佔了17.3萬輛,但尷尬的是這裡面大半是國產特斯拉 圖/視覺中國 文 | <財經>記者 邱瑤 編輯 | 施智梁 疫情籠罩下,中國汽車出口逆勢增長. 9 ...

煤炭資源逐漸短缺,中國從出口變成進口,我國換道解決能源難題

煤炭資源逐漸短缺,中國從出口變成進口,我國換道解決能源難題
煤炭是至關重要的傳統能源,在工業.民用等領域發揮著關鍵作用.在中國一次效能源結構之中,煤炭消費者量佔比68.8%之高. 煤炭短缺問題顯露 在最近一段時間裡,煤炭價格一路飆升.在2個月的時間裡,焦煤期貨 ...

漸入佳境還是陷入困境,中國先進戰鬥機出口現狀

漸入佳境還是陷入困境,中國先進戰鬥機出口現狀
前言:"工欲成其事,必先利其器",先進武器自然擁有強大戰鬥力,擁有先進武器的國家把武器作為擴大政治影響力的交換,也可以出售換取收益積累財富,沒有先進武器的國家引進的武器保護自己,俄 ...

作弊炸死越南士兵,敗壞中國武器聲譽,邪惡的美軍越戰長子行動

作弊炸死越南士兵,敗壞中國武器聲譽,邪惡的美軍越戰長子行動
1967年,越南與柬埔寨邊境附近,一支中國56式步槍黑洞洞的槍口,瞄準了美軍第一步兵師的一名士兵,隨著一聲清脆的槍聲,死神來了. 但蹊蹺的是,倒下的並不是被瞄準的美國士兵,而是埋伏在樹叢裡對他開槍的北 ...

中國軍貿頻傳喜訊,航展的背後,關於武器出口有哪些潛規則?

中國軍貿頻傳喜訊,航展的背後,關於武器出口有哪些潛規則?
原創 我是大伊萬 軍武速遞 珠海航展已經接近尾聲,雖然目前還沒等來咱們"20家族"的新成員,但是今年的航展還是帶給了我們不少驚喜.幾天前,航天科技在珠海航展成功簽約近500億的大單 ...

中國鋼鐵出口受阻,關稅高達103%,我國該如何逆風翻盤?

中國鋼鐵出口受阻,關稅高達103%,我國該如何逆風翻盤?
鋼鐵出口受阻,被墨西哥收取103%的關稅,中國的反應別有深意 近日墨西哥經濟部門宣佈完成對中國冷軋鋼板的反傾銷調查,並最終決定在未來5年對冷軋鋼板加徵額外的進口稅,相關產品最高需要繳納103%的進口稅 ...

能帶來什麼效果?珠海航展落幕,從珠海航展看我國武器出口情況

能帶來什麼效果?珠海航展落幕,從珠海航展看我國武器出口情況
2年一度的珠海航展在前不久終於是落下了帷幕,而航展也從最開的防務展覽,展示自己的軍事實力,變成了一個用於對外展示.對外交流,促進國內各大軍工企業對外出口軍火的展覽.我國的軍事技術透過20多年來的努力, ...

埃及海空軍裝備中國武器合集

埃及海空軍裝備中國武器合集
埃及作為非洲最強軍事大國,在軍事裝備上採取跟印度相同路線,走"萬國牌"拼湊路線,空軍裝備著1054架各種軍用飛機,包括215架戰鬥機和294架軍用直升機,陸軍地面武裝力量,裝備43 ...

俄憂慮美實驗室在中俄邊境擴大,韓國直接把德堡告上法庭,中國也得跟上

俄憂慮美實驗室在中俄邊境擴大,韓國直接把德堡告上法庭,中國也得跟上
9月6日,據環球網報道,俄羅斯聯邦偵察委員會副主席亞歷山大·費多羅夫表示,美軍在俄羅斯邊境周圍國家不受控制地.無限制地部署生物實驗室,引發俄羅斯專家和國際社會的擔憂. 這是時隔5個月後,俄羅斯再次對美 ...

俄媒:中國的技術優勢:6G專利申請超過美國 中國提交了 40.3% 專利

俄媒:中國的技術優勢:6G專利申請超過美國 中國提交了 40.3% 專利
俄羅斯衛星通訊社網站 中國已經在開發 6G 下一代網路.商業部署預計將在本十年末開始.日經亞洲和網路創意研究院的研究顯示,中國公司在全球提交了 40.3% 的 6G 專利申請.其中大多數來自華為.美國 ...

中國斥資800億打造的皇京港,卻令美新兩國焦慮不已,這是為何?

中國斥資800億打造的皇京港,卻令美新兩國焦慮不已,這是為何?
作為亞太地區的咽喉要地,馬六甲海峽是連線太平洋和印度洋之間的航運要道.而作為世界上的第二大貨運港口,新加坡港也有著無可比擬的戰略地位. 事實上,當地有個早先存在的港口並不出名,隨著國際航運的發展,和中 ...

兒時隨處可見的蓖麻,中國年需求40萬噸,還被美國列為戰略物資?

兒時隨處可見的蓖麻,中國年需求40萬噸,還被美國列為戰略物資?
小時候,農村的田間地頭經常能看到一個叫蓖麻子的東西,經常摘下來玩.讓人意想不到的是,這個兒時的玩物卻被美國列為"戰略物資",而且每年都要從其他國家大量進口. 蓖麻有什麼用?為何能成 ...

亞太海軍排名有變:中國造艦速度驚人,4年建132艘與美日印澳相當

亞太海軍排名有變:中國造艦速度驚人,4年建132艘與美日印澳相當
美國聯合英國印度和澳大利亞結成新的軍事聯盟,有國際軍事專家聲稱這就是為了對付中國.但他們可能不知道,太平洋地區的海軍力量對比已經發生了翻天覆地的變化.在2015年到2019年這4年間,中國造了132艘 ...

24歲柬埔寨公主自信曬中國大學畢業證,笑容甜美,穿蕾絲襯衫好美

24歲柬埔寨公主自信曬中國大學畢業證,笑容甜美,穿蕾絲襯衫好美
在亞洲王室圈中,柬埔寨王室十分低調,不像泰國王室為了69歲泰王瑪哈後宮爭的天翻地覆:也沒有汶萊王室的壕氣沖天,但是它卻是一個盛產美女的寶藏王室.比如已經85歲的莫尼列王太后,一頭銀髮美的端莊大氣,是歲 ...

美國妄圖在中國大後方“埋雷”?警惕美國兩大陰謀,看懂中美對抗

美國妄圖在中國大後方“埋雷”?警惕美國兩大陰謀,看懂中美對抗
蘇聯解體後,美國對中國的崛起愈發忌憚和重視.無論是在東亞聯合日韓對中國聯合包圍,還是糾集澳大利亞與菲律賓在東南亞.南海挑釁中國,抑或是支援印度,對中國進行軍事侵擾,在中國的大後方,與新疆.西藏接壤的中 ...

有好賣的也有難賣的,從珠海航展看中國海陸空武器的不同出口情況

有好賣的也有難賣的,從珠海航展看中國海陸空武器的不同出口情況
前言:80年代以前,中國的武器輸出原則是"不做軍火商",沒有把武器當做商品,而是無償軍援,輸出物件涉及70多個國家,其中北越.寮國.朝鮮和阿爾巴尼亞收到的武器數目最多,這種不計成本 ...

中國把美國擠下全球製造業出口第一的寶座,美國人:崛起的巨人

中國把美國擠下全球製造業出口第一的寶座,美國人:崛起的巨人
據外媒報道,中國的製造業在全球市場的出口佔比已經達到了全球第一,大幅度領先於美國.德國等,中國的製造業在全球擁有非常強的競爭力和持久力. 近些年,隨著印著"中國製造"的產品被越來越 ...

起源中國卻被偷學,今印度版被全球搶著買單,中國反倒年進口30億

起源中國卻被偷學,今印度版被全球搶著買單,中國反倒年進口30億
中國是全球茶葉生產大國,佔全球茶葉產量的40%,但是中國也是全球茶葉的進口大國,很多國人很喜歡喝外國的茶,而中國7萬家茶葉公司,在國外的知名度極低,品牌幾乎等同於隱形. 這是當前真實的中國茶現狀! 明 ...