프로젝트를 진행하면서 컴포넌트를 이미지로 저장하는 기능을 추가하고 싶어서 관련 자료를 찾기 시작했다. 처음엔 html2canvas와 jspdf로 기능을 구현하려고 구글링을 하다 dom-to-image, FileSaver 라이브러리를 알게 되었고 이 조합 코드가 좀 더 직관적이고 사용법이 좋은 것 같아서 결정하게 되었다.(그리고 좀 더 기능 구현 목적에 알맞다.)
dom-to-image와 FileSaver 설치
npm install dom-to-image
npm install file-saver --save
또는
yarn add dom-to-image
yarn add file-saver
기능 구현
각 컴포넌트에 다운로드 버튼이 있고 버튼을 클릭하면 해당 컴포넌트를 PNG 이미지로 저장하도록 구현한다.
import React from 'react';
import './card.css';
import domtoimage from 'dom-to-image';
import { saveAs } from 'file-saver';
const Card = () => {
// 컴포넌트 다운로드 함수
const onDownloadBtn = () => {
domtoimage
.toBlob(document.querySelector('.card'))
.then((blob) => {
saveAs(blob, 'card.png');
});
};
return (
<li className='card'>
<h1>카드 컴포넌트</h1>
<button className='downBtn' onClick={onDownloadBtn}>
다운로드 버튼
</button>
</li>
);
};
export default Card;
깃허브 페이지에 사용법이 아주 잘 나와있는데 PNG 이미지로 저장하는 코드는 다음과 같다.
const onDownloadBtn = () => {
// dom-to-image
domtoimage
.toBlob(document.querySelector('.card'))
.then((blob) => {
// FileSaver
saveAs(blob, 'card.png');
});
};
코드를 보면
- toBlob 메서드를 통해 이벤트가 일어날 타겟을 선택한 후
- saveAs 메서드로 해당 타겟을 card.png로 저장 시켜주는 것 같다.
이렇게 하면 동작은 하지만 여러 이슈가 존재한다..!
01. 리액트스럽지 않은 DOM 선택
JavaScript를 사용할 땐 getElementById, querySelector같은 DOM Selector 함수로 DOM을 선택하지만 리액트는 useRef라는 Hook 함수를 사용한다. 관련 내용
또 하나의 문제는 querySelector로 넣어주게 되면 각 컴포넌트마다 다운로드 기능을 적용할 수가 없다. (방법을 찾지 못했다.)
리액트 방식으로 노드를 선택할 경우 Ref의 .current 값으로 원하는 DOM을 선택하여 해결할 수 있다.
import React, { useRef } from 'react';
import './card.css';
import domtoimage from 'dom-to-image';
import { saveAs } from 'file-saver';
const Card = () => {
const cardRef = useRef();
const onDownloadBtn = () => {
const card = cardRef.current;
domtoimage
.toBlob(card)
.then((blob) => {
saveAs(blob, 'card.png');
});
};
return (
<li ref={cardRef} className='card'>
<h1>카드 컴포넌트</h1>
<button className='downBtn' onClick={onDownloadBtn}>
다운로드 버튼
</button>
</li>
);
};
export default Card;
02. 이미지에 다운로드 버튼 없애기
이제 다운로드 기능은 제대로 실행이 되지만 저장되는 이미지에 다운로드 버튼을 빼고 싶다. 그렇다면 toBlob 메서드에 힌트가 있지 않을까?
toBlob 메서드를 자세히 들여다보니 두번째 매개변수에 옵션을 넣을 수 있다. 함수도 넣을 수 있는걸 보니 이걸로 버튼 태그를 제외할 수 있지 않을까? 비슷한 사용법이 dom-to-image 깃허브 페이지에 있었다.
이걸 응용해서 다음과 같이 해결했다.
const cardRef = useRef();
const onDownloadBtn = () => {
const card = cardRef.current;
const filter = (card) => {
return card.tagName !== 'BUTTON';
};
domtoimage
.toBlob(card, { filter: filter })
.then((blob) => {
saveAs(blob, 'card.png');
});
};
03. margin 이슈
마지막으로 컴포넌트에 다양한 경우로 margin을 사용할 때가 있는데 이미지를 저장 시 해당 노드에 margin이 있을 경우 그 margin만큼 이미지가 잘려서 저장이 된다. 해결방법은 여러 방법이 있겠지만 요소를 한번더 감싸서 상위 요소에 margin을 적용하는 방법이 가장 간단하다. 관련 내용
'React.js' 카테고리의 다른 글
[React.js] JSX와 XSS(Cross Site Scripting) 공격 (0) | 2021.07.14 |
---|---|
[React.js] Netlify로 배포 후 새로고침 에러 | Page Not Found (0) | 2021.07.09 |
[React.js] 컴포넌트 저장 기능 구현하기(dom-to-image, FileSaver) (2) | 2021.07.07 |
[React.js] CRA 개인 프로젝트 Netlify로 배포하기 (0) | 2021.07.05 |
[React.js] 유튜브를 만들어 보자 (0) | 2021.04.30 |
[React.js] 유튜브 API로 인기 동영상 리스트 뽑기 (0) | 2021.04.08 |
저도 domtoimage로 리액트 차트컴포넌트를 다운로드하는 기능을 만들었는데, 생각보다 딜레이가 걸리더라구요. 그래서 html2canvas로 하는걸로 바꿨는데 혹시 비슷한 이슈는 없으셨나요?
답글
저는 사용자가 명함을 제작하면 해당 컴포넌트를 다운로드하는 기능이었어요. 딜레이가 생기거나 하는 이슈는 없었습니다!
html2canvas로 딜레이를 해결하신걸 보니 성능면에선 html2canvas가 더 좋나 보군요! :)