본문 바로가기

Frontend Note

[FE] 트래픽 100만이 몰려도 서버비 0원?! "html2canvas"로 이미지 만들어보자긔 React, Html..

 

 

웹 서비스에서 '내 결과물 공유하기', '영수증 이미지 다운로드', '포토 카드 저장' 같은 기능을 구현해야 할 때가 있습니다. 보통은 서버에서 이미지를 생성(렌더링)해서 클라이언트에게 내려주는 방식을 생각하기 쉽습니다.

 

하지만 이 방식은 치명적인 단점이 있는데,

  • 서버 부하: 이미지 렌더링은 CPU와 메모리를 크게 소모함,,
  • 비용 증가: 트래픽이 몰린다면? 서버 비용과 데이터 전송 비용이 기하급수적으로 늘어남

"이 작업을 서버가 아니라 사용자 브라우저에서 직접 하게 만들면 어떨까?" 이때 떠올려볼수있는 라이브러리는 html2canvas!입니다. 사용자의 기기 자원을 활용하여 화면의 DOM 요소(HTML/CSS)를 캔버스(Canvas)로 캡처해 줍니다.

 

즉, 트래픽이 100만이 몰려도 이미지 생성에 드는 서버 비용은 단돈 0원!!!!!!!

설치 방법

프로젝트 환경에 맞춰 설치해 줍니다.

NPM / Yarn을 사용하는 환경 (React, Vue 등)

# 쉘 bash zsh etc...
npm install html2canvas
# 또는
yarn add html2canvas

 

일반 HTML 환경 (CDN 방식)

<script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>

기본 사용법: 화면 캡처하기

가장 기본적인 원리는

  1. 캡처하고 싶은 HTML 요소를 선택
  2. html2canvas 함수에 해당 요소를 넘겨주기
  3. 결과물로 나온 canvas 객체를 다루기~

HTML 예시

<!-- 캡처할 영역 (id="capture-area" 지정) -->
<div id="capture-area" style="padding: 20px; background-color: #f0f0f0; border-radius: 10px;">
  <h2>나의 멋진 포토 카드</h2>
  <p>이 영역이 그대로 이미지로 변환됩니다!</p>
</div>

<!-- 실행 버튼 -->
<button id="capture-btn">이미지 캡처하기</button>

<!-- 결과물을 보여줄 빈 공간 -->
<div id="result-area"></div>

 

JavaScript 

// NPM 환경이라면 상단에 import 필요: import html2canvas from 'html2canvas';

const captureBtn = document.getElementById('capture-btn');
const captureArea = document.getElementById('capture-area');
const resultArea = document.getElementById('result-area');

captureBtn.addEventListener('click', () => {
  // html2canvas는 Promise를 반환합니다.
  html2canvas(captureArea).then((canvas) => {
    // 캡처된 canvas를 결과 영역에 추가하여 화면에 보여줌
    resultArea.appendChild(canvas);
  });
});

React에서 사용한다면, 돔말구 useRef를 활용하기

React는 가상 DOM을 사용하기 때문에, 특정 요소를 직접 가리키기 위해서는 useRef를 사용해야 합니다.

가장 표준적인 구현 패턴을 소개합니다.

/// 카드 캡쳐 js

import React, { useRef } from 'react';
import html2canvas from 'html2canvas';

const CardCapture = () => {
  // 1. 캡처하고 싶은 영역을 지정할 ref 생성
  const printRef = useRef(null);

  const handleDownload = async () => {
    const element = printRef.current;
    if (!element) return;

    try {
      // 2. html2canvas로 캔버스 생성 (옵션 추가 가능)
      const canvas = await html2canvas(element, {
        scale: 2, // 화질을 2배로 높임
        useCORS: true, // 외부 이미지 로드 허용
      });

      // 3. 이미지 데이터 URL로 변환
      const data = canvas.toDataURL('image/png');
      const link = document.createElement('a');
      
      link.href = data;
      link.download = 'my-awesome-card.png';
      link.click();
    } catch (error) {
      console.error('이미지 생성 실패:', error);
    }
  };

  return (
    <div>
      {/* 캡처하고 싶은 영역에 ref 연결 */}
      <div ref={printRef} style={{ padding: '20px', border: '1px solid #ccc', backgroundColor: '#fff' }}>
        <h1>나만의 포토카드</h1>
        <p>이 영역의 내용이 이미지로 저장됩니다.</p>
        <img src="https://example.com/profile.jpg" alt="프로필" style={{ width: '100px' }} />
      </div>

      <button onClick={handleDownload} style={{ marginTop: '20px' }}>
        이미지로 저장하기
      </button>
    </div>
  );
};

