오프스크린 캔버스—웹 작업자로 캔버스 작업 속도를 높이세요.

Tim Dresser

캔버스는 화면에 모든 종류의 그래픽을 그리는 데 사용되는 인기 있는 방법이며 WebGL의 시작점입니다. 도형, 이미지를 그리거나 애니메이션을 실행하거나 동영상 콘텐츠를 표시하고 처리하는 데 사용할 수 있습니다. 미디어 리치 웹 애플리케이션과 온라인 게임에서 아름다운 사용자 환경을 만드는 데 자주 사용됩니다.

스크립트로 작성할 수 있습니다. 즉, 캔버스에 그려진 콘텐츠를 JavaScript와 같이 프로그래매틱 방식으로 만들 수 있습니다. 이를 통해 캔버스의 유연성이 향상됩니다.

동시에 최신 웹사이트에서 스크립트 실행은 가장 흔한 사용자 응답성 문제의 원인 중 하나입니다. 캔버스 로직과 렌더링은 사용자 상호작용과 동일한 스레드에서 실행되므로 애니메이션과 관련된 계산 (가끔은 과도한 계산)이 앱의 실제 및 인식된 성능에 영향을 줄 수 있습니다.

다행히 OffscreenCanvas는 이러한 위협에 대한 대답입니다.

브라우저 지원

  • Chrome: 69
  • Edge: 79
  • Firefox: 105
  • Safari: 16.4.

소스

이전에는 캔버스 그리기 기능이 <canvas> 요소에 연결되어 있었으므로 DOM에 직접 종속되었습니다. OffscreenCanvas는 이름에서 알 수 있듯이 DOM과 Canvas API를 화면 밖으로 이동하여 결합 해제합니다.

이러한 분리 덕분에 OffscreenCanvas 렌더링이 DOM에서 완전히 분리되므로 두 캔버스 간에 동기화가 없으므로 일반 캔버스보다 속도가 다소 개선됩니다.

또한 사용 가능한 DOM이 없더라도 웹 워커에서 사용할 수 있습니다. 이를 통해 다양한 흥미로운 사용 사례를 지원할 수 있습니다.

작업자에서 OffscreenCanvas 사용

작업자는 웹 버전의 스레드로, 백그라운드에서 태스크를 실행할 수 있습니다.

일부 스크립트를 작업자로 이동하면 앱이 기본 스레드에서 사용자에게 중요한 작업을 실행할 여유가 더 생깁니다. OffscreenCanvas가 없으면 사용 가능한 DOM이 없으므로 워커에서 Canvas API를 사용할 방법이 없었습니다.

OffscreenCanvas는 DOM에 종속되지 않으므로 사용할 수 있습니다. 다음 예에서는 OffscreenCanvas를 사용하여 작업자에서 그라디언트 색상을 계산합니다.

// file: worker.js
function getGradientColor(percent) {
  const canvas = new OffscreenCanvas(100, 1);
  const ctx = canvas.getContext('2d');
  const gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
  gradient.addColorStop(0, 'red');
  gradient.addColorStop(1, 'blue');
  ctx.fillStyle = gradient;
  ctx.fillRect(0, 0, ctx.canvas.width, 1);
  const imgd = ctx.getImageData(0, 0, ctx.canvas.width, 1);
  const colors = imgd.data.slice(percent * 4, percent * 4 + 4);
  return `rgba(${colors[0]}, ${colors[1]}, ${colors[2]}, ${colors[3]})`;
}

getGradientColor(40);  // rgba(152, 0, 104, 255 )

기본 스레드 차단 해제

과도한 계산을 작업자로 이동하면 기본 스레드에서 상당한 리소스를 확보할 수 있습니다. transferControlToOffscreen 메서드를 사용하여 일반 캔버스를 OffscreenCanvas 인스턴스에 미러링합니다. OffscreenCanvas에 적용된 작업은 소스 캔버스에 자동으로 렌더링됩니다.

const offscreen = document.querySelector('canvas').transferControlToOffscreen();
const worker = new Worker('myworkerurl.js');
worker.postMessage({canvas: offscreen}, [offscreen]);

다음 예에서는 색상 테마가 변경될 때 과도한 계산이 발생합니다. 빠른 데스크톱에서도 몇 밀리초가 소요됩니다. 애니메이션을 기본 스레드에서 실행하거나 작업자에서 실행하도록 선택할 수 있습니다. 기본 스레드의 경우 과도한 작업이 실행되는 동안 버튼과 상호작용할 수 없습니다. 스레드가 차단되기 때문입니다. 작업자의 경우 UI 응답성에는 영향을 미치지 않습니다.

데모

반대로도 작동합니다. 작업자에서 실행되는 애니메이션은 바쁜 기본 스레드의 영향을 받지 않습니다. 이 기능을 사용하면 다음 데모와 같이 시각적 버벅거림을 방지하고 기본 스레드 트래픽에도 불구하고 원활한 애니메이션을 보장할 수 있습니다.

데모

일반 캔버스의 경우 기본 스레드가 인위적으로 과부하되면 애니메이션이 중지되지만 작업자 기반 OffscreenCanvas는 원활하게 재생됩니다.

OffscreenCanvas API는 일반적으로 일반 Canvas 요소와 호환되므로 시장의 일부 주요 그래픽 라이브러리와 함께 점진적 개선으로 사용할 수 있습니다.

예를 들어 기능 감지를 실행하고 가능한 경우 렌더러 생성자에 캔버스 옵션을 지정하여 Three.js와 함께 사용할 수 있습니다.

const canvasEl = document.querySelector('canvas');
const canvas =
  'OffscreenCanvas' in window
    ? canvasEl.transferControlToOffscreen()
    : canvasEl;
canvas.style = {width: 0, height: 0};
const renderer = new THREE.WebGLRenderer({canvas: canvas});

여기서 한 가지 주의할 점은 Three.js는 canvas에 style.widthstyle.height 속성이 있어야 한다고 예상한다는 것입니다. OffscreenCanvas는 DOM에서 완전히 분리되어 있으므로 이러한 값이 없으므로 스텁을 사용하거나 이러한 값을 원래 캔버스 크기에 연결하는 로직을 제공하여 직접 제공해야 합니다.

다음은 작업자에서 기본 Three.js 애니메이션을 실행하는 방법을 보여줍니다.

데모

일부 DOM 관련 API는 worker에서 바로 사용할 수 없으므로 텍스처와 같은 고급 Three.js 기능을 사용하려면 더 많은 해결 방법이 필요할 수 있습니다. 이러한 실험을 시작하는 방법에 관한 아이디어는 Google I/O 2017 동영상을 참고하세요.

캔버스의 그래픽 기능을 많이 사용하는 경우 OffscreenCanvas가 앱의 성능에 긍정적인 영향을 미칠 수 있습니다. 캔버스 렌더링 컨텍스트를 작업자에게 제공하면 웹 애플리케이션의 병렬 처리가 증가하고 멀티 코어 시스템을 더 효과적으로 활용할 수 있습니다.

추가 리소스