본문 바로가기

개발/프론트엔드

HTML을 이미지로 저장하기 - 트러블 슈팅 경험 공유 🔥

반응형

안녕하세요, 오늘은 제가 최근에 개발한 사이드 프로젝트 '퀵썸네일'을 만들면서 겪은 문제와 해결 방법에 대해 이야기해보려고 합니다. 퀵썸네일은 '몇 번의 간단한 클릭만으로 예쁜 썸네일을 빠르고 쉽게 만들 수 있는 프로젝트'인데요, HTML과 스타일 요소를 사용하고 있어서 HTML을 이미지로 저장하는 것이 핵심 기능이었습니다.

트러블슈팅 #1: 사파리 이미지 저장 문제

HTML을 이미지로 변환할 때 가장 유명한 라이브러리는 'html-to-image'입니다. 하지만 이 라이브러리를 사용하면 저장 시에 사파리 브라우저에서 이미지가 깨지는 문제가 있었습니다. 배경 이미지가 사라지거나, 폰트가 적용되지 않는 등의 문제가 존재했습니다.

좌: 퀵썸네일에서 만든 이미지 / 우: 저장된 이미지

사파리나 파이어폭스 브라우저에서는 해당 라이브러리를 활용해서 이미지를 저장할 때 외부 리소스를 새롭게 불러오게 되는데, 이미지나 폰트 등의 리소스 로딩 시간이 길어지면서 해당 부분이 없어지고 저장되는 문제였습니다.

이 문제는 'modern-screenshot' 라이브러리를 사용하면 해결할 수 있습니다. 해당 라이브러리는 'html-to-image' 라이브러리를 포크 해서 여러 문제를 해결 및 편의 기능을 추가한 라이브러리입니다.

다음은 modern-screenshot 라이브러리를 사용해 HTML을 이미지로 변환하여 저장하는 기본적인 코드입니다:

import { domToPng } from 'modern-screenshot'

async function downloadImage() {
  const node = document.querySelector('#app')

  if (!node) {
    window.alert("해당 요소가 없습니다.")
    return
  }

  const dataUrl = await domToPng(node)

  const link = document.createElement('a')
  link.download = 'screenshot.png'
  link.href = dataUrl
  link.click()
  link.remove()
}

트러블슈팅 #1-1: 이미지가 여전히 깨질 때

위의 방법을 적용해도 이미지가 깨지는 경우가 발생할 수 있습니다. (특히 모바일 사파리) 이 경우에 해결하는 방법은 간단합니다. 이상해보일 수는 있지만, 해당 코드를 두 번 실행하니 해결되었습니다. 퀵썸네일에서는 다음과 같은 코드를 사용합니다.

  // ...

  const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);

  let dataUrl = "";
  // 사파리 브라우저는 첫 시도에 이미지가 깨지는 경우 존재
  const trial = isSafari ? 2 : 1;

  for (let i = 0; i < trial; i++) {
    dataUrl = await domToPng(node)
  }

  // ...

트러블슈팅 #2: 모바일 사파리 / 카카오 웹브라우저 이미지 저장 문제

모바일 사파리에서는 위의 방식으로 이미지를 다운로드하면 이미지가 사진앱에 저장되지 않고 파일로 저장됩니다. 사진 앱에 저장하길 원하다면 다운로드한 파일을 찾아서 별도로 사진앱에 저장해주어야 합니다.

위와 같은 방식으로 저장하면 이미지를 사진앱에서 찾을 수 없음

사파리 브라우저뿐만이 아니라 카카오톡 브라우저에서도 이미지가 저장되지 않는 문제가 있습니다.

이 문제의 경우, 이미지를 바로 저장하지 않고 이미지를 페이지에 렌더링 한 후 사용자에게 길게 눌러서 저장하도록 안내하는 방법을 사용했습니다. 해당 과정은 아래와 같은 코드로 이루어집니다.

async function downloadImage() {
  const node = document.querySelector('#app')

  if (!node) {
    window.alert("해당 요소가 없습니다.")
    return
  }

  const dataUrl = await domToPng(node)

  const isMobileSafari = /iPad|iPhone|iPod/.test(navigator.userAgent)
  const isKakaoBrowser = /KAKAOTALK/.test(navigator.userAgent)

  if (isMobileSafari || isKakaoBrowser) {
    // 이미지를 저장하지 않고 모달에 렌더링
    openImageModal(dataUrl)
  } else {
    // 일반적인 다운로드 로직
    // ...
  }
}

이렇게 하면 모바일 사파리와 카카오 브라우저에서도 사용자가 이미지를 저장할 수 있습니다.

이상으로 HTML을 이미지로 저장할 때 겪었던 문제들과 그 해결 방법에 대해 알아보았습니다. 여러분의 프로젝트에도 도움이 되었기를 바랍니다!

반응형