<dev.log/>;

컴포넌트를 이미지 파일로 저장해보자!

개요

이전 포스팅과 동일한 프로젝트에서 필요한 기능이다. 사용자가 문장 카드를 만든 후에 이 카드를 이미지 형태로 다운로드하는 기능을 구현해야 했다. 기획 단계에서는 어떻게든 구현할 수 있겠지라는 마인드로 기능을 생각했는데, 구현 단계에서 라이브러리 덕에 성공적으로 구현을 끝마칠 수 있었다. 지금부터 어떻게 구현했는지 적어보려고 한다.

사용한 라이브러리

라이브러리에 대한 설정 방법이 다양하니 공식 문서를 읽어보길 추천한다.

html-to-image

npm: html-to-image

  • 우선 컴포넌트를 이미지로 변경해주는 라이브러리이다. 다른 비슷한 유형의 라이브러리들이 있지만 모종의 이유로 이 라이브러리를 선택했다. 이유는 아래에 따로 작성했다.

file-saver

npm: file-saver

  • 파일을 저장할때 사용하는 라이브러리이다. 다른 포맷의 파일도 저장이 가능한듯 한데, 나는 이미지 저장을 위해 사용했다. 지원되는 브라우저도 명시되어 있으니 참고하면 된다.

구현 과정

  1. 우선 이미지로 변환할 컴포넌트를 선택해야 한다. useRef를 사용하여 dom 요소를 선택해준다.

    const Detail = () => {
      const cardRef = useRef<HTMLDivElement>(null);
    
      return (
        <Container>
          <BasicCard ref={cardRef}>
            <IconQuote />
            <BasicCardContent />
            <BasicCardBookInfo />
          </BasicCard>
        </Container>
      );
    };
    
    export default Detail;
    
  2. 이미지 다운로드를 할 수 있는 버튼을 만들고 함수를 만들어준다. 나는 PNG로 만들기 위해 toPNG를 사용했다. 추가적으로 옵션을 설정해줄 수 있는데, 고화질 이미지를 만들기 위해 canvasWidthcanvasHeight 설정을 사용했다. 설정한 픽셀의 2배 해상도의 이미지를 얻을 수 있다.

    import { toPng } from 'html-to-image';
    
    const Detail = () => {
      const cardRef = useRef<HTMLDivElement>(null);
    
      const handleClickDownload = () => {
        const card = cardRef.current;
        if (card) {
          toPng(card, {
            canvasWidth: 1080,
            canvasHeight: data.imageSize === 'aspect-square' ? 1080 : 1920,
          });
        }
      };
    
      return (
        <Container>
          <BasicCard ref={cardRef}>
            <IconQuote />
            <BasicCardContent />
            <BasicCardBookInfo />
          </BasicCard>
    
          <button onClick={handleClickDownload}>
            <IconDownload />
            <span>이미지 다운로드</span>
          </button>
        </Container>
      );
    };
    
    export default Detail;
    
  3. file saver의 saveAs를 사용하여 이미지를 저장한다.

    import { toPng } from 'html-to-image';
    import { saveAs } from 'file-saver';
    
    const Detail = () => {
      const cardRef = useRef<HTMLDivElement>(null);
    
      const handleClickDownload = () => {
        const card = cardRef.current;
        if (card) {
          toPng(card, {
            canvasWidth: 1080,
            canvasHeight: data.imageSize === 'aspect-square' ? 1080 : 1920,
          }).then((image) => {
            saveAs(image, 'card.png');
          });
        }
      };
    
      return (
        <Container>
          <BasicCard ref={cardRef}>
            <IconQuote />
            <BasicCardContent />
            <BasicCardBookInfo />
          </BasicCard>
    
          <button onClick={handleClickDownload}>
            <IconDownload />
            <span>이미지 다운로드</span>
          </button>
        </Container>
      );
    };
    
    export default Detail;
    

html-to-image를 사용한 이유

기존에는 dom-to-image를 사용해 구현을 했는데 화질 설정을 하지 않으면 사용자가 설정한 화면 크기에 맞춰서 다운로드가 되기 때문에 언제든 고화질의 이미지를 다운로드 받게 해야 했다.

dom-to-image에서의 문제는 ref를 설정한 컴포넌트만 픽셀이 변경되고 하위의 자식 컴포넌트들은 사이즈가 변경되지 않았다. 이에 비해 html-to-imagecanvasWidth & canvasHeight가 있어서 내부의 모든 dom 요소들의 사이즈를 바꿔줄 수 있었다.

적용화면

  • 위에서 설정한 이름으로 파일이 다운로드가 되고 아래의 이미지를 얻을 수 있다.