中文字幕精品亚洲无线码二区,国产黄a三级三级三级看三级,亚洲七七久久桃花影院,丰满少妇被猛烈进入,国产小视频在线观看网站


  管理后臺基于 搭建,組件庫是 ,本文(wen)會對糟糕的性能(neng)和用戶體驗(yan)進行多輪優化。

一、存在的問題

  核心就是上傳的(de)(de)圖像數量龐大,公(gong)司的(de)(de)網(wang)絡(luo)速度慢,被全國94%的(de)(de)網(wang)絡(luo)用戶(hu)超(chao)越。

  1

1)預覽圖顯示慢

  2

  3

2)圖像請求失敗

  上傳組件預覽圖請求失敗圖裂。

  4

  點擊(ji)上傳組(zu)件(jian)中的預覽按鈕,在(zai)大圖(tu)預覽時的可拖動(dong)區域的小圖(tu),也(ye)會請求(qiu)失敗圖(tu)裂

  5

3)上傳時間長

  上(shang)傳時間長,頁面意外關閉需重新上(shang)傳。

  6

  接下來(lai)會記錄優化(hua)的整個過程,其(qi)中(zhong)不乏(fa)一(yi)些失敗的優化(hua)手(shou)段(duan),我(wo)都(dou)做了一(yi)一(yi)記錄。

  性(xing)能(neng)優化的(de)核心是(shi)降低圖像尺寸(cun),分批次(ci)請求等(deng),體驗優化的(de)核心是(shi)增加反饋(kui)交互(hu),續傳文件等(deng)。

二、預覽小圖優化

  首先讓運營(ying)做(zuo)了一個人工的前(qian)置優化。

  原始圖先讓攝影師自(zi)行壓(ya)縮(suo)一遍,從 4.5M 壓(ya)縮(suo)到(dao) 1.5M,總容(rong)量從 2.19G 縮(suo)小到(dao) 466M。

1)質量變換

  為預覽小圖增加(公司購買了七牛云的服務),經(jing)過(guo)調(diao)試,選擇 3p 的圖片質量。

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

  三次(ci)請求的圖(tu)像尺寸(cun)縮小(xiao)了(le) 538 倍,請求效(xiao)率(lv)提升(sheng)了(le)不少。

  10

2)批量預請求

  另一個(ge)優(you)化舉措(cuo)是在上傳(chuan)之前先用腳本批量(10個(ge)一組)去請求(qiu)壓縮裁剪(jian)后的(de)圖。

  在全部請求完成后(hou)才呈現上傳區域。

useEffect(() => {
  // 預加載圖像
  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); // 關閉加載中的狀態
      })
      .catch((error: Error) => {
        console.error('批量加載失敗:', error);
  });
  return () => {
    imageLoader.clearPendingRequests(); //銷毀請(qing)求
    setImgLoading(true); // 開啟加載中(zhong)的狀態(tai)
  }
}, [current])

  11

3)骨架屏

  還(huan)給 Modal 組件賦(fu)值 loading 屬性(xing)(5.18.0新(xin)增),出現骨架屏(ping)效(xiao)果。

  12

  不(bu)過,在(zai)骨架屏消失(shi)后,本來以為預請(qing)求了一次,不(bu)會再訪(fang)問出(chu)錯,結果還(huan)是會有一定概率出(chu)錯。

  公(gong)司網(wang)絡比較慢,很容易復現(xian)此(ci)類問(wen)題(ti)。

  4

4)3次重復請求

  并且從(cong)上傳(chuan)區(qu)域(yu)的(de)預加載(zai),到(dao)加載(zai)組件(jian)內的(de)預覽圖(tu)(tu),再到(dao)拖動(dong)區(qu)域(yu)的(de)小圖(tu)(tu),總共進行了 3 次請(qing)求,太冗余(yu)。

  14

  上(shang)述優化,除(chu)了(le)質(zhi)量參數達(da)到了(le)優化的效果,其余沒有達(da)到預期,遂放棄(qi)。

三、自定義上傳列表項

