<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

        管理后臺基于 Ant Design Pro 搭建,組件庫是 Ant Design 5.20,本文會(huì)對糟糕的性能和用戶體驗(yàn)進(jìn)行多輪優(yōu)化。

      一、存在的問題

        核心就是上傳的圖像數(shù)量龐大,公司的網(wǎng)絡(luò)速度慢,被全國94%的網(wǎng)絡(luò)用戶超越。

        1

      1)預(yù)覽圖顯示慢

        2

        3

      2)圖像請求失敗

        上傳組件預(yù)覽圖請求失敗圖裂。

        4

        點(diǎn)擊上傳組件中的預(yù)覽按鈕,在大圖預(yù)覽時(shí)的可拖動(dòng)區(qū)域的小圖,也會(huì)請求失敗圖裂

        5

      3)上傳時(shí)間長

        上傳時(shí)間長,頁面意外關(guān)閉需重新上傳。

        6

        接下來會(huì)記錄優(yōu)化的整個(gè)過程,其中不乏一些失敗的優(yōu)化手段,我都做了一一記錄。

        性能優(yōu)化的核心是降低圖像尺寸,分批次請求等,體驗(yàn)優(yōu)化的核心是增加反饋交互,續(xù)傳文件等。

      二、預(yù)覽小圖優(yōu)化

        首先讓運(yùn)營做了一個(gè)人工的前置優(yōu)化。

        原始圖先讓攝影師自行壓縮一遍,從 4.5M 壓縮到 1.5M,總?cè)萘繌?2.19G 縮小到 466M。

      1)質(zhì)量變換

        為預(yù)覽小圖增加質(zhì)量變換(公司購買了七牛云的服務(wù)),經(jīng)過調(diào)試,選擇 3p 的圖片質(zhì)量。

      https://static.xxx.com/activity/review/1746525272362kzrjtd.jpg

        7

      https://static.xxx.com/activity/review/1746525272362kzrjtd.jpg?imageMogr2/thumbnail/!5p

        8

      https://static.xxx.com/activity/review/1746525272362kzrjtd.jpg?imageMogr2/thumbnail/!3p

        9

        三次請求的圖像尺寸縮小了 538 倍,請求效率提升了不少。

        10

      2)批量預(yù)請求

        另一個(gè)優(yōu)化舉措是在上傳之前先用腳本批量(10個(gè)一組)去請求壓縮裁剪后的圖。

        在全部請求完成后才呈現(xiàn)上傳區(qū)域。

      useEffect(() => {
        // 預(yù)加載圖像
        const imageLoader = new BatchImageLoader({ batchSize: 10 });
        const imageUrls = current.reviews ? current.reviews.map(url => scaleImg(url)) : [];
        // 批量加載
        imageUrls.length > 0 && imageLoader.loadImagesBatch(imageUrls)
            .then((results: ImageLoadResult[]) => {
              setImgLoading(false); // 關(guān)閉加載中的狀態(tài)
            })
            .catch((error: Error) => {
              console.error('批量加載失敗:', error);
        });
        return () => {
          imageLoader.clearPendingRequests(); //銷毀請求
          setImgLoading(true); // 開啟加載中的狀態(tài)
        }
      }, [current])

        11

      3)骨架屏

        還給 Modal 組件賦值 loading 屬性(5.18.0新增),出現(xiàn)骨架屏效果。

        12

        不過,在骨架屏消失后,本來以為預(yù)請求了一次,不會(huì)再訪問出錯(cuò),結(jié)果還是會(huì)有一定概率出錯(cuò)。

        公司網(wǎng)絡(luò)比較慢,很容易復(fù)現(xiàn)此類問題。

        4

      4)3次重復(fù)請求

        并且從上傳區(qū)域的預(yù)加載,到加載組件內(nèi)的預(yù)覽圖,再到拖動(dòng)區(qū)域的小圖,總共進(jìn)行了 3 次請求,太冗余。

        14

        上述優(yōu)化,除了質(zhì)量參數(shù)達(dá)到了優(yōu)化的效果,其余沒有達(dá)到預(yù)期,遂放棄。

      三、自定義上傳列表項(xiàng)

      1)懶加載

        Upload 組件的默認(rèn)行為,是會(huì)加載組件內(nèi)的所有預(yù)覽圖,所以需要自定義上傳列表項(xiàng)(itemRender),做懶加載優(yōu)化。

      const itemRender = (originNode: React.ReactElement, file: UploadFile, fileList:UploadFile[], actions: {
          download: () => void;
          preview: () => void;
          remove: () => void;
        }) => {
          return (
            <div className="ant-upload-list-item-container">
              <div className="ant-upload-list-item ant-upload-list-item-done">
              {file.status === 'uploading' ? (
                <div style={{width: '100%', textAlign:'center'}}>
                  文件上傳中<Progress percent={file.percent} showInfo={false} size="small" strokeWidth={2}/>
                </div>
              ) : 
                <span className="ant-upload-list-item-thumbnail">
                  <img 
                    src={file.thumbUrl} 
                    alt={file.name} 
                    className="ant-upload-list-item-image" 
                    loading='lazy' 
                    onError={e => onImgError(e, file.thumbUrl)}
                  />
                </span>
              }
                <div className="ant-upload-list-item-actions">
                  {/* 預(yù)覽按鈕 */}
                  <a onClick={actions.preview}>
                    <EyeOutlined />
                  </a>
                  {/* 刪除按鈕 */}
                  <a onClick={actions.remove}>
                    <DeleteOutlined />
                  </a>
                </div>
              </div>
            </div>
          );
        }

        自定義的列表項(xiàng)與默認(rèn)的列表項(xiàng)在行為和樣式上保持了高度的一致。

        但是為 img 增加了 loading 懶加載屬性。

      • loading="lazy" 告訴瀏覽器,在用戶滾動(dòng)至圖片附近時(shí)才開始加載圖片。
      • loading="eager"(這是默認(rèn)值)則指示瀏覽器在頁面加載時(shí)立即加載所有圖片。

        經(jīng)過調(diào)試發(fā)現(xiàn),300個(gè)圖像請求,默認(rèn)會(huì)先請求100個(gè)左右。

        15

        盡管如此,還是會(huì)有小概率的情況出現(xiàn)圖裂。

      2)重加載一次

        為此,在圖像請求錯(cuò)誤時(shí),增加一次重新加載的機(jī)制,而可拖動(dòng)區(qū)域的小圖也要加上此機(jī)制。

      // 預(yù)覽圖像發(fā)生錯(cuò)誤時(shí)的事件 data-retried
      const onImgError = (e:SyntheticEvent, src:string|undefined) => {
          const img = e.target as HTMLImageElement;
          // 只重新加載一次
          if (!img.dataset.retried) {
            img.dataset.retried = 'true';
            src && (img.src = src);
            console.error('重新加載', src);
          }
      }

      四、可拖動(dòng)區(qū)域

        5

      1)網(wǎng)絡(luò)錯(cuò)誤

        拖動(dòng)區(qū)域的小圖裂掉主要是因?yàn)樯蟼鲄^(qū)域的圖裂了導(dǎo)致,兩者的地址是相同的。

        得到了圖裂時(shí)候的錯(cuò)誤信息。

      https://static.xxx.com/activity/review/1757929803424ukyomp.jpg?imageMogr2/thumbnail/!3p net::ERR_HTTP2_PROTOCOL_ERROR 200 (OK)

        在客戶端(你的瀏覽器)和服務(wù)器之間的 HTTP/2 通信過程中,發(fā)生了某種違反協(xié)議規(guī)則的事情,導(dǎo)致連接被中止。

        查看 Chrome Net Logs (網(wǎng)絡(luò)日志),它可以記錄所有網(wǎng)絡(luò)活動(dòng)。

      1. 在 Chrome 地址欄輸入 chrome://net-export/。
      2. 點(diǎn)擊 "Start Logging to Disk" 并保存一個(gè)文件。
      3. 在瀏覽器中復(fù)現(xiàn)這個(gè)錯(cuò)誤。
      4. 返回 chrome://net-export/ 點(diǎn)擊 "Stop Logging"。
      5. 將日志文件上傳到 https://netlog-viewer.appspot.com 進(jìn)行分析。搜索 ERR_HTTP2_PROTOCOL_ERROR,可以看到更詳細(xì)的錯(cuò)誤上下文。

        上述操作完成后,并沒有得到具體的錯(cuò)誤信息,可能是日志文件還沒有全部導(dǎo)出。

      2)懶加載

        但是在拖動(dòng)區(qū)域,也增加了 loading 屬性,有時(shí)候即使組件內(nèi)的預(yù)覽圖沒有顯示,此處也能正確顯示。

      <img src={item.thumbUrl} loading='lazy' />

        17

      3)選中效果

        還增加了一處體驗(yàn)優(yōu)化,即為當(dāng)前預(yù)覽小圖增加選中效果,就是增加個(gè) 2px 的邊框。

        寬高也設(shè)置了一下,還用到了CSS中的 object-fit 屬性。

      • cover:用于被替換的內(nèi)容在保持其寬高比的同時(shí),填充元素的整個(gè)內(nèi)容框。
      • contain:用于被替換的內(nèi)容將被縮放,以在填充元素的內(nèi)容框時(shí)保持其寬高比。

        選取關(guān)鍵字 cover,將圖像能夠等比縮放的填充滿整個(gè)內(nèi)容區(qū)域。

      {
        width:'100%';
        height: 45;
        object-fit: 'cover';
        border: 2px solid #a0d911;
      }

        18

      五、預(yù)覽大圖

      1)禁止打開預(yù)覽彈框

        預(yù)覽彈框有一處體驗(yàn)優(yōu)化,就是當(dāng)上傳組件還有文件在上傳中時(shí),禁止打開預(yù)覽彈框。

        避免在彈框的拖動(dòng)區(qū)域,修改圖像順序時(shí)發(fā)生異常

        19

        在預(yù)覽彈框中,調(diào)節(jié)大圖尺寸,并且為其配置 objectFit 屬性,將圖像能夠等比縮放的填充到內(nèi)容區(qū)域。

      <img 
       style={{ width: '100%', height: '60vh', marginTop: 5, objectFit: 'contain' }} 
       src='https://static.allreaday.com/xxx.jpg' 
       alt="預(yù)覽圖"
      />

        20

      2)loading切換

        由于在切換預(yù)覽時(shí)的加載時(shí)間較長(公司網(wǎng)絡(luò)較差),于是增加了 loading 加載的過渡動(dòng)畫。

        21

        后續(xù),也為大圖增加了質(zhì)量變換,值為 50p,確保肉眼看的時(shí)候能保持清晰度和色彩度。

      https://static.xxx.com/xxx.jpg?imageMogr2/thumbnail/!50p

      六、緩存上傳文件

      1)localStorage

        在上傳按鈕旁增加恢復(fù)最近數(shù)據(jù)的按鈕,每次上傳時(shí)會(huì)將文件列表緩存到 localStorage 中

        22

        在 Upload 組件的 onChange 事件中,增加緩存的配置,并且為上傳組件增加 isCache 屬性控制是否開啟緩存。

      const onChange = (data: UploadChangeParam) => {
        const { fileList } = data;
        // ...
        isCache && setUploadImageCache(fileList);
        // ...
      }
      /**
       * 緩存上傳文件
       */
      export function setUploadImageCache(data:UploadFile[]) {
        localStorage.setItem('upload_image', JSON.stringify(data));
      }

      2)續(xù)傳失敗

        本來將 uploading 也存儲(chǔ)到了緩存中,并且在更新狀態(tài)時(shí),組件也顯示了上傳中的樣式,但是無法續(xù)傳。

        可能是沒有 file 對象,即沒有文件信息導(dǎo)致無法上傳。

        目前就暫時(shí)無法給上傳中的文件實(shí)現(xiàn)斷點(diǎn)續(xù)傳,所以只能先把文件名給列出來。

        23

        在警告框中提供了復(fù)制按鈕(用OR分隔的名稱),點(diǎn)擊復(fù)制后,找到對應(yīng)的文件夾(macOS),選中搜索,

        24

        將得到的查詢條件復(fù)制到搜索框中,就能得到還未上傳的圖像。

        25

      3)存儲(chǔ)溢出

        當(dāng)上傳300多張圖像時(shí),頁面報(bào)錯(cuò)了。意思就是存儲(chǔ)超過最大閾值,localStorage 最大是 5M~10M。

      QuotaExceededError: Failed to execute 'setItem' on 'Storage': Setting the value of 'upload_image' exceeded the quota.

        嘗試采用數(shù)據(jù)壓縮,但仍然會(huì)報(bào)錯(cuò),只能另辟蹊徑。

      /**
       * 緩存上傳文件
       */
      export function setUploadImageCache(data:UploadFile[]) {
        // 壓縮數(shù)據(jù)
        const compress = btoa(encodeURIComponent(JSON.stringify(data)));
        localStorage.setItem('upload_image', compress);
      }
      /**
       * 讀取上傳文件的緩存
       */
      export function getUploadImageCache() {
        const files = localStorage.getItem('upload_image');
        if(!files) return [];
        // 解壓數(shù)據(jù)
        const uncompress = decodeURIComponent(atob(files));
        return JSON.parse(uncompress);
      }

      4)IndexedDB

        將數(shù)據(jù)緩存到 IndexedDB 中,理論上它可以占用 50% 的磁盤空間,所以不存在溢出的問題。

          /**
           * 存儲(chǔ)數(shù)據(jù)(支持大型數(shù)據(jù))
           * @param key 鍵名
           * @param value 要存儲(chǔ)的值(可為任意類型)
           */
          async setItem(key: string, value: any): Promise<void> {
            if (!this.db) await this.init();
        
            return new Promise<void>((resolve, reject) => {
              const transaction = this.db!.transaction(['largeData'], 'readwrite');
              const store = transaction.objectStore('largeData');
              const item: StoredItem = { key, value, timestamp: Date.now() };
        
              const request: IDBRequest = store.put(item);
        
              request.onsuccess = () => resolve();
              request.onerror = () => {
                console.error('存儲(chǔ)數(shù)據(jù)失敗:', request.error);
                reject(request.error);
              };
            });
          }
        
          /**
           * 獲取存儲(chǔ)的數(shù)據(jù)
           * @param key 鍵名
           * @returns 存儲(chǔ)的值,若不存在則返回 null
           */
          async getItem(key: string): Promise<any> {
            if (!this.db) await this.init();
        
            return new Promise<any>((resolve, reject) => {
              const transaction = this.db!.transaction(['largeData'], 'readonly');
              const store = transaction.objectStore('largeData');
              const request: IDBRequest = store.get(key);
        
              request.onsuccess = () => {
                const result = request.result as StoredItem | undefined;
                resolve(result ? result.value : null);
              };
        
              request.onerror = () => {
                console.error('獲取數(shù)據(jù)失敗:', request.error);
                reject(request.error);
              };
            });
          }

        為了能自動(dòng)清理 IndexedDB 的數(shù)據(jù),在組件中上傳文件時(shí),觸發(fā)清理的操作,下面查看占用空間的代碼。

      const quota = await navigator.storage.estimate();
      console.log(`已用空間: ${quota.usage} 字節(jié)`);
      console.log(`總配額: ${quota.quota} 字節(jié)`);
      const percentageUsed = (quota.usage / quota.quota) * 100;
      console.log(`已使用可用存儲(chǔ)的 ${percentageUsed}%`);

        現(xiàn)在即使緩存了 300 多張圖像,也能實(shí)現(xiàn)恢復(fù)。

      七、上傳反饋

      1)不友好的等待

        當(dāng)需要上傳幾百張圖時(shí),在選好圖像后,組件就會(huì)有比較長的時(shí)間沒有狀態(tài)變化。

        26

        不給用戶反饋,體驗(yàn)很不友好。需要在合適的時(shí)機(jī)更新上傳區(qū)域的 loading 狀態(tài)。

        27

      2)自定義上傳按鈕

        沒有找到合適的事件,就想自定義上傳按鈕,然后控制按鈕的點(diǎn)擊事件,以此來更新狀態(tài)。

        Upload 組件只要在子元素位置增加按鈕元素就能替換默認(rèn)的上傳按鈕。

      <Upload {...props}>
        <Button icon={<UploadOutlined />}>Click to Upload</Button>
      </Upload>

        但是在 Ant Design Pro 中的 ProFormUploadButton 組件內(nèi),并不能覆蓋上傳按鈕。

        除非自定義 ProFormUploadButton 組件的邏輯,但開發(fā)成本較高。

      3)點(diǎn)擊事件

        首先想到的是在 ProFormUploadButton 外增加個(gè)容器元素,注冊點(diǎn)擊事件,通過冒泡的方式觸發(fā)。

      <div onClick={}>
        <ProFormUploadButton />
      </div>

        但這樣的話,點(diǎn)擊范圍會(huì)比較大,并不局限于上傳按鈕,而是整個(gè)區(qū)域都會(huì)冒泡觸發(fā)。

        然后想到的是直接給生成的 input[type=file] 控件注冊點(diǎn)擊事件。

      document.getElementById(name)?.addEventListener('click', () => {
        setUploadAreaLoading(true); 
      });

        組件的屬性 name 會(huì)渲染成 input 按鈕的 id 屬性。

        在 onChange 事件中調(diào)用 setUploadAreaLoading(false) 取消 loading 狀態(tài)。

      4)取消文件選擇

        但會(huì)有一個(gè)問題,就是在彈出的選擇文件框中,不選擇文件,點(diǎn)擊取消,那么就不能觸發(fā) onChange 事件。

        針對取消按鈕,瀏覽器也沒有提供專門的事件。

        28

        雖然為 window 注冊 focus 事件,能夠?qū)崿F(xiàn)在點(diǎn)擊取消時(shí)觸發(fā)事件。

      window.addEventListener('focus', function() {
        const fileInput = document.getElementById(name) as HTMLInputElement;
        console.log(fileInput && fileInput.files)
      });

        但是無法準(zhǔn)確讀取 fileList 列表,當(dāng)文件少的時(shí)候,fileList 會(huì)馬上更新。

        但是當(dāng)文件幾百個(gè)時(shí),在觸發(fā) focus 事件時(shí),fileList 仍然是空的。

        而我優(yōu)化的目的就是要在加載這些文件的過程中,出現(xiàn) loading 過渡動(dòng)畫,如此就無法判斷了。

        為 file 控件注冊 change 事件的確能馬上拿到 files 列表。

      document.getElementById(name)?.addEventListener('change', (e) => {
        const fileInput = e.target as HTMLInputElement;
        if(!fileInput) return;
        const files = fileInput.files;
        console.log('change', files);
        if(files && files.length > 0) {
          setUploadAreaLoading(true);
        }
        // fileInput.value = '';   //清空已選文件
      });

        但是在 Upload 組件中,再次上傳時(shí),卻無法觸發(fā) file 控件 change 事件。

        即使在 Upload 組件內(nèi)清空文件的值,也無法再次觸發(fā)事件,很奇怪。

        后面發(fā)現(xiàn)原來上傳控件(input[type=file])在上傳完成后會(huì)被刪除,之前注冊的事件就沒有了。

      <span class="ant-upload">
        <input id="reviews" type="file" accept="image/*" multiple="" style="display: none;">
      </span>

        需要自定義 Upload 組件的上傳區(qū)域,暫時(shí)不修改。

        先與業(yè)務(wù)方同步,告知他們開啟緩存的上傳組件會(huì)有取消彈框無法自動(dòng)移除 loading 的問題。

      5)事件委托

        既然不能指定控件綁定事件,那么可以借用委托,給容器元素注冊事件。

        如果用 DOM 的方式查找容器,像下面這樣,那么當(dāng)頁面中有多個(gè)上傳組件時(shí),就會(huì)給所有的容器都注冊事件。

      document.getElementsByClassName('ant-upload-select')

        而 Upload 提供了 ref,可以通過該屬性注冊特定組件內(nèi)的容器。

      const fileContainer = uploadRef.current?.nativeElement;
      const onClick = (e:Event) => {
        const fileInput = e.target as HTMLInputElement;
        // 攔截非上傳控件的點(diǎn)擊事件
        if(!fileInput || fileInput.id !== name) {
          return;
        }
        /**
         * 在上傳區(qū)域開啟加載狀態(tài)
         * 因?yàn)楫?dāng)上傳幾百張圖時(shí),整個(gè)界面就會(huì)卡住,不給用戶反饋
         */
        setUploadAreaLoading(true);
      };
      fileContainer?.addEventListener('click', onClick, false);

        同樣用委托的方式為 file 控件增加 change 事件,當(dāng)有文件時(shí),更新 loading 狀態(tài)。

      const onChange = (e:Event) => {
        const fileInput = e.target as HTMLInputElement;
        // 攔截非上傳控件的點(diǎn)擊事件
        if(!fileInput || fileInput.id !== name) {
          return;
        }
        const files = fileInput.files;
        if(files && files.length > 0) {
          setUploadAreaLoading(true);
        }
      };
      fileContainer?.addEventListener('change', onChange, false);

        這樣就能實(shí)現(xiàn)在選中文件后,組件顯示 loading 等待狀態(tài),取消選擇框,也能關(guān)閉等待狀態(tài)。

       

       posted on 2025-10-27 10:34  咖啡機(jī)(K.F.J)  閱讀(1698)  評論(3)    收藏  舉報(bào)
      主站蜘蛛池模板: 国产精品午夜福利片国产| 亚洲国产日韩A在线亚洲| 亚洲成人精品一区二区中| 亚洲成人av综合一区| 永久免费无码av在线网站| 嫖妓丰满肥熟妇在线精品| 国产大学生自拍三级视频| 亚洲国产精品日韩在线| 日韩丝袜欧美人妻制服| 日本一区二区三本视频在线观看| 亚洲国产成人久久精品APP | 国产精一区二区黑人巨大| 日本一区二区不卡精品| www久久只有这里有精品| 国产AV无码专区亚洲AV漫画| 国产精品美女自慰喷水| 亚洲成在人天堂一区二区| 18岁日韩内射颜射午夜久久成人| 五月开心六月丁香综合色啪| 亚洲成在人线AV品善网好看| 成人免费A级毛片无码片2022| 丰满人妻被黑人猛烈进入| 丰满少妇呻吟高潮经历| 乱人伦人妻系列| 亚洲欧美日韩综合一区二区| 开心五月深深爱天天天操| 91中文字幕一区在线| 国产高清在线精品一区APP| 国产精品高清视亚洲乱码| 色成人亚洲| 亚洲人成网站在小说| 欧美牲交a欧美牲交aⅴ图片| 人妻少妇偷人精品一区| 激情亚洲专区一区二区三区| 中文字幕日韩精品亚洲一区| 久久88香港三级台湾三级播放| 日韩一区精品视频一区二区| 中文字幕在线精品人妻| 日本边添边摸边做边爱| 色综合久久精品亚洲国产| 亚洲欧美日本久久网站|