dom element를 이미지로 변환하기 (feat canvas API)
화면에 생성된 dom을 이미지로 다운로드 하기 위해서는 먼저 blob이나 file객체로 변환해야 한다.
그럼 어떻게 변환할 수 있을까?
1. 라이브러리 사용하기
이미 dom을 이미지로 변환하는 라이브러리가 존재한다.
html2canvas와 dom2image이다
그러나 html2canvas는 성능 문제가 있어 이미지로 변환할 때 화면이 멈추는 이슈가 있고
domtoimage는 화질 이슈가 있다.
그래서 직접 구현하도록 하겠다.
2. 직접구현
dom을 이미지로 구현하기 위해서는 dom을 svg테그안에 넣어주고 그이후 canvas를 통해 렌더링한 후 blob객체로 변환하여 다운로드를 하면 된다.
그럼 시작해 보자!
1. canvas생성하기
먼저 canvas를 생성해야한다.
const canvas = document.createElement('canvas');
그리고 dom의 width와 height를 받아와야 한다. cnavas는 width와 height를 직접 주입해야 하는데 그 값을 dom의 width와 height를 받아와 주입한다.
const { width, height } = element.getBoundingClientRect();
const context = canvas.getContext('2d');
canvas.width = width;
canvas.height = height;
canvas테그가 처음에 생성이 되면 안에 내용이 비어있다. 그래서 getContext를 통해 2d를 렌더링하나다는것을 알려주어야 한다.
또 canvas의 width와 height에 dom의 width와 height를 주입한다.
그다음 svg데그를 만든다
const svgData = `
<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">
<foreignObject width="100%" height="100%">
${new XMLSerializer().serializeToString(element)}
</foreignObject>
</svg>
`;
문자열을 통해 svg테그를 만드는데 svg 안에 foreignObject를 통해 dom을 svg로 만든다. dom은 new XMLSerializer().serializeToString()읕 통해 XML 형태로 만들고 그것을 문자열 형태로 변환하여 svg안에 집어넣는다.
const image = new Image();
image.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svgData);
image.onload = () => {
context?.drawImage(image, 0, 0);
resolve(canvas);
};
image테그를 생성하여 src에 방금 만든 svg 문자열을 집어너은 후 canvas의 context안에 drewImage를 통해 화면에 그려주어야 한다.
전체코드
const applyInlineStyles = (element: HTMLElement) => {
const computedStyle = window.getComputedStyle(element);
let inlineStyles = '';
Array.from(computedStyle).forEach(key => {
inlineStyles += `${key}: ${computedStyle.getPropertyValue(key)}; `;
});
element.setAttribute('style', inlineStyles); // 한 번에 적용
Array.from(element.children).forEach((child: Element) => applyInlineStyles(child as HTMLElement));
};
const domToCanvas = (element: HTMLElement): Promise<HTMLCanvasElement> => {
applyInlineStyles(element);
const canvasEl = new Promise<HTMLCanvasElement>(resolve => {
const { width, height } = element.getBoundingClientRect();
const canvas = document.createElement('canvas');
const context = canvas.getContext('');
canvas.width = width;
canvas.height = height;
const svgData = `
<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">
<foreignObject width="100%" height="100%">
${new XMLSerializer().serializeToString(element)}
</foreignObject>
</svg>
`;
const image = new Image();
image.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svgData);
image.onload = () => {
context?.drawImage(image, 0, 0);
resolve(canvas);
};
});
return canvasEl;
};
canvas를 생성을 한후에 다른작업이 이루어져야 되기 때문에 promise객체로 생성하였다.