본문 바로가기

개발/JS|TS

자바스크립트로 이미지 클립보드에 복사하기

navigator.clipboard.write API를 사용해서 이미지를 복사하는 방법과, 과정에서 만났던 오류와 해결 방법 정리


1. 기본 이미지 복사하기

navigator.clipboard.write 메서드를 사용하면 이미지 같은 바이너리 데이터(Blob) 를 클립보드에 넣을 수 있다.
단, 주의할 점은 HTTPS 환경에서만 동작한다는 것.

const copyImage = async () => {
  try {
    const response = await fetch(props.resultImg);
    let blob = await response.blob();
    await navigator.clipboard.write([
      new ClipboardItem({
        [blob.type]: blob,
      }),
    ]);
    console.log('Image copied to clipboard');
  } catch (err) {
    console.error('Failed to copy image: ', err);
  }
};

 

2. MIME 타입 문제

이미지 복사 시 binary/octet-stream 타입으로 들어오는 경우가 있다.
이는 실제 타입을 알 수 없는 이진 데이터라는 뜻인데, 브라우저에서 제대로 처리되지 않을 수 있다.
따라서 blob.slice() 메서드를 사용해 지원되는 형식(예: image/png)으로 바꿔주는 것이 필요하다.

const copyImage = async () => {
  const response = await fetch(props.resultImg);
  let blob = await response.blob();
  blob = blob.slice(0, blob.size, 'image/png'); // MIME 타입 변경
  
  await navigator.clipboard.write([
    new ClipboardItem({
      'image/png': blob
    })
  ]);
};

참고: MIME 타입 문서 (MDN)

 

3. NotAllowedError: Document is not focused

여기서 또 다른 문제를 만났다.

Failed to copy image: NotAllowedError: 
Failed to execute 'write' on 'Clipboard': Document is not focused.

이 에러는 클립보드 API가 실행될 때 문서가 포커스되지 않은 상태라서 발생했다.
브라우저는 보안상의 이유로 사용자가 직접 상호작용하는 시점에서만 클립보드를 허용하기 때문이다.

 

해결 방법은 사용자 액션(버튼 클릭 등) 과 함께 동기적으로 실행되도록 보장하는 것이다.

const copyImage = async () => {
  try {
    const response = await fetch(props.resultImg);
    let blob = await response.blob();
    blob = blob.slice(0, blob.size, 'image/png');

    await navigator.clipboard.write([
      new ClipboardItem({ [blob.type]: blob }),
    ]);
    console.log('Image copied to clipboard');
  } catch (err) {
    console.error('Failed to copy image:', err);
  }
};

 

 

4. 더 안정적인 방법: Canvas 활용

가장 확실한 방법은 이미지를 Canvas에 그린 후 PNG blob으로 변환하는 방식이다.
이 방법은 포맷을 확실히 지정할 수 있고, 브라우저 지원도 넓다.

const copyImage = async () => {
  const img = await loadImage(props.resultImg);
  const canvas = document.createElement('canvas');
  canvas.width = img.width;
  canvas.height = img.height;
  const ctx = canvas.getContext('2d');
  ctx.drawImage(img, 0, 0);

  canvas.toBlob(async blob => {
    try {
      await navigator.clipboard.write([
        new ClipboardItem({ 'image/png': blob }),
      ]);
      openToast('이미지가 클립보드에 복사되었습니다.', 'success');
    } catch (err) {
      openToast('이미지 복사에 실패했습니다.', 'error');
    }
  }, 'image/png');
};

function loadImage(url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.crossOrigin = 'anonymous';
    img.onload = () => resolve(img);
    img.onerror = reject;
    img.src = url;
  });
}