๐ ๋ค์ด๊ฐ๋ฉฐ
React์์ ํ๋ฉด (DOM)๋ฅผ ์บก์ฒ, ์ ์ฅํ๋ ๊ธฐ๋ฅ์ ๋ํด์ ์์๋ณด๋ ค ํฉ๋๋ค.
์์ ํ ์ด ํ๋ก์ ํธ๋ฅผ ํ๋ฉด์ ์ฌ๋ฌ๊ฐ์ง ๋ฌธ์ ๋ฅผ ๋ง์ด ํ์๋๋ฐ, ๊ฒฐ๊ณผ์ ์ผ๋ก ์ฑ๊ณตํ ๋ฐฉ๋ฒ๋ค๋ก ํฌ์คํ ํ๊ฒ ๋ฉ๋๋ค.
์บก์ณ(์คํฌ๋ฆฐ์ท)์ ๋์์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค
๋ํ์ ์ผ๋ก html2canvas, dom-to-image, html-to-image ๋ฑ์ด ์์ต๋๋ค.
๊ธฐ๋ฅ์ ๊ตฌํํ๋ฉด์ html2canvas์ dom-to-image๋ฅผ ๋ชจ๋ ์ฌ์ฉํด ๋ดค์ต๋๋ค.
์ ๊ฐ ์ฌ์ฉํด ๋ณธ ๋ ๊ฐ์ง ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ํด์๋ ์ผ๋ฐ์ ์ผ๋ก ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ชจ๋ ์์ญ์ ์ ํํ๊ฒ ์บก์ฒํ๊ณ DOM ์์์ ์ ์ฉ๋ CSS ์คํ์ผ์ ์บก์ฒํ๋ ๋ฐ ํฐ ๋ฌธ์ ๋ ์์์ต๋๋ค.
๊ทธ๋ฌ๋ dom-to-image๊ฐ html2canvas๋ณด๋ค ๋ ๋น ๋ฅด๊ณ ์์ ์ ์ด๋ผ๊ณ ํ๋ ์ฌ์ฉ์๋ ์๋ค๊ณ ํฉ๋๋ค.
๋ํ, dom-to-image๋ ๋ ์์ ๋ฒ๋ค ํฌ๊ธฐ(์ฝ 14kb)๋ฅผ ๊ฐ๋ ๋ฐ๋ฉด html2canvas๋ ๋ ํฐ ๋ฒ๋ค ํฌ๊ธฐ(์ฝ 140kb)๋ฅผ ๊ฐ๋๋ค๋ ๊ฒ์ ๊ณ ๋ คํด ๋ณผ ์ ์์ต๋๋ค.
๋ฐ๋ผ์ ๋ฒ๋ค ํฌ๊ธฐ๊ฐ ์ค์ํ ๊ฒฝ์ฐ dom-to-image๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ ์ข์ ์ ์์ต๋๋ค.
๊ทธ๋ฌ๋ ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํฐ ์ฐจ์ด์ ์ค ํ๋๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ฒ๋ฆฌํ๋ ๊ธฐ๋ฅ์ ๋ฒ์์ ๋๋ค.
html2canvas๋ SVG ๋ฐ Canvas ์์๋ ์บก์ฒํ ์ ์์ผ๋ฏ๋ก ๋ ๋ค์ํ ์์์ ๋ํ ์บก์ฒ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค.
์ค์ ๋ก, ๊ฐ๋ฐํ๋ฉด์ DOM์์ ์์ img ํ์ผ์ด ํฌํจ๋์ด ์๋ ์ํฉ์์ dom-to-image๋ imgํ์ผ์ ์ธ์ํ์ง ๋ชปํ์ฌ imgํ์ผ์ด ๊นจ์ ธ์ ์บก์ฒ๋๋ ๊ฒฐ๊ณผ๋ฅผ ๋ณผ ์ ์์ด์ dom-to-image์์ html2canvas๋ก ๋ณ๊ฒฝํ์ต๋๋ค.
SVG ๋ฐ Canvas๋ฅผ ํฌํจํ ๋ค์ํ ์์์ ๋ํ ์บก์ฒ๊ฐ ํ์ํ ๊ฒฝ์ฐ html2canvas๋ฅผ ์ ํํ๋ ๊ฒ์ด ์ต์ ์ด๋ผ๊ณ ํ ์ ์์ต๋๋ค.
๊ทธ๊ฒ์ด ์๋๋ผ๋ฉด ๊ฐ๋ณ๊ณ ๋น ๋ฅธ dom-to-image๋ฅผ ์ ํํ๋ ๊ฒ์ด ์ต์ ์ธ ๊ฒ ๊ฐ์ต๋๋ค.
html2canvas์ dom-to-image๋ก ์ฝ๋๋ฅผ ์์ฑํ๋ ๋ถ๋ถ์ ๋งค์ฐ ์ ์ฌํ๊ณ ์ฝ๊ธฐ ๋๋ฌธ์ ๋ค๋ฅธ ๊ฑฑ์ ์ ํ์ง ์์๋ ๋ฉ๋๋ค.
๊ทธ๋๋ html2canvas๊ฐ ํ์ฌ๋ ์๋์ ์ผ๋ก ๋ง์ด ์ฌ์ฉ๋๊ณ ์์ต๋๋ค.
html2canvas
์ฌ์ฉ์ ๋ธ๋ผ์ฐ์ ์์ ์ง์ ์น ํ์ด์ง ๋๋ ๊ทธ ์ผ๋ถ์ "์คํฌ๋ฆฐ์ท"์ ์ฐ์ ์ ์๋ค. ์คํฌ๋ฆฐ์ท์ DOM์ ๊ธฐ๋ฐ์ผ๋ก ํ๋ค.
์บก์ฒํ ์์ญ (DOM)์ ์ง์ ํ๊ณ ํด๋น ์์ญ์ ๋ํ ์คํฌ๋ฆฐ์ท์ ์ฐ์ ์ ์๋๋ก ๋์์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค.
https://html2canvas.hertzen.com/
์ค์น ๋ฐฉ๋ฒ
npm install html2canvas
or
yarn add html2canvas
๊ธฐ๋ณธ์ ์ธ ์ฌ์ฉ ๋ฐฉ๋ฒ ์์
const saveAsImageHandler = () => {
const target = document.getElementById('content');
if (!target) {
return alert('๊ฒฐ๊ณผ ์ ์ฅ์ ์คํจํ์ต๋๋ค.');
}
html2canvas(target).then((canvas) => {
const link = document.createElement('a');
document.body.appendChild(link);
link.href = canvas.toDataURL('image/png');
link.download = 'result.png';
link.click();
document.body.removeChild(link);
});
};
html2canvas์ ๋ํ ์ฌ์ฉ ๋ฐฉ๋ฒ์ ์ฐพ์๋ณด๋ฉด ๋ณดํต ์ด๋ฐ ์์ผ๋ก ๋ง์ด ๊ธฐ๋ฅ์ ์์ฑํ๋ ์์ ๋ฅผ ๋ง์ด ์ฐพ์๋ณผ ์ ์์ต๋๋ค.
ํ์ง๋ง ์ด๋ ๊ฒ a tag๋ฅผ ํตํด์ ์ฝ๋๋ฅผ ์์ฑํ์ ๋ PC์์๋ ์ ํ ๋ฌธ์ ๊ฐ ์์์ผ๋, IPhone ๊ธฐ์ค Safari ๋ธ๋ผ์ฐ์ ์ธ์ ๋ชจ๋ ์๋ํ์ง ์์์ต๋๋ค.
Android์์๋ ํ ์คํธ๋ฅผ ํด๋ณผ ์๊ฐ ์์ด์ ๋น์ทํ ์ํฉ์ผ๋ก ๊ตฌ๊ธ๋ง์ ํด๋ณธ ๊ฒฐ๊ณผ Samsung Phone์์ ์๋ ๋ค์ด๋ก๋๋ฅผ ์ฐจ๋จํด ๋์ ๊ฒ์ผ๋ก ํ์ธ ๋์ต๋๋ค.
file-saver
์์์ ๋งํ ๋ฌธ์ ์ ์ ํด๊ฒฐํ๊ธฐ ์ํด ํด๋ผ์ด์ธํธ์์ ํ์ผ ์ ์ฅ์ ๋์์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ file-saver๊ฐ ์์ต๋๋ค.
file-saver๋ฅผ ์ฌ์ฉํ๋ฉด PC, Mobile ๊ฐ ํ๊ฒฝ๊ณผ ์ฌ๋งํ ๋ธ๋ผ์ฐ์ ๋ชจ๋ ํธํ์ด ๋๋๋ก ํด์ฃผ๋ ๊ธฐ๋ฅ์ ํด์ฃผ๊ณ ์๊ณ , ๊ฐ๋จํ ์ฝ๋๋ก ํ์ผ์ blob, file, url ํ์์ ๋ํ ํ์ผ๋ก ์ ์ฅํ๋ ๊ฒ์ ์ ์ฅํ ์ ์๋๋ก ๋์์ฃผ๊ณ ์์ต๋๋ค.
https://www.npmjs.com/package/file-saver
์ค์น ๋ฐฉ๋ฒ
npm install file-saver --save
or
yarn add file-saver
ํ์ ์คํฌ๋ฆฝํธ ํ๊ฒฝ์์๋ ํ์ ์ ์๋ ๋ฐ๋ก ์ค์น๋ฅผ ํด์ค์ผ ํฉ๋๋ค.
npm install @types/file-saver --save-dev
or
yarn add @types/file-saver -D
html2canvas + file-saver
๊ทธ๋ผ ์ด์ html2canvas์ file-saver๋ฅผ ํตํด์ ์ด๋ป๊ฒ ์ ์ฅ ๊ธฐ๋ฅ์ ๊ตฌํํ ์ง ์์๋ณด๊ฒ ์ต๋๋ค.
๋จผ์ ๊ฐ๋ฐ ํ๊ฒฝ์ React + TypeScript ํ๊ฒฝ์ ๋๋ค.
์์ ์ฝ๋
import "./styles.css";
import html2canvas from "html2canvas";
import saveAs from "file-saver";
import { useRef } from "react";
export default function App() {
const divRef = useRef<HTMLDivElement>(null);
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 (
<div className="App">
<div
ref={divRef}
style={{ backgroundColor: "lime", width: "300px", height: "200px" }}
>
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
<button onClick={handleDownload}>๋ค์ด๋ก๋</button>
</div>
);
}
html2canvas์ ๋ ๋์๋ค๋๋ ๊ธฐ๋ณธ ์์ ์๋ querySelector๋ก DOM์์๋ฅผ ์ก์์ฃผ๋๋ฐ,
React ํ๊ฒฝ์์๋ useRef DOM์์๋ฅผ ๋ค๋ฃจ๋ ๊ฒ์ด ์ผ๋ฐ์ ์ด๊ธฐ ๋๋ฌธ์ useRef๋ก ์บก์ฒ ์์ญ์ ์ง์ ํด ์ฃผ์์ต๋๋ค.
React์์ querySelector ๋์ useRef๋ก DOM์ ๋ค๋ค์ผ ํ๋ ์ด์
- React ์ ์ธ์ ๋ฐฉ์์ ์ ํฉ: useRef๋ React์ ์ ์ธ์ ๋ฐฉ์์ ๋ณด๋ค ์ ํฉํฉ๋๋ค. React์์๋ UI๋ฅผ ์์ฑํ ๋ ์ ์ธ์ ์ธ ์ฝ๋ ์คํ์ผ์ ์ฌ์ฉํ๋ฏ๋ก, useRef๋ฅผ ์ฌ์ฉํ๋ฉด ์ฝ๋์ ์ผ๊ด์ฑ์ด ์ ์ง๋๊ณ ๋ฆฌ์กํธ์ ์ธ ๊ฐ๋ฐ ์ต๊ด์ ์งํฌ ์ ์์ต๋๋ค.
- ์ง์ ์กฐ์ ์ต์ํ: React๋ ์ปดํฌ๋ํธ์ ์ํ์ ๋ ๋๋ง์ ๊ด๋ฆฌ๋ฅผ ๋์์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค. querySelector์ ๊ฐ์ ์์ JavaScript ์ฝ๋๋ฅผ ์ฌ์ฉํ๋ฉด, ์ง์ DOM ์กฐ์์ด ํ์ํ๋ฏ๋ก ๋ฆฌ์กํธ์ ์ฒ ํ์ ์ด๊ธ๋ฉ๋๋ค. useRef๋ DOM์ ๋ํ ์ฐธ์กฐ๋ฅผ ํจ๊ณผ์ ์ผ๋ก ๊ด๋ฆฌํ ์ ์์ด ์ํ ๋ณ๊ฒฝ ์ React๊ฐ ์ํํ๋ ๋ ๋๋ง ์ต์ ํ๋ฅผ ๋ฐฉํดํ์ง ์์ต๋๋ค.
- ๋ฆฌ๋ ๋๋ง์ ์์ : querySelector๋ ๋ ๋๋ง ์๋ง๋ค ํธ์ถ๋์ด์ผ ํ๋ฉฐ, ๊ฒฐ๊ณผ์ ์ผ๋ก ์ฌ๋ฌ ๋ฒ DOM์ ์ฐพ์์ผ ํ๋ ๋ฐ๋ฉด, useRef๋ ํ ๋ฒ๋ง DOM์ ์ฐพ์์ ๊ณ์ ์ฐธ์กฐํ๋ฏ๋ก ๋ฆฌ๋ ๋๋ง์ ์์ ํ๊ณ ์ฑ๋ฅ์ ๋ ์ด์ ์ ์ค๋๋ค.
- ์ปดํฌ๋ํธ์ ์๋ช ์ฃผ๊ธฐ ์ ๋ฐ์ ์ฌ์ฉ ๊ฐ๋ฅ: useRef๋ React์ ๋ค์ํ ์๋ช ์ฃผ๊ธฐ์ ๊ด๋ จ Hook๋ค, ์๋ฅผ ๋ค์ด useEffect, useState, useCallback ๋ฑ๊ณผ ํจ๊ป ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด๋ก ์ธํด ๋ ์ ์ฐํ๊ฒ ์ฝ๋๋ฅผ ๊ตฌ์ฑํ ์ ์์ผ๋ฉฐ, ์ปดํฌ๋ํธ์ ์๋ช ์ฃผ๊ธฐ์ ์ํ์ ๋ฐ๋ผ ์ฐธ์กฐ๊ฐ์ ์ ๋ฐ์ดํธํ๊ฑฐ๋ ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐ ๋์์ด ๋ฉ๋๋ค.
์ด๋ฌํ ์ด์ ๋ก ์ธํด, React์์ DOM ์์๋ฅผ ๋ค๋ฃฐ ๋ useRef๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์ต๋๋ค. ๋ฌผ๋ก ํ์ํ ๊ฒฝ์ฐ(ํ์ธ ์ ์ฉ ์ฉ๋ ๋ฑ) querySelector๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ด์ฉ ์ ์์ง๋ง, ๊ฐ๋ฅํ ํ React ์ปดํฌ๋ํธ์ ์ํ์ ์๋ช ์ฃผ๊ธฐ๋ฅผ ์งํฌ ์ ์๋ useRef๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
์บก์ฒํ ์์ญ์ ์ง์ ํด ์ฃผ๊ณ html2canvas์ ๋๊ฒจ์ฃผ๊ณ html2canvas๊ฐ ์บก์ฒํด ์ค ๊ฒ blob์ผ๋ก ๋ณํํ ๋ค์ file-saver๋ฅผ ํตํด์ ์ ์ฅ๋๋๋ก ํด์ฃผ์์ต๋๋ค.
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํด์ ๊ธฐ๋ฅ์ ๊ตฌํํ๋ ์ฝ๋ ์์ฒด๊ฐ ๋งค์ฐ ์ง๊ด์ ์ธ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
๐ ๋ง๋ฌด๋ฆฌ
html2canvas์ file-saver ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ํด์ ์์๋ดค์ต๋๋ค.
๊ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ ๋ํด ๋ค์ํ ์ต์ ๋ค๋ ์์ผ๋ ๊ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ํ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ์๋ฉด ๋ฉ๋๋ค.