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>

        性能優(yōu)化之全面圖片改造方案

        共 10914字,需瀏覽 22分鐘

         ·

        2022-06-30 10:37

        術(shù)  堅(jiān)  

        背景

        在最近觀察業(yè)務(wù)表現(xiàn)過(guò)程中,注意到系統(tǒng)中圖片占較大比重,但是圖片的加載經(jīng)常會(huì)出現(xiàn)空白閃爍等等的一些體驗(yàn)問(wèn)題,部分頁(yè)面如下

        一些場(chǎng)景的加載卡頓截取

        可以看到是典型的圖文為主的展示頁(yè)面,系統(tǒng)內(nèi)有多處類(lèi)似的場(chǎng)景。并且加載首屏的圖片資源消耗也是非常耗時(shí),lighthouse對(duì)課程列表的分析結(jié)果。圖片比重和大小都偏大。

        因此這里做優(yōu)化的收益是比較明顯的能給用戶(hù)和公司帶來(lái)收益的。但是缺少一個(gè)系統(tǒng)化的優(yōu)化流程。

        開(kāi)始之前

        在開(kāi)始之前我們先對(duì)一些基本只是有些了解,如圖片格式,什么是無(wú)損和有損壓縮。

        回顧下圖片格式

        既然是說(shuō)圖片加載,那么我們先對(duì)常見(jiàn)的圖片格式做一個(gè)梳理和回顧,因?yàn)楦袷揭彩怯绊憟D片加載的一個(gè)重要因素,簡(jiǎn)單列舉一下常見(jiàn)的圖片格式:

        • jpg/jpeg
        • png
        • gif
        • WebBP
        • Avif
        • Jpeg xl

        無(wú)損 OR 有損

        有損壓縮

        維基百科定義:有損數(shù)據(jù)壓縮(英語(yǔ):lossy compression)是一種數(shù)據(jù)壓縮[1]方法,經(jīng)過(guò)此方法壓縮、解壓的數(shù)據(jù)會(huì)與原始數(shù)據(jù)不同但是非常接近。有損數(shù)據(jù)壓縮又稱(chēng)破壞性資料壓縮、不可逆壓縮。有損數(shù)據(jù)壓縮借由將次要的數(shù)據(jù)舍棄,犧牲一些質(zhì)量來(lái)減少數(shù)據(jù)量、提高壓縮比。根據(jù)各種格式設(shè)計(jì)的不同,有損數(shù)據(jù)壓縮都會(huì)有代間損失[2]——每次壓縮與解壓文件都會(huì)帶來(lái)漸進(jìn)的質(zhì)量下降。

        由于有損壓縮減少了文件本身的數(shù)據(jù)量,且以犧牲圖像質(zhì)量為代價(jià),因此壓縮后的文件無(wú)論是在磁盤(pán)占用還是內(nèi)存占用上都會(huì)比原始圖像要小。針對(duì)于目前探討的圖片加載方式,對(duì)應(yīng)的都是有損壓縮,目標(biāo)都是更小的內(nèi)存占用和更快的解碼速度。

        無(wú)損壓縮

        維基百科定義:無(wú)損數(shù)據(jù)壓縮(Lossless Compression),是指資料經(jīng)過(guò)壓縮[3]后,信息不被破壞,還能完全恢復(fù)到壓縮前的原樣。相比之下,有損數(shù)據(jù)壓縮[4]只允許一個(gè)近似原始資料進(jìn)行重建,以換取更好的壓縮率。無(wú)損數(shù)據(jù)壓縮在許多應(yīng)用程序中使用。例如,ZIP[5]gzip[6]。無(wú)損數(shù)據(jù)壓縮通常用于嚴(yán)格要求“經(jīng)過(guò)壓縮、解壓縮的資料必須與原始資料一致”的場(chǎng)合。

        無(wú)損壓縮的方法可以通過(guò)一些編碼手段,用結(jié)構(gòu)化的數(shù)據(jù)來(lái)減少對(duì)重復(fù)信息的磁盤(pán)占用,針對(duì)圖片來(lái)說(shuō)減少了圖片在磁盤(pán)上的空間占用。但是并不能減少圖像的內(nèi)存占用量,這是因?yàn)?,?dāng)從磁盤(pán)或網(wǎng)絡(luò)請(qǐng)求上獲取圖像時(shí),瀏覽器又會(huì)對(duì)圖片進(jìn)行解碼,把丟失的像素用適當(dāng)?shù)念伾畔⑻畛溥M(jìn)來(lái)。

        因此如果要減少圖像占用內(nèi)存的容量,就必須使用有損壓縮方法。

        聊一聊webp

        概念一覽

        WebP 是一種現(xiàn)代圖像格式,可為 Web 上的圖像提供卓越的無(wú)損和有損壓縮。使用 WebP可以創(chuàng)建更小、更豐富的圖像,從而使 Web 更快。與 PNG 相比,WebP 無(wú)損圖像的大小要小 26% 。[7]在同等 SSIM[8]質(zhì)量指數(shù)下, WebP 有損圖像比可比較的 JPEG 圖像小 25-34% 。[9]無(wú)損 WebP支持透明度(也稱(chēng)為 alpha 通道),成本僅為22% 額外字節(jié)[10]。對(duì)于可以接受有損 RGB 壓縮的情況,有損 WebP 還支持透明度,通常提供比 PNG 小 3 倍的文件大小。

        來(lái)個(gè)直觀體驗(yàn)

        也可以戳這里看下社區(qū)其他同學(xué)做的對(duì)比效果[11],可以看到webp在圖片體積和效果上都做的不錯(cuò),很適合我們的場(chǎng)景。并且webp的使用目前已經(jīng)比較廣泛,如在youtube以及抖音pc上都可以看到。

        Youtube 部分頁(yè)面的截取,在封面圖等大圖場(chǎng)景均使用的webp格式

        抖音pc站

        壓縮技術(shù)

        webp的壓縮技術(shù)基于 VP8[12]關(guān)鍵幀編碼,無(wú)損 WebP 壓縮使用已知的圖像片段來(lái)精確地重建新的像素,在無(wú)法找到相應(yīng)的匹配值的情況下,使用本地調(diào)色板進(jìn)行優(yōu)化。在webp的開(kāi)發(fā)者平臺(tái)已經(jīng)有詳細(xì)的壓縮技術(shù)的推演,可以直接戳這里[13]看下。

        WebP 應(yīng)用效果

        隨著瀏覽器對(duì) WebP 支持的普及,目前也有越來(lái)越多的互聯(lián)網(wǎng)開(kāi)始使用 WebP,這里分享幾個(gè)數(shù)據(jù):

        • YouTube 的視頻略縮圖采用 WebP 后,網(wǎng)頁(yè)加載速度提升了 10%;
        • Google Chrome 應(yīng)用商店采用 WebP 后,每天可以節(jié)省幾 TB 的帶寬,頁(yè)面加載時(shí)間減少了30% 左右;
        • 花瓣網(wǎng)在 2017 年 5 月開(kāi)啟 WebP 后,在網(wǎng)站總體請(qǐng)求量沒(méi)有減少的情況下,整體帶寬下降了近 50%。

        結(jié)論:無(wú)論是技術(shù)上還是使用上都已經(jīng)得到了可行的驗(yàn)證,并且有明顯收益。

        優(yōu)化思路

        圖片的優(yōu)化分為加載階段和顯示階段。

        加載階段

        圖片體積

        圖片體積直接反應(yīng)了網(wǎng)路需要加載的時(shí)間,等同于磁盤(pán)占用,因此減少圖片體積能直接減少圖片請(qǐng)求的時(shí)間。進(jìn)而在首屏提升FCP等相關(guān)指標(biāo),讓瀏覽器能更快拿到數(shù)據(jù)進(jìn)行繪制。

        內(nèi)存占用

        內(nèi)存占用和圖片體積不等同,兩張不同體積的圖片可能有著相同的內(nèi)存占用,因此優(yōu)化內(nèi)存占用可以讓瀏覽器解碼圖片和光柵化的時(shí)間減少,因?yàn)椴恍枰?jì)算繪制那么多的圖片信息。光柵化時(shí)間的減少直接影響了頁(yè)面的渲染速度,以及頁(yè)面的卡頓。

        顯示階段

        加載占位

        占位圖是為了給用戶(hù)有感知的加載,提升用戶(hù)體驗(yàn)。避免用戶(hù)等待過(guò)程中的流失。

        懶加載

        懶加載也已經(jīng)是當(dāng)前各種站點(diǎn)的常規(guī)優(yōu)化手段,懶加載盡量減少了不必要的資源請(qǐng)求以提高瀏覽器的渲染效率,減少內(nèi)存占用。并顯著減少不必要的帶寬,是為用戶(hù)和公司都省錢(qián)的方式。

        格式回退

        對(duì)于瀏覽器對(duì)不同格式的圖片支持程度不同,我們的一些優(yōu)化手段和格式可能不太適用所有瀏覽器,但是為了保證性能和體驗(yàn)并最大兼容支持的瀏覽器,我們需要對(duì)圖片進(jìn)行格式降級(jí)處理。如對(duì)于不支持webp的瀏覽器自動(dòng)降級(jí)為png。

        錯(cuò)誤占位

        錯(cuò)誤占位也是必要的一步,當(dāng)所有的嘗試都失敗后我們也需要一種良好的方式展示并給用戶(hù)感知到。比如目前業(yè)務(wù)內(nèi)的錯(cuò)誤展示。

        實(shí)踐-實(shí)驗(yàn)階段

        圖片壓縮

        對(duì)應(yīng)于我們優(yōu)化思路的加載階段,使用公司已有的平臺(tái)能力。我們可以獲得不同格式和壓縮比例的圖片。比如我們選擇壓縮比75的webp以及原圖兩種格式。webp作為默認(rèn)格式,原圖則作為backup的兜底資源。這里需要注意的是,圖片列表需要服務(wù)端的支持,因?yàn)槟壳跋到y(tǒng)的圖片是經(jīng)由服務(wù)端返回的鑒權(quán)url,因此這部分需要配合改造。

        基本格式如下

        type ImgUrlList={
         // 原圖
         origin:string,
         // webp格式
         webp:string,
         // avif格式
         avif:string,
        }

        模板配置如圖

        對(duì)于為什么圖片地址需要多個(gè),主要是為了方便我們做回退處理,遇到瀏覽器不兼容的格式就犧牲流量換取可正常展示的圖片,保證內(nèi)容可見(jiàn)。這里獲得的圖片格式消費(fèi)流程如下

        通過(guò)近一周的站點(diǎn)數(shù)據(jù)統(tǒng)計(jì),目前業(yè)務(wù)方瀏覽器數(shù)據(jù)如下,其中chrome占比78.66% ,瀏覽器版本chrome最低55,fireforx最低99,均在webp的支持范圍內(nèi)。數(shù)據(jù)均兼容不考慮移動(dòng)端瀏覽器。由于IE也存在極小的比重,所以IE應(yīng)該會(huì)是觸發(fā)降級(jí)占比最高的。

        圖片加載

        圖片加載這里是優(yōu)化思路的顯示階段的實(shí)現(xiàn),主要包含從加載占位到失敗占位的整個(gè)流程,當(dāng)然也包含懶加載。加載我們?cè)谟^測(cè)階段和穩(wěn)定階段使用了不同的方式。這里針對(duì)觀測(cè)階段的方案展開(kāi)介紹。最穩(wěn)定方案是Picturede 方式,可以在下文穩(wěn)定階段看到。

        觀測(cè)主要是為了有數(shù)據(jù)對(duì)比,這里我們使用到了xx圖片處理包來(lái)做圖片加載,主要原因有三:一經(jīng)過(guò)抖音pc和西瓜視頻的場(chǎng)景驗(yàn)證、二集成上報(bào)的能力,能夠拿到圖片的相關(guān)數(shù)據(jù)、三提供了圖片加載和回退的支持,滿(mǎn)足當(dāng)前場(chǎng)景。使用示例如下

        import type ImageObserver from 'xxxxxxxxx';
        let imgObserver: ImageObserver;

        export async function getImgObserver(): Promise<ImageObserver> {
          if (imgObserver) {
            return imgObserver;
          }
          const ImageObserverSDK = import('xxxxxxxxx');
          const LoggerSDK = import('xxxxxxxxx-logger');
          const [imgObserverSdk, logggerSdk] = await Promise.all([ImageObserverSDK, LoggerSDK]);

          const ImageObserver = imgObserverSdk?.default;
          const Logger = logggerSdk?.default;

          if (ImageObserver && Logger) {
            imgObserver = new ImageObserver({
              plugins: [Logger],
              divider: {
                dataSrc: 'src',
                backUpSrc: 'backup-src',
              },
              logger: {
                user_unique_id: 'cccccc', // TODO, 
                app_id: 111111, // TODO,       },
            });
          }
          return imgObserver;
        }

        本圖片處理包包含了圖片加載錯(cuò)誤重試的邏輯,跟我們上面圖片壓縮章節(jié)設(shè)計(jì)的圖片列表相結(jié)合,可以完成自動(dòng)回退。

        錯(cuò)誤示例如下,我們給定一個(gè)可用地址,其中src以及backup-src的第一個(gè)均不可用,預(yù)期是可以自動(dòng)降級(jí)到最后一個(gè)可用地址

        為了保證圖片加載流程的可控性,比如在圖片即將出現(xiàn)再去做響應(yīng)的加載處理。因此一些通用的默認(rèn)攔截圖片并自動(dòng)做加載處理的方式就不在適用了,因?yàn)槲覀儧](méi)辦法嚴(yán)格控制每個(gè)圖片的顯示時(shí)間也不好做攔截處理。因此懶加載我們手動(dòng)通過(guò)IntersectionObserver來(lái)實(shí)現(xiàn),基本代碼如下,其中useIntersectionObserverIntersectionObserver的一個(gè)實(shí)現(xiàn)封裝。

          const observerCb: IntersectionObserverCallback = useCallback((entrys, observer) => {
            const entry = entrys[0];
            if (entry.isIntersecting) {
              setImgVisible(true);
              observer.disconnect();
            }
          }, []);

          const { updateObserverEl } = useIntersectionObserver({
            cb: observerCb,
          });

        這樣我們明確控制了每個(gè)圖片的加載時(shí)機(jī),并對(duì)加載結(jié)果精細(xì)化控制和處理。在一次觀測(cè)完成后立即清除觀測(cè),完成一次加載。

        加載數(shù)據(jù)上報(bào)

        我們通過(guò)第一步獲取了可用的幾種格式,因?yàn)槲覀儾恢烙脩?hù)的瀏覽器會(huì)是什么樣子,所以不能一股腦的都換成webp格式,所以我們需要知道webp的格式加載成功了多少,我們的圖片加載耗時(shí)情況是什么樣子。有多少是回退到了原圖,加載耗時(shí)又是什么樣子。那當(dāng)我們有新的方案能不能讓用戶(hù)無(wú)縫切換過(guò)去,怎么做用戶(hù)放量等等問(wèn)題。因此我們需要對(duì)圖片加載做監(jiān)控。

        細(xì)心的你可能已經(jīng)注意到我們圖片加載部分有一個(gè)xxxxxxxx-logger,沒(méi)錯(cuò)這個(gè)就是用來(lái)做上報(bào)的,上報(bào)流程為嘗試加載->失敗重試->加載結(jié)果->上報(bào)。logger插件會(huì)收集加載過(guò)程中的圖片信息,加載時(shí)長(zhǎng),失敗情況進(jìn)行上報(bào)。這樣我們就能夠根據(jù)數(shù)據(jù)情況查看我們改造的用戶(hù)覆蓋度和使用情況,以便我們做后續(xù)分析。

        優(yōu)化反推

        這一步是對(duì)我們優(yōu)化結(jié)果的進(jìn)一步結(jié)論導(dǎo)出,什么意思呢。以我們加載的圖片類(lèi)型數(shù)據(jù)為例,如果我們的webp支持程度很好,那是不是可以實(shí)驗(yàn)性的將avif格式作為下一次的實(shí)驗(yàn)對(duì)象來(lái)驗(yàn)證更高的性能。如果我們的圖片每種格式都很慢,那么我們自然可以反推cdn來(lái)優(yōu)化解決方案。同時(shí)如果webp的不支持,也可以看下我們的降級(jí)策略是不是很好的生效了,保證的系統(tǒng)的高可用。等等。因?yàn)槲覀冇辛藬?shù)據(jù)支撐,反推變得更加容易。

        實(shí)踐-穩(wěn)定階段

        我們通過(guò)上一步的實(shí)踐已經(jīng)完成了我們需要的數(shù)據(jù)觀測(cè)和預(yù)期效果。這時(shí)我們已經(jīng)有了圖片在線上的加載耗時(shí),解碼耗時(shí),加載穩(wěn)定性相關(guān)的數(shù)據(jù),并且反推了在系統(tǒng)整體設(shè)計(jì)的上下游對(duì)圖片的限制的合理性,比如課程封面場(chǎng)景限制圖片上傳尺寸10M,但是這個(gè)限制無(wú)論如何都嚴(yán)重影響加載性能,那降低到200K是既滿(mǎn)足需要又不影響性能的適合值,那么這就是通過(guò)實(shí)驗(yàn)階段推導(dǎo)到的優(yōu)化結(jié)果。也是進(jìn)入穩(wěn)定階段的重要一步。因此上一步的實(shí)驗(yàn)階段需要盡可能有效的分析全面數(shù)據(jù)。

        上報(bào)移除+瀏覽器支持

        那么說(shuō)了一堆之后,我們穩(wěn)定階段可以做點(diǎn)什么。當(dāng)然是期望再優(yōu)化一點(diǎn),于是我們做的事情有兩個(gè),一是下掉上一步的監(jiān)控,二是變更為瀏覽器處理圖片,同時(shí)滿(mǎn)足我們的場(chǎng)景。第一步就比較明顯因?yàn)楸O(jiān)控本身是有流量損耗和代碼體積影響的。那么第二步就是加個(gè)js處理圖片降級(jí)的方式平滑過(guò)渡到瀏覽器一支持。于是就有了如下形式的代碼

          const pictureRender = () => {
            const { webp, avif, image } = remain.urlList;
            return (
              <picture>
                <source srcSet={avif} type="image/avif" />
                <source srcSet={webp} type="image/webp" />
                <img src={image} onError={() => onError?.()} {...remain} />
              </picture>
            );
          };

        這里我們使用了picture標(biāo)簽來(lái)做圖片的自動(dòng)降級(jí),關(guān)于picture標(biāo)簽的用法和場(chǎng)景可以這篇文章[14]??偟膩?lái)說(shuō)就是做響應(yīng)式圖片和自動(dòng)降級(jí)的一個(gè)比較好的方式。這里就不展開(kāi)了。我們通過(guò)上面的代碼把我們兼容的格式進(jìn)行分類(lèi)指定,以滿(mǎn)足picture的使用場(chǎng)景。示例的集中格式會(huì)在加載不滿(mǎn)足條件時(shí)依次降級(jí)。因?yàn)閜icture的加載事件最終還是會(huì)落到img標(biāo)簽上,所以我們上面的監(jiān)聽(tīng)方式依然適用。

        兼容實(shí)驗(yàn)場(chǎng)景和穩(wěn)定階段

        到這里我們已經(jīng)總結(jié)了穩(wěn)定階段和實(shí)驗(yàn)階段各自采用的加載策略。但是有一點(diǎn)好處是,這兩者是不沖突的。我們希望繼續(xù)保持對(duì)新業(yè)務(wù)場(chǎng)景開(kāi)啟實(shí)驗(yàn)觀測(cè)的能力,穩(wěn)定業(yè)務(wù)可以繼續(xù)用穩(wěn)定場(chǎng)景方案。因此我們只需要輕微改造就可以完成這個(gè)支持,完整代碼貼在下方。這里需要注意的是,雖然保留了兩者的能力,但是并不會(huì)影響首頁(yè)體積,因?yàn)楸旧韏s監(jiān)控圖片的方式也是動(dòng)態(tài)加載的,因此除了打包階段會(huì)有總包體積的占用,對(duì)系統(tǒng)性能是沒(méi)有損耗的。

        import { getImgObserver } from '../../utils/observer';
        import React, { useRef, useEffect } from 'react';
        export const ImageMonitor: React.FC<any> = (props: any) => {
          const { currentref, onError, usePicture, ...remain } = props;
          const imgNode = useRef<HTMLImageElement | null>(null);

          useEffect(() => {
            if (!usePicture) {
              const monitor = async () => {
                const observer = await getImgObserver();
                observer?.observer?.(imgNode.current).then((res: any) => {
                  if (res.code !== 0) {                                                    // 加載最終失敗
                    onError?.();
                  }
                });
              };
              monitor();
            }
          }, []);

          const pictureRender = () => {
            const { webp, avif, image } = remain.urlList;
            return (
              <picture>
                <source srcSet={avif} type="image/avif" />
                <source srcSet={webp} type="image/webp" />
                <img src={image} onError={() => onError?.()} {...remain} />
              </picture>
            );
          };

        // 兼容js處理圖片和瀏覽器原生處理圖片
          if (usePicture) {
            return pictureRender();
          }

          return (
            <img
              {...remain}
              ref={el => {
                if (!imgNode.current) {
                  imgNode.current = el;
                  currentref?.(el);
                }
              }}
              flag="monitor"
            />
          );
        };

        ?? 謝謝支持

        以上便是本次分享的全部?jī)?nèi)容,希望對(duì)你有所幫助^_^

        喜歡的話別忘了 分享、點(diǎn)贊、收藏 三連哦~。

        歡迎關(guān)注公眾號(hào) 趣談前端 收貨大廠一手好文章~



        從零搭建全棧可視化大屏制作平臺(tái)V6.Dooring

        從零設(shè)計(jì)可視化大屏搭建引擎

        Dooring可視化搭建平臺(tái)數(shù)據(jù)源設(shè)計(jì)剖析

        可視化搭建的一些思考和實(shí)踐

        基于Koa + React + TS從零開(kāi)發(fā)全棧文檔編輯器(進(jìn)階實(shí)戰(zhàn)




        點(diǎn)個(gè)在看你最好看

        瀏覽 33
        點(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>
            欧美草屄 | 精品久久久久久久久久 | 91丨豆花丨成人熟女 | 日本一区二区免费电影 | 污污视频网址 | 日本逼片 | 女人同志舌吻二区 | 污污污污污污禁 网站 | 又粗又硬又爽视频 | 影音先锋成人网 |