[Next js] browser-image-compression를 사용한 이미지 성능최적화_(이미지 압축 라이브러리,AVIF 파일변환)

임보라·2024년 11월 19일

Next.js

목록 보기
2/23

갤러리에서 처음 사용자가 이미지를 올릴 때 용량제한을 두지않아 이미지가 용량이 크고 양이 많아질수록 페이지 로드되는 속도가 현저히 낮아지는 현상발생

이전코드 문제점

  1. 압축 또는 이미지 최적화 안함
  2. 반복적인 상태 업데이트
    setImgSrc((prev) => [...prev, e.target!.result as string]);
// 이미지 파일받기
  const OnChangePhoto = (e: ChangeEvent<HTMLInputElement>) => {
    if (onClickUserCheck(e)) return;

    const files = e.target.files;
    setCurrentRegion(e.target.id.split('-')[1]);
    if (!files) return;

    Array.from(files).forEach((file) => {
      const fileReader = new FileReader();
      fileReader.readAsDataURL(file);

      fileReader.onload = (e) => {
        if (typeof e.target?.result === 'string' && e.target.result) {
          if (activeTab === 'allTab') {
            setImgSrc((prev) => [...prev, e.target!.result as string]);
            // setIsRigionModal(true);
            openModal();
          } else if (activeTab === 'rigionTab') {
            setImgSrc((prev) => [...prev, e.target!.result as string]);
            setRegionCate(item);
          }
        }
      };
    });
  };

수정코드

  1. 파일 읽기와 압축을 병렬로 처리해 전체 시간을 단축한다.
  2. 상태 업데이트 최소화하여 불필요한 렌더링을 방지한다.
  // 이미지 파일받기
  const OnChangePhoto = async (e: ChangeEvent<HTMLInputElement>) => {
    if (onClickUserCheck(e)) return;

    const files = e.target.files;
    setCurrentRegion(e.target.id.split('-')[1]);
    if (!files) return;

    const imgSrcArray: string[] = await Promise.all(
      Array.from(files).map(async (file) => {
        // 파일 압축
        const compressedImage = await imageCompression(file, {
          maxSizeMB: 1, // 1MB
          maxWidthOrHeight: 1024, // 이미지의 최대 가로 또는 세로 길이를 1024로 제한
          useWebWorker: true // 압축 작업이 메인 스레드에 영향을 미치지 않도록 설정
        });
        
        // 압축파일 AVIF 형식으로 변환
        const avifImage = await convertImageToAvif(compressedImage);

        // 압축된 파일 읽기
        return new Promise<string>((resolve) => {
          const reader = new FileReader();
          reader.readAsDataURL(avifImage);
          reader.onload = () => {
            if (typeof reader.result === 'string') {
              resolve(reader.result);
            }
          };
        });
      })
    );

    // 상태 업데이트를 한 번에 처리
    setImgSrc((prev) => [...prev, ...imgSrcArray]);

    if (activeTab === 'allTab') {
      openModal();
    } else if (activeTab === 'rigionTab') {
      setRegionCate(item);
    }
  };
  • Promise.all 사용해 파일 압축 & 읽기 -> 병렬로 처리
    -> 병렬로 처리하면 하나끝날때까지 안기다리고 여러개를 동시에 처리가 가능해 시간단축이 가능하고, 코드가 좀 더 깔끔해진다.

  • 반복됬던 상태 업데이트를 한 번에 처리하여 불필요한 렌더링을 방지
    setImgSrc((prev) => [...prev, ...imgSrcArray]);

  • 전달받은 압축된이미지를 한 번 더 AVIF 형식으로 변환

AVIF 형식으로 변환하는 함수

// AVIF 형식으로 변환하는 함수
export const convertImageToAvif = (file: File): Promise<File> => {
  return new Promise((resolve) => {
    const img = new Image();
    img.src = URL.createObjectURL(file);
    img.onload = () => {
      const canvas = document.createElement('canvas');
      canvas.width = img.width;
      canvas.height = img.height;
      const ctx = canvas.getContext('2d');
      ctx?.drawImage(img, 0, 0);
      canvas.toBlob(
        (blob) => {
          if (blob) {
            const webpFile = new File([blob], `${file.name.split('.')[0]}.webp`, { type: 'image/webp' });
            resolve(webpFile);
          } else {
            console.warn('WebP 변환 실패, 원본 파일 사용');
            resolve(file);
          }
        },
        'image/webp',
        0.8
      );
    };
    img.onerror = () => {
      console.warn('이미지 로드 실패, 원본 파일 사용');
      resolve(file);
    };
  });
};

결과

실제로 이미지용량이 줄었다.
성능이 좀 올라갔다.
화면에 앨범제외하고도 pc에서만 나타나는 푸터로 큰레이아웃변경이 원인일 수 있다.
(BF)
BF

(AF)
AF

0개의 댓글