天天看點

雜談:電商平台中的圖檔資源優化實戰

圖檔渲染優化

以前談過許多次圖檔問題。也給出了幾種方案。在實際使用中這幾種無疑是可行而且友善的:

  1. loading
  2. connection API +​

    ​promise.all()​

    ​異步加載圖檔
  3. 骨架屏
  4. 懶加載 + 占位圖

但是在電商場景下,第一種方案是不可以的:我們不能為了一張圖檔而放棄整個内容對使用者的正常展現。(尤其是這個圖檔還隻是個背景圖)

第二種方案在首頁是有奇效的,但是筆者覺得限制太多 —— 如果對于“通用型”的方案來說,骨架屏似乎更适合中大型項目。

這個方案當時提出的場景是:在我校的實驗系統首頁會有一個超大的輪播圖,但是它的作用隻是展示和宣傳。很顯然,這個輪播圖并不需要所有的圖檔都能展示出來,但是如果放任不管,在網絡情況稍差的時候會有空白甚至閃白的問題出現。這是不好的!是以用​​

​connection​

​ API檢測目前網絡情況以決定展示輪播圖還是一張圖檔。

最後一種方案應該是現在比較常用的。占位圖還可以換做一個固定寬高的、有背景顔色的​

​div​

​​,減少一次圖檔的請求(以前去哪兒首頁就這樣做的)。

但是它的缺陷在于:如果圖檔太大,長時間的loading也會讓使用者覺得煩躁!

不做處理是不可能的。長時間的白屏或者閃白估計會讓産品發瘋。。。

雪碧圖

CSS spirte是個好東西,它能大幅減少 http 開銷。​

​spirte + prefetch​

​的組合可以讓優化 http 請求的同時讓其提早被加載:

<link rel="prefetch" href="xxx" />      

prefetch是對浏覽器的“暗示”:将來可能需要某些資源,但由浏覽器決定是否加載以及什麼時候加載這些資源。它的優先級比較低。

spirte的原理是将整個整個頁面需要的圖檔都放到一張圖中,通過​

​transform​

​移動到需要展示的地方。但還是那句話:如果有許多體積太大的圖檔,會讓雪碧圖的加載異常困難,進而帶來不好的使用者體驗。

懶加載

剛進入網頁頁面就會有大批量的圖檔資源加載,這會間接影響頁面的加載,增加白屏加載時間,影響使用者體驗。是以,我們的訴求就是不在可視化視窗内的圖檔不真正加載,盡可能減少本地帶寬的浪費和請求資源的數量。

懶加載的優勢很明顯:

  • 減少帶寬資源消耗,減少不必要的資源加載消耗。
  • 防止并發加載圖檔資源導緻的資源加載阻塞,進而減少白屏時間。

實作簡單的懶加載

實作的方式有兩種:

  1. 通過​

    ​scroll​

    ​事件來監聽視窗滾動區域實作。該方法相容性好,絕大多數浏覽器和WebView都相容支援。
  2. 通過​

    ​IntersectionObserver​

    ​ API觀察DOM是否出現在視窗内,該方法優點在于調用簡單,隻是部分移動端相容沒有上一種方式好。

兩種形式都是在觀察目前DOM是否出現在了可視視窗内,如果出現的話就将​

​data-src​

​中的圖檔位址指派給src,然後開始加載目前的圖檔。

筆者之前專門研究過 ​​預加載和懶加載(點選檢視文章)​​​,在其中封裝了一個函數可供調用。但那是通過監聽 scroll 的方式。

事實上,浏覽器提供的​​

​IntersectionObserver​

​ API要更友善一些:

const root = 擷取父元素;
const options = {
    root: root,
    // 這裡是一個數組可以指定多個比例類似[0.25, 0.5, 0.75, 1]
    threshold: [0],//交會處
    rootMargin:"0px"//對視口進行收縮和擴張
}
const lazyIntersection = new IntersectionObserver(entires => {
    // entires為監聽的節點數組對象
    entires.forEach((item,index) => {
        console.log(item)
        // console.log(item.target, item.isIntersecting? '可見': '不可見')
        // isIntersecting是目前監聽元素交叉區域是否在可視區域指定的門檻值内傳回的是一個布爾值
        if(item.isIntersecting) {
            console.log('可見')
            item.target.src = item.target.getAttribute('data-src')
            // 這裡資源加載後就停止進行觀察
            lazyIntersection.unobserve(item.target)
        }
        // console.log(item)
    })
}, options)

