CSS 페인트 API

Chrome 65의 새로운 가능성

CSS Paint API('CSS 맞춤 페인트' 또는 'Houdini의 페인트 Worklet'이라고도 함)는 Chrome 65부터 기본적으로 사용 설정됩니다. 기본 설명 무엇을 할 수 있을까요? 어떻게 작동하나요? 자, 계속 읽어 볼까요?

CSS Paint API를 사용하면 CSS 속성이 이미지를 예상할 때마다 프로그래매틱 방식으로 이미지를 생성할 수 있습니다. background-image 또는 border-image와 같은 속성은 일반적으로 이미지 파일을 로드하기 위해 url()와 함께 사용되거나 linear-gradient()와 같은 CSS 내장 함수와 함께 사용됩니다. 이를 사용하는 대신 이제 paint(myPainter)를 사용하여 페인트 Worklet을 참조할 수 있습니다.

페인트 작업 도구 작성

myPainter라는 페인트 Worklet을 정의하려면 CSS.paintWorklet.addModule('my-paint-worklet.js')를 사용하여 CSS 페인트 Worklet 파일을 로드해야 합니다. 이 파일에서 registerPaint 함수를 사용하여 페인트 Worklet 클래스를 등록할 수 있습니다.

class MyPainter {
  paint(ctx, geometry, properties) {
    // ...
  }
}

registerPaint('myPainter', MyPainter);

paint() 콜백 내에서 <canvas>에서와 동일한 방식으로 ctx을 사용할 수 있습니다. CanvasRenderingContext2D <canvas>에 그리는 방법을 알고 있다면 페인트 워크릿에서 그릴 수 있습니다. geometry는 사용 가능한 캔버스의 너비와 높이를 알려줍니다. properties 이 도움말의 뒷부분에서 설명해 드리겠습니다.

입문 예로, 격자무늬 페인트 Worklet을 작성하여 <textarea>의 배경 이미지로 사용해 보겠습니다. (기본적으로 텍스트 영역의 크기를 조절할 수 있으므로 텍스트 영역을 사용하고 있습니다.)

<!-- index.html -->
<!doctype html>
<style>
  textarea {
    background-image: paint(checkerboard);
  }
</style>
<textarea></textarea>
<script>
  CSS.paintWorklet.addModule('checkerboard.js');
</script>
// checkerboard.js
class CheckerboardPainter {
  paint(ctx, geom, properties) {
    // Use `ctx` as if it was a normal canvas
    const colors = ['red', 'green', 'blue'];
    const size = 32;
    for(let y = 0; y < geom.height/size; y++) {
      for(let x = 0; x < geom.width/size; x++) {
        const color = colors[(x + y) % colors.length];
        ctx.beginPath();
        ctx.fillStyle = color;
        ctx.rect(x * size, y * size, size, size);
        ctx.fill();
      }
    }
  }
}

// Register our class under a specific name
registerPaint('checkerboard', CheckerboardPainter);

이전에 <canvas>를 사용한 적이 있다면 이 코드가 익숙할 것입니다. 여기에서 라이브 데모를 확인하세요.

배경 이미지로 격자무늬 패턴이 있는 텍스트 영역
배경 이미지로 격자무늬 패턴을 사용하는 텍스트 영역

여기에서 일반적인 배경 이미지를 사용할 때와 다른 점은 사용자가 텍스트 영역의 크기를 조절할 때마다 필요에 따라 패턴이 다시 그려진다는 것입니다. 즉, 배경 이미지는 고밀도 디스플레이의 보정을 포함하여 항상 정확히 필요한 만큼 커야 합니다.

꽤 멋지지만 상당히 정적이기도 합니다. 패턴은 동일하지만 정사각형의 크기는 다르게 만들고 싶을 때마다 새 Worklet을 작성하고자 할까요? 그렇지 않습니다.

Worklet의 매개변수화

다행히 페인트 Worklet은 추가 매개변수 properties가 작동하는 다른 CSS 속성에 액세스할 수 있습니다. 클래스에 정적 inputProperties 속성을 제공하면 맞춤 속성을 비롯한 모든 CSS 속성의 변경사항을 구독할 수 있습니다. 값은 properties 매개변수를 통해 제공됩니다.

<!-- index.html -->
<!doctype html>
<style>
  textarea {
    /* The paint worklet subscribes to changes of these custom properties. */
    --checkerboard-spacing: 10;
    --checkerboard-size: 32;
    background-image: paint(checkerboard);
  }
</style>
<textarea></textarea>
<script>
  CSS.paintWorklet.addModule('checkerboard.js');
</script>
// checkerboard.js
class CheckerboardPainter {
  // inputProperties returns a list of CSS properties that this paint function gets access to
  static get inputProperties() { return ['--checkerboard-spacing', '--checkerboard-size']; }

  paint(ctx, geom, properties) {
    // Paint worklet uses CSS Typed OM to model the input values.
    // As of now, they are mostly wrappers around strings,
    // but will be augmented to hold more accessible data over time.
    const size = parseInt(properties.get('--checkerboard-size').toString());
    const spacing = parseInt(properties.get('--checkerboard-spacing').toString());
    const colors = ['red', 'green', 'blue'];
    for(let y = 0; y < geom.height/size; y++) {
      for(let x = 0; x < geom.width/size; x++) {
        ctx.fillStyle = colors[(x + y) % colors.length];
        ctx.beginPath();
        ctx.rect(x*(size + spacing), y*(size + spacing), size, size);
        ctx.fill();
      }
    }
  }
}

registerPaint('checkerboard', CheckerboardPainter);