1)懶加載

  Upload 組件(jian)的默認(ren)行(xing)為,是會加載組件(jian)內的所有(you)預(yu)覽圖,所以需要(yao)自定義上(shang)傳(chuan)列表項(itemRender),做懶加載優化。

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'}}>
            文件上傳中(zhong)<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">
            {/* 預覽按鈕 */}
            <a onClick={actions.preview}>
              <EyeOutlined />
            </a>
            {/* 刪除按鈕 */}
            <a onClick={actions.remove}>
              <DeleteOutlined />
            </a>
          </div>
        </div>
      </div>
    );
  }

  自定義的列(lie)表項(xiang)與默認的列(lie)表項(xiang)在(zai)行為和樣式上保(bao)持了高度的一致(zhi)。

  但是為(wei) img 增(zeng)加了 loading 懶(lan)加載屬性(xing)。

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

  經過調試發現(xian),300個圖(tu)像請求(qiu),默認會先(xian)請求(qiu)100個左右。

  15

  盡管(guan)如此,還是會有小(xiao)概率的(de)情況(kuang)出現圖(tu)裂。

2)重加載一次

  為此,在圖(tu)像請求錯誤(wu)時(shi),增加一次(ci)重新加載(zai)的機制(zhi),而可拖動區(qu)域(yu)的小圖(tu)也要加上此機制(zhi)。

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

四、可拖動區域

  5

1)網絡錯誤

  拖動區域的(de)小圖(tu)裂(lie)掉主要是因(yin)為上傳(chuan)區域的(de)圖(tu)裂(lie)了導致,兩者的(de)地址是相同的(de)。

  得到了(le)圖裂時候的(de)錯誤(wu)信息。

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

  在客戶端(你的(de)(de)瀏覽器)和服務(wu)器之(zhi)間的(de)(de) HTTP/2 通信(xin)過(guo)程(cheng)中,發生了(le)某(mou)種違反協(xie)議規則的(de)(de)事情(qing),導致連接被中止。

  查看 Chrome Net Logs (網絡日志),它可以記錄所有網絡活動。

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

  上(shang)述操(cao)作完成后,并沒(mei)有得到具體的(de)錯誤信息,可(ke)能是日志文件(jian)還沒(mei)有全部導(dao)出。

2)懶加載

  但是在(zai)拖動區(qu)域(yu),也增加了(le) loading 屬性,有(you)(you)時候即(ji)使組件(jian)內的預覽圖沒(mei)有(you)(you)顯示(shi),此處也能正確(que)顯示(shi)。

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

  17

3)選中(zhong)效果

  還增(zeng)(zeng)加了一(yi)處體驗優化,即(ji)為當前預覽(lan)小圖增(zeng)(zeng)加選中效果,就是增(zeng)(zeng)加個 2px 的邊框。

  寬高也設置了一下,還用到了CSS中的 屬性。

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

  選取(qu)關鍵字(zi) cover,將(jiang)圖像能夠等比縮放的填充(chong)滿整(zheng)個內容區(qu)域。

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

  18

五、預覽大圖

1)禁止打開預覽彈框

  預(yu)覽彈框(kuang)有(you)(you)一處體驗優化,就是(shi)當上傳(chuan)組件還(huan)有(you)(you)文件在上傳(chuan)中(zhong)時,禁(jin)止打開預(yu)覽彈框(kuang)。

  避免(mian)在彈框的拖動區域,修改(gai)圖像順序時(shi)發生異(yi)常

  19

  在預覽(lan)彈框(kuang)中,調節(jie)大(da)圖尺寸,并且(qie)為其配(pei)置 objectFit 屬性,將圖像能夠(gou)等(deng)比(bi)縮(suo)放(fang)的(de)填(tian)充到內容(rong)區(qu)域。

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

  20

2)loading切換

  由(you)于在切換(huan)預覽時的(de)加(jia)載時間較長(公司網(wang)絡較差),于是(shi)增(zeng)加(jia)了 loading 加(jia)載的(de)過渡動畫。

  21

  后續,也為大(da)圖增加了質量變換,值為 50p,確保肉眼看的(de)時候能保持(chi)清晰度(du)和(he)色(se)彩度(du)。

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