let data = Array.from(document.querySelectorAll('img'))
data.forEach(item => {
    // observe用來觀察指定的DOM節點
    lazyIntersection.observe(item)
})      

大圖檔渲染優化

你也許見過類似這張圖:

雜談:電商平台中的圖檔資源優化實戰

普通圖檔加載方式和後來被提出的jpg漸進式加載、png交錯式加載相比簡直不值一提。

事實上,漸進式加載在實際使用中更加常見 —— 筆者推薦jpeg格式的漸進式加載方式:你不必完整的下載下傳完畢圖檔,就可以看到圖檔的内容了。

沒錯它是一種由模糊到清晰的加載方式。

看到好多文章用JS模仿這種方式加載圖檔。但是…你把圖檔産生的 http 消耗轉換為單線程js阻塞。結果是使用者看到頁面/可互動時間變長了。這樣好麼?

用 photoshop 生成圖檔時有個“存儲為web所用格式”,然後,其中那個連續勾選就是漸進式JPEG圖檔了:

切記!需要勾選那個轉換為sRGB選項,在某些浏覽器下,圖像設定為CMYK會出現一些問題!

FireWorks等圖像軟體也是有類似的輸出設定的。

漸進式圖檔的優缺點

  1. 漸進式圖檔一開始大小架構就定好,不會像基本式圖檔一樣,由于尺寸未設定而造成回流 —— 提高的渲染性能;
  2. 漸進式圖檔也有不足,就是吃CPU吃記憶體。

長圖渲染優化

在電商場景中,最難受的其實是商品詳情頁面,這裡營運可能會配置一些商品的較長的描述圖文。不僅對圖檔的品質會比較高,同時圖檔也會非常長。那麼很顯然,我們并不可能說直接拿到圖檔就顯示在頁面上,如果使用者的網速比較慢的情況下,頁面上就會直接出現一個很長的白條,或者一張加載失敗的錯誤圖。這些很明顯不是我們想要的結果。

怎麼辦呢?

筆者研究了我司的圖檔處理工具:當你點開看大圖時,會發現隻顯示了圖檔的一部分,或者在大圖區域第一次隻展示最頂部一小部分的内容。

依照這個思路,我們可以做相應的切圖優化,将一張長圖分成多個等比例大小的多張圖塊,來進行一個分批渲染調優,減少單次渲染長圖的壓力。

這個步驟顯然是後端處理的 ——拿Node來說,你可以下載下傳 ​

​gm​

​​​1​​​ 或是 ​

​ImageMagic​

​​​2​​

npm install --save gm      

gm 中提供了用于剪裁的函數:

gm("img.png").crop(width, height, x, y)      
ImageMagic 更加強大,不僅可以剪裁圖檔,還支援 圖檔格式轉換 和 圖檔識别 等功能!

處理完成後可以存放在專門的目錄下,然後監聽 ​

​scroll​

​​ 或 ​

​IntersectionObserver​

​ API在使用者下滑時不斷請求“下一段”圖檔。

圖檔異常處理

圖檔的異常分為兩種:加載異常 和 渲染異常。

加載異常

加載異常就是指“由于各種原因導緻的圖檔請求失敗”。因為圖檔屬于資源,資源的異常會觸發一個 ​

​Event​

​​ 接口的 ​

​error​

​​ 事件,這些 error 事件不會向上冒泡到 window,但能被捕獲。而​

​window.onerror​

​​不能監測捕獲。

但可以被​​

​addEventListener​

​捕獲:

// 圖檔、script、css加載錯誤,都能被捕獲
<script>
  window.addEventListener('error', (error) => {
    console.log('捕獲到異常:', error);
  }, true)
</script>
<img src="https://yun.tuia.cn/image/kkk.png">
<script src="https://yun.tuia.cn/foundnull.js"></script>
<link href="https://yun.tuia.cn/foundnull.css" rel="stylesheet"/>
  
// new Image錯誤,不能捕獲
<script>
  window.addEventListener('error', (error) => {
    console.log('捕獲到異常:', error);
  }, true)
</script>
<script>
  new Image().src = 'https://yun.tuia.cn/image/lll.png'
</script>

// fetch錯誤,不能捕獲
<script>
  window.addEventListener('error', (error) => {
    console.log('捕獲到異常:', error);
  }, true)
</script>
<script>
  fetch('https://tuia.cn/test')
</script>      

渲染異常

  1. gm官網:​​http://www.graphicsmagick.org/​​ ​​↩︎​​
  2. ImageMagic官網:​​https://imagemagick.org/​​ ​​↩︎​​

繼續閱讀