hmk run dev

javascript로 이미지 파일을 보다 빠르게 만들어보자! (feat. canavs & base64) 본문

Front-End

javascript로 이미지 파일을 보다 빠르게 만들어보자! (feat. canavs & base64)

hmk run dev 2023. 11. 3. 23:39

 

 

쇼핑몰에서 상품을 데이터를 수집하는 크롬확장프로그램을 개발 중 문제가 생겼다.

 

  • 스크랩하는 페이지의 모든 이미지들을 File 형태로 서버로 보내는데 기존의 방식으로 소요시간이 오래걸려 UX 상으로 좋지 않았음
  • 크롬확장프로그램에서 백엔드 통신을 담당하는 background는 요청을 받는 content script와 n : 1 관계로 background 작업에 block이 걸리면 작업이 무한정 길어 질 수 있음
  • 백엔드에서 파일을 다운로드 받기엔 같은 ip에서 많은 다운로드를 수행하면 어뷰징으로 간주되 ip가 차단될 가능성이 있었음

 

 

 

 

결국 현재로썬 FE 단에서 파일을 모두 다운로드 받아야 하는 문제를 풀어야함

 

단, 단순 fetch로 다운로드 받는 것 보단 빠르게!


해결방법 후보들을 아래와 같이 선정해보았다!

  • webworker 이용 - 별도의 스레드를 생성해 content script에서 받은 각각 작업을 멀티 스레드 형식으로 처리하는 방향
    • 워커풀을 관리하고 개발난이도가 높아 현재 일정상 맞지 않는 구현방법
  • 이미지 다운로드 시점 변경 - 수집 시점엔 File 형태가 아닌 string 형태의 imageUrl들만 받고 추후 be 혹은 fe에서 파일 형식으로 s3에 업로드하는 방식
    • 위에도 말했듯이 어뷰징으로 간주되 ip 차단 가능성이 높음
  • fetch 대신 canvas와 base64를 이용한 방식 - 이미지를 다시 다운로드 받는 방식이 아닌 canvas에 이미지를 다시 그리고 base64으로 추출후 다시 File 형태로 가공

 

 

canvas를 이용해 imageUrl을 base64로 변환 (수집 장 이므로 Promise.all)

export async function encodeImageToBase64(imageSrc: string): Promise<string> {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.crossOrigin = "anonymous";
    img.src = imageSrc;
    img.onload = () => {
      const canvas = document.createElement("canvas");
      canvas.width = img.width;
      canvas.height = img.height;
      const ctx = canvas.getContext("2d");
      if (ctx) {
        ctx.drawImage(img, 0, 0);
        const base64Image = canvas.toDataURL();
        resolve(base64Image);
      } else {
        console.error("encodeImageToBase64 ctx error");
        reject(null);
      }
    };
    img.onerror = (error) => {
      console.error("encodeImageToBase64 imageload error :", error);
      reject(null);
    };
  });
}

 

 

이미지가 기본 수십 장이므로 병렬적으로 처리

 const imageDatas = await Promise.allSettled(
    [...new Set(imageUrls)].map(async (imageUrl) => ({
      base64Image: await encodeImageToBase64(imageUrl),
      imageSrc: imageUrl,
    }))
  ).then((results) => {
    return results
      .filter((result) => result.status === "fulfilled" && result.value)
      .map((result) => (result as PromiseFulfilledResult<Base64Image>).value);
  });

 

 

 

base64를 다시 이미지 파일로

/** base64를 파일로 만드는 함수 */
export async function base64ToFile(base64String, filename = "image") {
  if (!base64String) {
    return null;
  }
  const arr = base64String?.split(",");
  const mime = base64MimeType(base64String);
  const bstr = atob(arr[1]);
  let n = bstr.length;
  const u8arr = new Uint8Array(n);

  while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
  }

  return await new File([u8arr], `${filename}.${mime}`, { type: mime });
}


/** 위의 함수를 병렬처리 */
const imagesFilesByBase64Promises = collectedProductData.imageDatas.map(
    async (imageData) =>
      await base64ToFile(imageData.base64Image, imageData.imageSrc)
);




아래는 fetch 방식과 canvas + base64 방식의 속도차이!

 

Comments