六、緩存上傳文件

1)localStorage

  在上(shang)傳按(an)鈕旁增加恢復最近(jin)數(shu)據的按(an)鈕,每次上(shang)傳時會(hui)將文件列表緩存到 localStorage 中

  22

  在(zai) Upload 組件的(de) onChange 事件中,增加緩(huan)存的(de)配置,并且為上傳(chuan)組件增加 isCache 屬(shu)性控(kong)制是否(fou)開啟緩(huan)存。

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

2)續傳失敗

  本來將(jiang) uploading 也存儲(chu)到了(le)緩(huan)存中(zhong),并且在更新狀態時,組(zu)件(jian)也顯示了(le)上傳中(zhong)的樣式,但是無法續(xu)傳。

  可能是沒有 file 對象,即沒有文件(jian)信息(xi)導致(zhi)無(wu)法上傳。

  目前就暫時無法給上傳(chuan)中的文件(jian)實現斷點(dian)續傳(chuan),所(suo)以只(zhi)能先把文件(jian)名給列出來(lai)。

  23

  在警告框中提(ti)供了復制(zhi)(zhi)按鈕(用OR分隔的名稱),點擊復制(zhi)(zhi)后,找到(dao)對應的文件(jian)夾(macOS),選中搜索,

  24

  將(jiang)得到的查詢(xun)條件復制到搜索框中,就能(neng)得到還未(wei)上傳的圖(tu)像。

  25

3)存儲溢出

  當上傳300多(duo)張圖(tu)像時,頁面(mian)報(bao)錯了(le)。意思就是存儲超過最大閾(yu)值,localStorage 最大是 5M~10M。

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

  嘗試采用數據壓(ya)縮,但仍然會報錯(cuo),只(zhi)能另(ling)辟蹊徑。

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

