[트러블슈팅] 모바일웹 환경 이미지 프레임 캡쳐 이슈

liinyeye·2024년 11월 24일
0

Project

목록 보기
44/44

🔎 문제 상황

모바일 웹 환경에서 이미지 프레임 캡쳐가 제대로 되지 않는 이슈 발생

  • 이미지 비율이 화면과 다름
  • 간헐적으로 카드 안에 있는 로고 파일 사라짐

💡 문제 분석

  • 이미지 사이즈 & 비율 확인
    카드 이미지는 360x480px이고, 다운로드 된 이미지는 1240x1920px이리 비율 상으로는 동일한 상태

  • 이미지 css 속성 확인
    개발자모드를 켜고 확인을 해보니 화면 속 이미지는 object-fit : cover 인데, 다운로드 된 이미지는 아무 속성이 적용되지 않은 상태로 저장된다는 것을 파악

초기에 html2canvas를 사용하고 있었는데, 해당 라이브러리가 object-fit 속성을 지원하지 않아 생긴 문제라는 것을 파악하고 이미지 프레임 캡쳐에 주로 사용되는 라이브러리를 찾아 비교 분석해봤다.

📌 라이브러리별 특징과 그에 따른 의사결정

https://npm-compare.com/dom-to-image,html-to-image,html2canvas

html2canvas

  • 지원하지 않는 css 속성이 많은 편
    → object-fit css 속성 지원하지 않아 이미지 비율 기대와 다르게 저장됨
  • 이미지 화질 변경 용이 (scale 속성)

html-to-image

dom-to-image

  • toPng로 저장 시 이미지 화질이 많이 깨지는 경향이 있음
    (quality 옵션 최대인 1.0로 설정했음에도 불구하고)
  • toSvg 로 저장해서 확인하니 화질 저하 현상이 없어 toSvg → toPng 저장 시도했으나 화질에 큰 차이 없고, 코드가 과하게 복잡해짐

🚀 해결 과정

옵션 1
: html2canvas 사용하되 object-fit : cover 을 대신한 css 속성 부여

옵션 2
: dom-to-image 사용해 toSvg → toPng로 변환

결론 : 옵션 1 선택

사용하기 더 간단하고 라이브러리 생태계가 큰 html2canvas 사용하되, css 속성 다른 방식으로 해결

  • file-saver와 함께 사용해 브라우저 별 다운로드 문제 해결
  • 캡쳐할 이미지는 항상 세로 > 가로 사이즈 이므로, width는 부모 컨테이너에 100%로 맞춰 채우고 height는 auto로 두어 원본 이미지 비율 보장
  • 카드에 포함되는 브랜드 로고 이미지는 Image → SVG 컴포넌트로 직접 사용해 이미지 품질 높임

코드 참고

  // src > hooks > useImageShareDownload.ts
  
  import { saveAs } from 'file-saver';
  import html2canvas from 'html2canvas';
  
  const HTML2CANVAS_OPTIONS = {
  scale: 4, // 이미지 품질 조절
  backgroundColor: null, // "transparent" 속성과 동일
  logging: false, // 성능을 위해 로깅 비활성화
  useCORS: true, // 외부 이미지 리소스 처리를 위해 필요할 수 있음
  allowTaint: false, // 보안을 위해 false로 유지
};

  // 이미지 생성
  const generateImage = useCallback(async (element: HTMLDivElement): Promise<Blob> => {
    try {
    // 먼저 캔버스로 이미지 생성
      const canvas = await html2canvas(element, HTML2CANVAS_OPTIONS);

		// file-saver로 저장하기 위해 Blob으로 변환
      return new Promise<Blob>((resolve, reject) => {
        canvas.toBlob(
          (blob) => {
            if (blob) {
              resolve(blob);
            } else {
              reject(new Error('Failed to create blob from canvas'));
            }
          },
          'image/png',
          1.0,
        );
      });
    } catch (error) {
      console.error('Error in generating image:', error);
      throw error;
    }
  }, []);

// file-saver로 이미지 다운로드
const downloadImage = async (dataUrl: string, fileName: string) => {
        try {
          const response = await fetch(dataUrl);
          const blob = await response.blob();
          saveAs(blob, fileName);
          showToast('Image downloaded successfully', 'success');
          return true;
        } catch (error) {
          console.error('Download failed:', error);
          return false;
        }
      };
// src > components > molecules > ShareCard.tsx

const ShareCard = ({ backgroundImage, brand, productName, containerRef }: ShareCardProps) => {
  return (
    <div className="shadow-share-card rounded-[20px]"> // 이미지 저장 시 그림자 제외시키기 위해 분리
      <div
        ref={containerRef}
        className="share-card relative w-[18.125rem] h-[26.25rem] h-min-w-72 min-h-[26.25rem] flex flex-col items-center rounded-[1.25rem] overflow-hidden"
        style={{ backgroundColor: 'transparent' }} // tailwind로 동일 속성 적용 안 되기 때문에 인라인 적용
      >
        <div className="relative w-full h-full flex justify-center items-center overflow-hidden">
          {backgroundImage ? (
            <Image
              src={backgroundImage}
              alt="Virtual try-on background"
              width={290}
              height={420}
              style={{
                maxWidth: '100%', // 부모 컨테이너 사이즈에 
                height: 'auto',
              }}
              priority
              quality={100}
              loading="eager" // 페이지 로드 시점에 이미지 즉시 로드
              crossOrigin="anonymous" // iOS Safari 환경 로드 문제 방지
            />
          ) : null}
        </div>
        <div className="absolute inset-0 z-10 flex flex-col items-center w-full h-full mt-[2.494rem] text-center">
          {brand ? (
            <div className="relative w-[7.5rem] h-10 aspect-auto max-w-[7.5rem] mb-2 overflow-hidden">
              <BrandLogo brand={brand} width="100%" height="100%" className="w-full h-full" />
            </div>
          ) : null}
          {productName ? <span className="text-body3-paragraph">{productName}</span> : null}
        </div>
      </div>
    </div>
  );
};

추가적인 고민

이미지 품질 높이기

이미지 품질을 조절하는 scale 옵션을 높이더라도, 어느 정도 이상부터는 다운로드 되는 이미지 품질에 직접적인 영향을 미치지 않고 특정 품질에 머물러 있음.

더 좋은 품질로 올리고 싶은데, 어떤 방법을 사용할 수 있을지?

  • 캡쳐하는 캔버스 사이즈를 크게 만든다?
  • 최대로 반영되는 scale값 찾기 → 너무 높다면 성능상 이슈 고려 필요

다른 라이브러리

  • modern-screenshot 도 있으나 위의 라이브러리로 해결되어 이번에는 사용해보지 않았지만, 이후에 같은 기능을 구현한다면 사용해볼 예정

참고 자료

profile
웹 프론트엔드 UXUI

0개의 댓글

관련 채용 정보