안녕하세요. Unno FE 노수민입니다.🌸 웹 개발에서 브라우저 호환성 문제는 늘 도전적인 과제입니다. 최신 웹 API를 사용하고 싶지만 모든 브라우저에서 동작하지 않는 경우, 개발자는 대안을 모색해야 합니다. 이번에는 색상 스포이드 기능을 구현하면서 겪었던 기술적 고민과 해결 과정을 공유하고자 합니다.

37368848-이-스포이드의-그림입니다.jpg

EyeDropper API: 간결함과 제한된 호환성 사이

프로젝트에서 사용자가 화면 어디서든 색상을 추출할 수 있는 스포이드 기능이 필요했습니다. 가장 먼저 떠올린 방법은 최신 웹 기술인 EyeDropper API였습니다.

const handlePickColor = async () => {
  if (!window.EyeDropper) {
    sweetAlertUtil.error(
      '현재 브라우저에서는 스포이드 기능을 사용할 수 없어요.',
      '원활한 사용을 위해 크롬, 엣지, 오페라 브라우저를 추천드려요.'
    );
    return;
  }

  try {
    const eyeDropper = new window.EyeDropper();
    const result = await eyeDropper.open();
    onColorChange(result.sRGBHex);
  } catch (e) {
    console.error('스포이드 취소됨 or 에러 발생:', e);
  }
};

이 구현은 간결하고 효과적이었으나, 큰 제약이 있었습니다. 2023년 기준으로 EyeDropper API는 Chrome, Edge, Opera 등 Chromium 기반 브라우저에서만 지원됩니다. Safari, Firefox 등 다른 주요 브라우저에서는 작동하지 않는 것이 문제였습니다.

html2canvas: 대안적 접근법과 기술적 장벽

브라우저 호환성 문제를 해결하기 위해 html2canvas 라이브러리를 활용한 대안적 접근법을 모색했습니다. 이 라이브러리는 DOM 요소를 캔버스로 변환하여 렌더링하는 기능을 제공합니다.

그러나 html2canvas를 활용한 구현 과정에서 여러 기술적 장벽에 직면했습니다:

  1. 보안 정책으로 인한 제약:

    SecurityError: The operation is insecure.
    
    
    Unable to get image data from canvas because the canvas has been tainted by cross-origin data.
    
    

    웹 보안 정책(CORS, Same-Origin Policy)으로 인해 외부 리소스가 포함된 요소의 픽셀 데이터를 추출할 수 없는 문제가 발생했습니다.

  2. 좌표 계산 오류:

    TypeError: Value Infinity is outside the range [-2147483648, 2147483647]
    
    

    캔버스 내 좌표 계산 과정에서 예상치 못한 값이 생성되어 오류가 발생했습니다.

해결 방안: 옵션 조정과 하이브리드 접근법

html2canvas의 다양한 옵션을 조정하여 문제 해결을 시도했습니다:

const canvas = await html2canvas(viewportElement, {
  logging: false,
  backgroundColor: null,
  scale: 1,
  useCORS: true,
  allowTaint: true,
  foreignObjectRendering: false,
  width: window.innerWidth,
  height: window.innerHeight,
  x: window.scrollX,
  y: window.scrollY,
  windowWidth: window.innerWidth,
  windowHeight: window.innerHeight,
  ignoreElements: (element) => {
    return element.tagName === 'IMG' ||
           element.tagName === 'IFRAME' ||
           element.tagName === 'VIDEO' ||
           element.style.backgroundImage !== '';
  }
});

특히 ignoreElements 옵션을 통해 외부 리소스가 포함된 요소를 렌더링에서 제외함으로써 tainted canvas 문제를 해결할 수 있었습니다. 또한 좌표 계산 문제는 다음과 같이 간소화하여 해결했습니다:

const x = event.clientX;
const y = event.clientY;

if (x >= 0 && x < canvas.width && y >= 0 && y < canvas.height) {
  const imageData = ctx.getImageData(x, y, 1, 1);
  const r = imageData.data[0];
  const g = imageData.data[1];
  const b = imageData.data[2];
  const color = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase()}`;
  onColorChange(color);
}