4)IndexedDB

  將數據緩存到(dao) IndexedDB 中,理(li)論上(shang)它可(ke)以(yi)占用(yong) 50% 的(de)磁盤(pan)空間,所以(yi)不存在溢(yi)出(chu)的(de)問題。

    /**
     * 存(cun)儲(chu)數(shu)據(支持(chi)大型(xing)數(shu)據)
     * @param key 鍵名(ming)
     * @param value 要存(cun)儲(chu)的值(可(ke)為任意類型(xing))
     */
    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('存儲數據失敗:', request.error);
          reject(request.error);
        };
      });
    }
  
    /**
     * 獲取存儲的數據
     * @param key 鍵名
     * @returns 存儲的值,若不存在則返回 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('獲取數據失敗:', request.error);
          reject(request.error);
        };
      });
    }

  為了能自(zi)動清理 IndexedDB 的(de)數據,在組(zu)件中上(shang)傳(chuan)文件時(shi),觸發清理的(de)操作,下面查看(kan)占用空間的(de)代(dai)碼。

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

  現(xian)在即使緩存了 300 多張圖像(xiang),也能實現(xian)恢復。

七、上傳反饋

1)不友好的等待

  當需要(yao)上傳幾百張(zhang)圖時,在選好(hao)圖像后,組件就會有比(bi)較長的時間沒有狀態變(bian)化。

  26

  不給用戶反饋(kui),體(ti)驗(yan)很不友好。需要在合適的(de)時機(ji)更新上傳區域(yu)的(de) loading 狀態。

  27

2)自定義上傳按鈕

  沒(mei)有找到合適的事(shi)件(jian),就(jiu)想自定義上傳按(an)鈕,然(ran)后控制按(an)鈕的點擊(ji)事(shi)件(jian),以(yi)此來(lai)更新狀態。

  Upload 組(zu)件只要在(zai)子(zi)元素位置增加按(an)鈕(niu)元素就(jiu)能替換默認(ren)的上傳按(an)鈕(niu)。

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

  但是在 Ant Design Pro 中的 ProFormUploadButton 組件(jian)內,并不能(neng)覆(fu)蓋上傳按(an)鈕。

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

3)點擊事件

  首先想到的(de)是在 ProFormUploadButton 外增加個容器元素,注冊點擊(ji)事件,通過冒泡的(de)方式觸發。

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

  但這樣的(de)話(hua),點擊范圍會比較大,并不(bu)局限于上傳按鈕,而(er)是(shi)整個區域都會冒泡觸發。

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

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

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

  在 onChange 事件中調(diao)用 setUploadAreaLoading(false) 取消 loading 狀態。

4)取消文件選擇

  但會有一個(ge)問題(ti),就是在彈出的(de)選擇文件框(kuang)中,不選擇文件,點擊取消,那么(me)就不能(neng)觸發(fa) onChange 事件。

  針(zhen)對取(qu)消按鈕,瀏覽(lan)器也沒有提供專(zhuan)門的事(shi)件。

  28

  雖然為 window 注冊 focus 事件(jian),能夠(gou)實現在點擊取消時(shi)觸發事件(jian)。

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

  但是無法(fa)準確讀(du)取 fileList 列表,當(dang)文(wen)件少的時候,fileList 會馬上更新(xin)。

  但是當(dang)文件幾百個時(shi),在(zai)觸發 focus 事件時(shi),fileList 仍然(ran)是空的。

  而(er)我優化的(de)目的(de)就(jiu)是要(yao)在加(jia)載這些文件的(de)過(guo)程中,出(chu)現 loading 過(guo)渡動畫(hua),如此就(jiu)無法判斷了。

  為 file 控件(jian)注冊(ce) change 事件(jian)的(de)確能馬(ma)上(shang)拿到 files 列(lie)表(biao)。

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 = '';   //清空已選文(wen)件
});

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

  即(ji)使在 Upload 組件內清(qing)空(kong)文件的值(zhi),也無法再次觸發事件,很奇(qi)怪。

  后面(mian)發現原(yuan)來上傳(chuan)(chuan)控件(input[type=file])在(zai)上傳(chuan)(chuan)完成(cheng)后會被刪除,之前(qian)注冊(ce)的事件就沒(mei)有了。

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

  需要自定義(yi) Upload 組件的上(shang)傳區域(yu),暫時不(bu)修改(gai)。

  先與業務方同步,告(gao)知他(ta)們開(kai)啟(qi)緩存(cun)的上傳組件會有取消彈框無法自動移除 loading 的問(wen)題。

5)事件委托

  既(ji)然不(bu)能(neng)指定控件(jian)綁定事件(jian),那么(me)可以(yi)借用委托,給容器元素注冊事件(jian)。

  如果用(yong) DOM 的方(fang)式(shi)查找容器(qi),像(xiang)下面這樣,那么當頁(ye)面中(zhong)有多個上傳組件時,就會給(gei)所有的容器(qi)都注冊(ce)事件。

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

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

const fileContainer = uploadRef.current?.nativeElement;
const onClick = (e:Event) => {
  const fileInput = e.target as HTMLInputElement;
  // 攔截非(fei)上傳控(kong)件的點擊(ji)事件
  if(!fileInput || fileInput.id !== name) {
    return;
  }
  /**
   * 在上傳區域開啟加載狀態
   * 因為當上傳幾百(bai)張圖時,整個界面就會卡住,不給用戶反饋
   */
  setUploadAreaLoading(true);
};
fileContainer?.addEventListener('click', onClick, false);

  同樣用委托的方式為 file 控件(jian)(jian)增加(jia) change 事(shi)件(jian)(jian),當有文件(jian)(jian)時,更(geng)新(xin) loading 狀態。

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

  這樣(yang)就能實現在選中文件(jian)后,組件(jian)顯示 loading 等待狀態(tai),取消選擇框,也能關(guan)閉等待狀態(tai)。

 

 posted on 2025-10-27 10:34  咖啡機(K.F.J)  閱讀(1812)  評論(3)    收藏  舉報