export default CardCapture;

 

캡처 후 이미지 파일로 다운로드하기

보통 화면에 캔버스를 띄우는 것보다, 사용자 기기에 이미지 파일(.png)로 저장해 주는 것이 최종 목적입니다.

이를 위해서는 캔버스를 데이터 URL로 변환하고, 보이지 않는 <a> 태그를 만들어 클릭 이벤트를 발생시켜야 합니다.

 

captureBtn.addEventListener('click', async () => {
  try {
    // 1. 캡처할 영역을 캔버스로 변환
    const canvas = await html2canvas(captureArea);
    
    // 2. 캔버스를 Base64 형태의 이미지 데이터(URL)로 변환
    const image = canvas.toDataURL('image/png');
    
    // 3. 다운로드를 위한 가상의 <a> 태그 생성
    const link = document.createElement('a');
    link.href = image;
    link.download = 'my-card-image.png'; // 다운로드될 파일 이름 지정
    
    // 4. 강제로 클릭 이벤트 발생시켜 다운로드 실행
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    
  } catch (error) {
    console.error('이미지 캡처 중 오류 발생:', error);
  }
});

 

위에는 다운로드 기능 코드입니다

실무에서 꼭 알아야 할 꿀팁과 주의사항

막상 적용해 보면 깨지거나 흐리게 나오는 등 예상치 못한 문제를 만날 수 있습니다.

 

1. "이미지가 너무 흐리게 나와요" (고화질 캡처 방법)

모바일 기기나 레티나 디스플레이에서는 기본 캡처 시 이미지가 흐려질 수 있습니다.

이때 scale 옵션을 주어 해상도를 높여야 합니다.

// js 코드
html2canvas(captureArea, {
  scale: 2, // 기본값 1에서 2~3으로 올리면 화질이 좋아집니다. (단, 용량과 시간 증가)
  // window.devicePixelRatio 를 사용하면 기기 환경에 맞게 자동으로 최적화됨
  // scale: window.devicePixelRatio 
}).then(canvas => { ... });

2. 외부 이미지(프로필 사진 등)가 엑스박스로 떠요 (CORS 문제)

내 도메인이 아닌 외부 서버의 이미지(예: AWS S3, 카카오 프로필 등)가 캡처 영역에 포함되어 있으면, 브라우저 보안 정책(CORS) 때문에 캔버스가 오염(Tainted) 처리되어 캡처가 실패합니다.

이 경우 외부 이미지 서버에서 CORS 허용 헤더를 열어주어야 하며, html2canvas 옵션에 useCORS: true를 추가해야 합니다.

// js 코드

html2canvas(captureArea, {
  useCORS: true, // 외부 이미지 로드 허용
  allowTaint: false // 교차 출처 이미지 오염 방지
}).then(...)

3. 지원하지 않는 CSS 속성이 있어요

html2canvas는 브라우저의 렌더링 엔진을 100% 똑같이 구현한 것이 아니어서 최신 CSS 속성(복잡한 box-shadow, filter, grid의 일부 속성 등)은 무시되거나 깨질 수 있습니다.

  • 해결책: 캡처해야 하는 영역은 가급적 안전한 CSS (Flexbox, 기본 속성 등) 위주로 스타일링하는 것이 좋습니다.

4. 이미지가 다 로드되기 전에 캡처돼버려요

영역 안에 큰 이미지가 렌더링 중인데 캡처 버튼을 누르면 이미지가 빠진 채로 캡처됩니다.

웹 폰트가 늦게 로딩되는 경우에도 폰트가 깨집니다.

  • 해결책: 모든 이미지와 웹 폰트가 완전히 로드된 것을 보장한 후에 html2canvas를 실행해야 합니다.

 

 

html2canvas는 프론트엔드 레벨에서 리소스를 아끼면서도 사용자에게 멋진 경험(포토카드, 결과 저장 등)을 제공할 수 있는 방법이죠.

CORS 이슈와 지원하지 않는 CSS 한계만 명확히 인지하고 설계한다면, 서버 개발자의 개입 없이, 비용 0원으로 수백만 트래픽의 이미지 생성 기능을 처리 할 수 있슴다.