AI 기반 톡방 대화 분석 서비스 '대화 평화상' 프로젝트를 진행하면서 구현한 내용입니다.
프로젝트 진행 중 컴포넌트를 캡쳐해서 다운받는 기능을 구현해야했다.
PM분이 요청하신 내용은 아래와 같았는데, 바로 상장 다운로드 기능이다.
현재 프로젝트에서는 대화 분석 결과 페이지의 대화 평화상(or 욕쟁이상)을 리워드하는 상장 컴포넌트가 존재하는데 이를 웹에서 사진으로 저장할 수 있게 만들어야한다. 또한 해커톤에서 진행한 프로젝트로 짧은 시간안에, 최대한 빨리 구현해야만 한다.
먼저 DOM 캡쳐가 가능한 지 알아보면서 화면 캡쳐 라이브러리에 대해 알게 되었다. 대표적으로 html2canvas
, dom-to-image
, html-to-image
등이 있었다.
널리 사용되고 검증된 라이브러리. 오랜 기간 동안 많은 프로젝트에서 사용되어 왔다. proxy기능을 지원하며 다양한 웹 브라우저에서 안정적으로 작동한다. 복잡한 CSS 스타일이나 HTML 구조는 정확하게 변환되지 않을 수 있다고 한다. 또한 크고 복잡한 페이지의 경우 변환 과정에서 성능 저하가 발생할 수 있다.
html2canvas와 유사하지만 성능적으로 우수하고 속도가 빠르다고 한다.
최신 웹 기술과 CSS 속성을 더 잘 지원하여, 더 정확한 이미지 변환을 제공한다고 한다. 또한 다양한 이미지 포맷으로의 변환을 지원하며, 품질 조절 등의 추가 옵션이 가능하다.하지만 상대적으로 새로운 라이브러리이기 때문에, 레퍼런스가 다양하지 않았다.
즉, 크게 성능을 고려하지 않아도 되는 범위이기 때문에 안정성과 빠른 구현에 초점을 두고 선택하였다.
추가적으로 file-saver 라이브러리를 사용했다.
사용할 라이브러리들을 설치해준다.
npm install html2canvas
npm install file-saver --save
npm install @types/file-saver --save-dev //TypeScript 환경시 타입 추가
먼저 캡쳐할 컴포넌트 즉 DOM을 선택해야한다.
React에서는 DOM을 다루는 방법은 useRef를 권장하고 있다. 따라서 먼저 빈 값의 useRef로 선택할 DOM을 선언해주었다.
const divRef = useRef<HTMLDivElement>(null);
캡쳐할 DOM이 존재한다면 html2canvas
에 넘겨준다. 그 후 이 변환된 canvas
를 blob
으로 변환한 뒤 file-saver
를 통해 저장가능하도록 해준다.
html2canvas(캡쳐할 요소, options)
: scale 옵션을 통해 해상도를 높일 수 있다.toBlob(callback)
: canvas에 포함된 이미지를 나타내는 Blob 객체를 생성한다.saveAs(blob, filename)
: file-saver의 메소드로 Blob 객체와 이름을 전달하면 파일을 저장할 수 있게 해준다.const canvas = await html2canvas(div, { scale: 2 });
canvas.toBlob((blob) => {
if (blob !== null) {
saveAs(blob, "result.png");
}
import html2canvas from "html2canvas";
import saveAs from "file-saver";
export default function Reward() {
// 1. 캡쳐할 영역 DOM 조작을 위한 useRef 선언
const divRef = useRef<HTMLDivElement>(null);
// 2. 다운로드 버튼 클릭 시 실행될 함수
const handleDownload = async () => {
if (!divRef.current) return;
try {
const div = divRef.current;
const canvas = await html2canvas(div, { scale: 2 });
canvas.toBlob((blob) => {
if (blob !== null) {
saveAs(blob, "result.png");
}
});
} catch (error) {
console.error("Error converting div to image:", error);
}
};
return (
// 스타일 코드는 생략
<section>
<div ref={divRef}>
<Card/>
</div>
<Button onClick={handleDownload}>다운받기</Button>
</section>
);
}
그런데 문제가 발생했다. 웹 화면의 그대로가 아닌 카드 컴포넌트 특정 부분이 계속해서 이상하게 저장되었다.
Card 컴포넌트 CSS 관련 문제라고 생각했다.아니면 동적으로 받아오는 데이터 때문인가. 논리 연산자 때문인가...🥺
프론트엔드 팀원분과 함께 코드를 분석해보면서 무시되는 부분에 대한 일관성이 없어서 좀 헤맸었다.
그런데...언제나 등잔 밑이 어둡다. 그저 저 컴포지션 gif 때문이었다.
현재 축하를 나타내는 컴포지션은 디자인 한계로 투명 배경이 아니라 카드 배경색과 동일한 gif 파일로 구성되어있다. 그래서 CSS로 구현할 때 z-index
로 뒤에 배치해두었는데 이게 파일 추출 시 무시가 된 것 같았다. 검색해보니 복잡한 CSS 스타일에는 종종 오류가 날 수 있다고 한다. 아마 position
속성과 z-index
속성을 복잡하게 구현해서 html2canvas 라이브러리가 인식하지 못하는 것 같았다.
그럼 컴포지션을 삭제하거나 CSS를 인식가능하게 고쳐야했다. 하지만 시간관계상 CSS를 다시 다 고친다는 것은 말이 되지않았고 다른 방법에 대해 고민하던 중 html2canvas에서 제공하는 속성을 찾게 되었다.
특정 요소를 추출 렌더링 시 제외하려면 data-html2canvas-ignore
속성을 추가하면 된다고 한다.
바로 컴포지션 img 태그에 해당 속성을 추가하고 테스트 해봤다.
// Card 컴포넌트 속 img 태그 부분
<img
src={compositon_one}
alt="compostion_1"
data-html2canvas-ignore="true"
/>
테스트 후 바로 결과를 공유했고 PM님과 팀원들에게 OK 컨펌을 받았다!
웹, 모바일에서 모두 정상적으로 다운로드 되는 것을 확인할 수 있었다.
정말 좋은기능이네요!