이제 서로 다른 모든 종류의 격자무늬에 동일한 코드를 사용할 수 있습니다. 하지만 더 좋은 점은 이제 DevTools로 이동하여 올바른 모양을 찾을 때까지 값을 조작할 수 있다는 것입니다.

페인트 Worklet을 지원하지 않는 브라우저

이 문서 작성 시점에는 Chrome에만 페인트 Worklet이 구현되어 있습니다. 다른 모든 브라우저 공급업체에서도 긍정적인 신호이지만 진전이 없습니다. 최신 정보를 확인하려면 Is Houdini Ready Yet?(후디니 준비 완료 여부)를 정기적으로 확인하세요. 그때까지는 점진적 개선을 사용하여 페인트 Worklet을 지원하지 않더라도 코드를 계속 실행해야 합니다. 예상대로 작동하도록 하려면 CSS와 JS라는 두 위치에서 코드를 조정해야 합니다.

JavaScript에서 페인트 Worklet의 지원을 감지하려면 CSS 객체를 확인하면 됩니다. js if ('paintWorklet' in CSS) { CSS.paintWorklet.addModule('mystuff.js'); } CSS 측의 경우 두 가지 옵션이 있습니다. 다음과 같이 @supports를 사용할 수 있습니다.

@supports (background: paint(id)) {
  /* ... */
}

좀 더 간단한 방법은 CSS에 알 수 없는 함수가 있는 경우 전체 속성 선언을 무효화한 후 무시한다는 사실을 활용하는 것입니다. 속성을 두 번 지정하면(먼저 페인트 Worklet 없이 다음 페인트 Worklet을 사용하여 지정) 점진적으로 개선됩니다.

textarea {
  background-image: linear-gradient(0, red, blue);
  background-image: paint(myGradient, red, blue);
}

페인트 Worklet을 지원 하는 브라우저에서는 두 번째 background-image 선언이 첫 번째 선언을 덮어씁니다. 페인트 Worklet을 지원하지 않는 브라우저에서는 두 번째 선언이 유효하지 않으므로 삭제되고 첫 번째 선언이 적용됩니다.

CSS 페인트 폴리필

여러 용도로 최신 브라우저에 CSS 맞춤 페인트 및 페인트 Worklet 지원을 추가하는 CSS 페인트 폴리필을 사용할 수도 있습니다.

사용 사례

페인트 워크릿의 사용 사례는 많지만 그중 일부는 다른 워크릿보다 더 명확합니다. 가장 명백한 방법 중 하나는 페인트 Worklet을 사용하여 DOM의 크기를 줄이는 것입니다. CSS를 사용하여 장식을 만들기 위해서만 요소를 추가하는 경우가 많습니다. 예를 들어 Material Design Lite에서 물결 효과가 있는 버튼에는 물결 효과 자체를 구현하기 위한 2개의 추가 <span> 요소가 포함되어 있습니다. 버튼이 많은 경우 DOM 요소가 상당히 많이 추가되어 모바일에서 성능이 저하될 수 있습니다. 대신 페인트 Worklet을 사용하여 물결 효과를 구현하면 추가 요소 0개와 페인트 Worklet 하나만 생성됩니다. 또한 맞춤설정 및 매개변수화가 훨씬 쉬운 항목이 있습니다.

페인트 Worklet을 사용하는 또 다른 이점은 대부분의 시나리오에서 페인트 Worklet을 사용하는 솔루션이 바이트 측면에서 작다는 것입니다. 물론 단점이 있습니다. 캔버스 크기나 매개변수가 변경될 때마다 페인트 코드가 실행된다는 점입니다. 따라서 코드가 복잡하고 시간이 오래 걸리면 버벅거림이 발생할 수 있습니다. Chrome은 오래 실행되는 페인트 워크렛도 기본 스레드의 응답성에 영향을 미치지 않도록 페인트 워크렛을 기본 스레드 외부로 이동하는 작업을 진행하고 있습니다.

제 생각에 가장 흥미로운 전망은 페인트 워크렛을 사용하면 아직 브라우저에는 없는 CSS 기능을 효율적으로 폴리필할 수 있다는 점입니다. 한 가지 예는 원뿔 그라디언트가 Chrome에 기본적으로 적용될 때까지 폴리필하는 것입니다. 또 다른 예로, CSS 회의에서 이제 여러 테두리 색상을 사용할 수 있다는 결정을 내렸습니다. 이 회의는 계속 진행되면서 동료인 Ian Kilpatrick이 페인트 워크렛을 사용하여 이 새로운 CSS 동작을 위한 polyfill을 작성했습니다.

'틀을 벗어나 생각하기'

대부분의 사람들은 페인트 Worklet에 관해 알아볼 때 배경 이미지와 테두리 이미지를 생각하기 시작합니다. 페인트 Worklet의 덜 직관적인 사용 사례는 DOM 요소가 임의의 도형을 갖도록 하는 mask-image입니다. 예를 들어 다이아몬드는 다음과 같습니다.

다이아몬드 모양의 DOM 요소입니다.
다이아몬드 모양의 DOM 요소입니다.

mask-image는 요소의 크기인 이미지를 사용합니다. 마스크 이미지가 투명한 영역에서는 요소가 투명합니다. 마스크 이미지가 불투명한 영역, 즉 요소가 불투명합니다.

지금 Chrome에서 보기

페인트 worklet은 한동안 Chrome Canary에 사용되었습니다. Chrome 65에서는 기본적으로 사용 설정되어 있습니다. 페인트 워크렛이 여는 새로운 가능성을 시험하고 빌드한 것을 보여줍니다. 더 많은 영감을 얻으려면 빈센트 드 올리베이라의 컬렉션을 살펴보세요.