CSS 페인팅 API 레벨 1

W3C 후보 권고안 초안,

이 문서에 대한 추가 정보
이 버전:
https://www.w3.org/TR/2021/CRD-css-paint-api-1-20211216/
최신 공개 버전:
https://www.w3.org/TR/css-paint-api-1/
에디터 초안:
https://drafts.css-houdini.org/css-paint-api-1/
이전 버전:
역사:
https://www.w3.org/standards/history/css-paint-api-1
피드백:
public-houdini@w3.org 제목에 “[css-paint-api] … 메시지 주제 …” 포함 (아카이브)
GitHub
구현 보고서:
https://wpt.fyi/results/css/css-paint-api
에디터:
이전 에디터:

요약

웹 개발자가 자바스크립트를 사용하여 사용자 정의 CSS <image>를 정의할 수 있도록 해주는 API로, 스타일과 크기 변경에 반응합니다. 자세한 내용은 EXPLAINER를 참고하세요.

이 문서의 상태

이 섹션은 문서가 발행된 시점의 상태를 설명합니다. 현재 W3C 발행 목록과 이 기술 보고서의 최신 개정본은 W3C 기술 보고서 인덱스 https://www.w3.org/TR/에서 확인할 수 있습니다.

이 문서는 CSS 작업 그룹에서 후보 권고안 초안으로, 권고안 트랙을 따라 발행되었습니다. 후보 권고안으로 발행되었다고 해서 W3C 및 회원의 승인됨을 의미하지는 않습니다. 후보 권고안 초안은 이전 후보 권고안에서 작업 그룹이 다음 후보 권고안 스냅샷에 포함하기로 한 변경사항을 통합합니다.

이 문서는 초안이며, 언제든지 업데이트, 교체 또는 폐기될 수 있습니다. 이 문서를 진행 중인 작업 외의 용도로 인용하는 것은 적절하지 않습니다.

피드백은 GitHub에서 이슈 등록 (권장)에 의해 제출해 주세요. 제목에 “css-paint-api”를 포함하여 다음과 같이 작성해 주십시오: “[css-paint-api] ...코멘트 요약...”. 모든 이슈와 코멘트는 아카이브됩니다. 또는, 피드백은 (아카이브됨) 공개 메일링 리스트 www-style@w3.org로 보낼 수 있습니다.

이 문서는 2021년 11월 2일 W3C 프로세스 문서에 따라 관리됩니다.

이 문서는 W3C 특허 정책에 따라 운영되는 그룹에서 작성되었습니다. W3C는 그룹의 결과물과 관련하여 공개된 특허 공개 목록을 유지합니다. 해당 페이지에는 특허 공개 방법에 대한 안내도 포함되어 있습니다. 특정 특허가 필수 청구항을 포함한다고 판단되는 경우 W3C 특허 정책 6절에 따라 정보를 공개해야 합니다.

1. 소개

CSS의 페인트 단계는 레이아웃 단계에서 생성된 박스의 크기와 계산된 스타일을 기반으로 박스의 배경, 콘텐츠, 하이라이트를 그리는 역할을 합니다.

이 명세서는 개발자가 크기 또는 계산된 스타일 변경에 따라 박스의 일부를 추가 <image> 함수로 그릴 수 있도록 하는 API를 설명합니다.

참고: 향후 명세 버전에서는 박스의 일부(예: 배경 레이어)에 대해 clip, global alpha, filter를 정의하는 기능이 추가될 수 있습니다.

2. 페인트 워클릿

CSS/paintWorklet

오직 한 개의 엔진에서만 지원됩니다.

Firefox미지원Safari미지원Chrome65+
Opera52+Edge79+
Edge (Legacy)미지원IE미지원
Firefox for Android미지원iOS Safari미지원Chrome for Android65+Android WebView65+Samsung Internet9.0+Opera Mobile47+

paintWorklet 속성을 통해 페인트와 관련된 모든 클래스에 대한 Worklet 에 접근할 수 있습니다.

paintWorkletworklet global scope typePaintWorkletGlobalScope입니다.

paintWorkletworklet destination type"paintworklet"입니다.

partial namespace CSS {
    [SameObject] readonly attribute Worklet paintWorklet;
};

PaintWorkletGlobalScopepaintWorklet 의 글로벌 실행 컨텍스트입니다.

PaintWorklet/devicePixelRatio

오직 한 개의 엔진에서만 지원됩니다.

Firefox미지원Safari미지원Chrome65+
Opera?Edge79+
Edge (Legacy)미지원IE미지원
Firefox for Android미지원iOS Safari미지원Chrome for Android65+Android WebView65+Samsung Internet9.0+Opera Mobile?

PaintWorkletGlobalScopedevicePixelRatio 속성을 가지며, 이는 Window.devicePixelRatio 속성과 동일합니다.

PaintWorklet

오직 한 개의 엔진에서만 지원됩니다.

Firefox미지원Safari미지원Chrome65+
Opera?Edge79+
Edge (Legacy)미지원IE미지원
Firefox for Android미지원iOS Safari미지원Chrome for Android65+Android WebView65+Samsung Internet9.0+Opera Mobile?
[Global=(Worklet,PaintWorklet),Exposed=PaintWorklet]
interface PaintWorkletGlobalScope : WorkletGlobalScope {
    undefined registerPaint(DOMString name, VoidFunction paintCtor);
    readonly attribute unrestricted double devicePixelRatio;
};

PaintRenderingContext2DSettings 는 페인트 캔버스와 연결된 렌더링 컨텍스트의 설정을 포함합니다. PaintRenderingContext2DSettings 는 캔버스 렌더링 컨텍스트 2D 설정의 지원되는 부분 집합을 제공합니다. 향후에는 페인트 캔버스에서 색상 관리 지원이 추가될 수 있습니다.

dictionary PaintRenderingContext2DSettings {
    boolean alpha = true;
};
참고: 클래스의 구조는 다음과 같습니다:
class MyPaint {
    static get inputProperties() { return ['--foo']; }
    static get inputArguments() { return ['<color>']; }
    static get contextOptions() { return {alpha: true}; }

    paint(ctx, size, styleMap) {
        // 페인트 코드가 여기에 들어갑니다.
    }
}

3. 개념

페인트 정의구조체로, 저자 정의 PaintWorkletGlobalScope 에 필요한 정보를 설명합니다. 이 정보는 <image> (paint() 함수로 참조 가능)와 관련 있습니다. 구성 요소는 다음과 같습니다:

문서 페인트 정의구조체로, 저자 정의 문서에 필요한 정보를 설명합니다. 정보는 <image> 함수(paint 함수에서 참조 가능)와 관련됩니다. 구성 요소는 다음과 같습니다:

4. 사용자 정의 페인트 등록

document에는 map 형태의 문서 페인트 정의가 있습니다. 처음에는 이 map이 비어 있으며, registerPaint(name, paintCtor) 가 호출될 때 채워집니다.

PaintWorkletGlobalScope 에는 map 형태의 페인트 정의가 있습니다. 처음에는 이 map이 비어 있으며, registerPaint(name, paintCtor) 가 호출될 때 채워집니다.

PaintWorkletGlobalScope 에는 map 형태의 페인트 클래스 인스턴스가 있습니다. 처음에는 이 map이 비어 있으며, 페인트 이미지를 그리기가 사용자 에이전트에 의해 호출될 때 채워집니다.

페인트 클래스 인스턴스 map에 있는 클래스 인스턴스들은 사용자 에이전트가 언제든지 해제하고 map에서 제거할 수 있습니다. 이는 <paint()> 함수가 더 이상 사용되지 않거나, 사용자 에이전트가 메모리를 회수해야 하는 경우에 발생할 수 있습니다.

PaintWorklet/registerPaint

오직 한 개의 엔진에서만 지원됩니다.

Firefox미지원Safari미지원Chrome65+
Opera?Edge79+
Edge (Legacy)미지원IE미지원
Firefox for Android미지원iOS Safari미지원Chrome for Android65+Android WebView65+Samsung Internet9.0+Opera Mobile?
registerPaint(name, paintCtor) 메서드가 호출되면, 사용자 에이전트는 다음 단계를 반드시 실행해야 합니다:
  1. name이 빈 문자열이면, throw TypeError를 발생시키고 모든 단계를 중단합니다.

  2. paintDefinitionMapPaintWorkletGlobalScope페인트 정의 map으로 설정합니다.

  3. paintDefinitionMap[name] 존재하면, throw "InvalidModificationError" DOMException 을 발생시키고 모든 단계를 중단합니다.

  4. inputProperties를 빈 sequence<DOMString>으로 설정합니다.

  5. inputPropertiesIterableGet(paintCtor, "inputProperties")의 결과로 설정합니다.

  6. inputPropertiesIterable이 undefined가 아니면, inputProperties변환 결과로 설정합니다. inputPropertiesIterablesequence<DOMString>으로 변환합니다. 예외가 발생하면 예외를 다시 발생시키고 모든 단계를 중단합니다.

  7. inputProperties지원되는 CSS 속성커스텀 속성만 포함하도록 필터링합니다.

참고: input properties getter로 제공되는 CSS 속성 목록은 커스텀 또는 기본 CSS 속성 모두 가능합니다.

참고: CSS 속성 목록에는 shorthand가 포함될 수 있습니다.

참고: paint 이미지 클래스가 미래 호환성을 위해, CSS 속성 목록은 현재 사용자 에이전트에 유효하지 않은 속성도 포함할 수 있습니다. 예시: margin-bikeshed-property.

  1. inputArguments를 빈 sequence<DOMString>으로 설정합니다.

  2. inputArgumentsIterableGet(paintCtor, "inputArguments")의 결과로 설정합니다.

  3. inputArgumentsIterable이 undefined가 아니면, inputArguments변환 결과로 설정합니다. inputArgumentsIterablesequence<DOMString>으로 변환합니다. 예외가 발생하면 예외를 다시 발생시키고 모든 단계를 중단합니다.

  4. inputArgumentSyntaxes 리스트로 설정합니다.

  5. inputArguments의 각 item에 대해 다음 하위 단계를 수행합니다:

    1. consume a syntax definitionitem에서 시도합니다. 실패하면 throw TypeError를 발생시키고 모든 단계를 중단합니다. 성공하면 parsedSyntax를 반환된 syntax definition으로 설정합니다.

    2. parsedSyntaxinputArgumentSyntaxes에 추가합니다.

  6. contextOptionsValueGet(paintCtor, "contextOptions")의 결과로 설정합니다.

  7. paintRenderingContext2DSettings변환 결과로 설정합니다. contextOptionsValuePaintRenderingContext2DSettings로 변환합니다. 예외가 발생하면 예외를 다시 발생시키고 모든 단계를 중단합니다.

    참고: paintRenderingContext2DSettings.alphafalse로 설정하면 사용자 에이전트가 텍스트를 안티앨리어싱하는 것 외에도 "visibility" 최적화(예: paint 이미지가 불투명할 때 paint 이미지 뒤의 이미지를 그리지 않음)를 수행할 수 있습니다.

  8. IsConstructor(paintCtor)의 결과가 false이면, throw TypeError를 발생시키고 모든 단계를 중단합니다.

  9. prototypeGet(paintCtor, "prototype")의 결과로 설정합니다.

  10. Type(prototype)의 결과가 Object가 아니면, throw TypeError를 발생시키고 모든 단계를 중단합니다.

  11. paintValueGet(prototype, "paint")의 결과로 설정합니다.

  12. paint변환 결과로 설정합니다. paintValueFunction 콜백 함수 타입으로 변환합니다. 변환 중 발생하는 예외는 다시 발생시킵니다.

  13. definition을 새로운 페인트 정의로 생성하며, 다음과 같이 구성됩니다:

  14. paintDefinitionMap[name]을 definition으로 설정합니다.

  15. 테스크를 큐에 추가하여 다음 단계를 실행합니다:

    1. documentPaintDefinitionMap을 연결된 문서문서 페인트 정의 map으로 설정합니다.

    2. documentDefinition을 새로운 문서 페인트 정의로 생성하며, 다음과 같이 구성됩니다:

    3. documentPaintDefinitionMap[name] 존재하면, 다음 단계를 실행합니다:

      1. existingDocumentDefinitionget documentPaintDefinitionMap[name]의 결과로 설정합니다.

      2. existingDocumentDefinition"invalid"이면 모든 단계를 중단합니다.

      3. existingDocumentDefinitiondocumentDefinition이 동등하지 않으면 (입력 속성, 입력 인자 문법, PaintRenderingContext2DSettings 객체가 다르면), 다음을 실행합니다:

        documentPaintDefinitionMap[name]을 "invalid"로 설정합니다.

        동일한 클래스가 다른 inputProperties, inputArguments, paintRenderingContext2DSettings와 함께 등록되었다는 오류를 디버깅 콘솔에 기록합니다.

    4. 그렇지 않으면 documentPaintDefinitionMap[name]을 documentDefinition으로 설정합니다.

참고: 입력 속성 목록은 반드시 한 번만 조회되어야 하며, 클래스는 입력 속성을 동적으로 변경할 기회가 없습니다.

참고: 향후 명세 버전에서는 저자가 다른 타입의 RenderingContext를 받을 수 있는 기능이 추가될 수 있습니다. 저자가 3D 효과를 렌더링하기 위해 WebGL 렌더링 컨텍스트를 원할 수도 있습니다. WebGL 렌더링 컨텍스트를 PaintSizeStylePropertyMap을 입력으로 받도록 설정하는 데는 복잡성이 있습니다.

5. 페인트 표기법

image/paint()

오직 한 개의 엔진에서만 지원됩니다.

Firefox미지원Safari미지원Chrome65+
Opera52+Edge79+
Edge (Legacy)미지원IE미지원
Firefox for Android미지원iOS Safari미지원Chrome for Android65+Android WebView65+Samsung Internet9.2+Opera Mobile47+
paint() = paint( <ident>, <declaration-value>? )

<paint()> 함수는 <image> 타입에서 추가로 지원되는 표기법입니다.

<style>
    .logo { background-image: paint(company-logo); }
    .chat-bubble { background-image: paint(chat-bubble, blue); }
</style>

cursor 속성에 대해서, <paint()> 함수는 잘못된 이미지로 취급되어 다음으로 지원되는 <image>로 대체됩니다.

computed value 시점에서는 <paint()> 함수가 registerPaint()로 등록된 문법과 일치할 필요는 없습니다. 대신, 파싱이 페인트 이미지를 그리기 내부에서 발생할 때 잘못된 이미지가 됩니다.

6. 2D 렌더링 컨텍스트

[Exposed=PaintWorklet]
interface PaintRenderingContext2D {
};
PaintRenderingContext2D includes CanvasState;
PaintRenderingContext2D includes CanvasTransform;
PaintRenderingContext2D includes CanvasCompositing;
PaintRenderingContext2D includes CanvasImageSmoothing;
PaintRenderingContext2D includes CanvasFillStrokeStyles;
PaintRenderingContext2D includes CanvasShadowStyles;
PaintRenderingContext2D includes CanvasRect;
PaintRenderingContext2D includes CanvasDrawPath;
PaintRenderingContext2D includes CanvasDrawImage;
PaintRenderingContext2D includes CanvasPathDrawingStyles;
PaintRenderingContext2D includes CanvasPath;

참고: PaintRenderingContext2DCanvasRenderingContext2D API의 부분 집합만 구현합니다. 특히 CanvasImageData, CanvasUserInterface, CanvasText, CanvasTextDrawingStyles API는 구현하지 않습니다.

PaintRenderingContext2D 객체는 출력 비트맵을 가집니다. 이 객체가 생성될 때 초기화됩니다. 출력 비트맵의 크기는 렌더링되는 객체의 구체적 객체 크기입니다.

PaintRenderingContext2D 객체는 alpha 플래그도 가지며, true 또는 false로 설정할 수 있습니다. 컨텍스트가 생성될 때 기본적으로 alpha 플래그는 true로 설정되어야 합니다. PaintRenderingContext2D 객체의 alpha 플래그가 false로 설정되면, 모든 픽셀의 alpha 채널은 1.0(완전히 불투명)으로 고정되고, 픽셀의 alpha 성분을 변경하는 시도는 조용히 무시되어야 합니다.

출력 비트맵의 크기는 반드시 내부적으로 또는 렌더링 시 실제 사용자 에이전트가 사용할 비트맵의 크기를 의미하지는 않습니다. 예를 들어, 시각적 뷰포트가 확대된 경우 사용자 에이전트는 좌표 공간의 디바이스 픽셀 수에 해당하는 비트맵을 내부적으로 사용할 수 있으므로 결과 렌더링 품질이 높아집니다.

또한 사용자 에이전트는 출력 비트맵에 적용된 그리기 작업의 순서를 기록하여, 이후 디바이스 비트맵에 올바른 해상도로 그릴 수 있게 할 수 있습니다. 이는 예를 들어 시각적 뷰포트가 확대되는 동안 출력 비트맵의 동일한 결과물을 반복적으로 재사용할 수 있게 합니다.

"currentColor"PaintRenderingContext2D API에서 색상으로 사용될 때, 불투명한 검정색으로 처리됩니다.

아래 코드는 완전히 검은색 사각형을 생성합니다.
registerPaint('currentcolor', class {
    paint(ctx, size) {
        ctx.fillStyle = 'currentColor';
        ctx.fillRect(0, 0, size.width, size.height);
    }
});
사용자 에이전트가 주어진 width, height, paintRenderingContext2DSettings에 대해 PaintRenderingContext2D 객체 생성을 할 때, 다음 단계를 반드시 실행해야 합니다:
  1. PaintRenderingContext2D를 생성합니다.

  2. 컨텍스트의 출력 비트맵에 대해 비트맵 크기 설정widthheight의 반올림 값으로 합니다.

  3. PaintRenderingContext2Dalpha 플래그를 paintRenderingContext2DSettingsalpha 값으로 설정합니다.

  4. PaintRenderingContext2D를 반환합니다.

참고: 렌더링 컨텍스트의 초기 상태는 비트맵 크기 설정 알고리즘에서 설정되며, 여기서 렌더링 컨텍스트를 초기 상태로 리셋하고 출력 비트맵을 지웁니다.

6.1. CSSImageValue 그리기

CanvasImageSource 타입은 CSSImageValue 타입도 이미지 소스로 사용할 수 있도록 확장되었습니다.

CanvasDrawImage 믹스인을 사용하는 인터페이스의 경우:

참고: 이것은 결국 HTML 명세의 canvas 섹션으로 이동되어야 합니다. 이슈 819를 참고하세요.

7. 이미지 그리기

<paint()> 함수 이미지를 box에 대해 시각적 뷰포트 내에 있는 경우, 사용자 에이전트는 반드시 draw a paint image 알고리즘을 호출하여 나온 이미지 출력을 표시해야 합니다.

참고: 사용자 에이전트는 시각적 뷰포트 내의 <paint()> 함수에 대해 매 프레임마다 draw a paint image를 실행할 필요는 없습니다. 결과를 캐시할 수 있고, (추가적인 무효화 단계를 사용할 수도 있음) 올바른 이미지 출력을 표시할 수 있습니다.

참고: 사용자 에이전트는 시각적 뷰포트 밖에 있는 이미지를 그리는 것을 선택적으로 지연시킬 수 있습니다.

저자가 requestAnimationFrame 내에서 스타일을 업데이트하는 경우 예시:
requestAnimationFrame(function() {
    element.styleMap.set('--custom-prop-invalidates-paint', 42);
});

그리고 element가 시각적 뷰포트 내에 있다면, 사용자 에이전트는 draw a paint image를 실행하고 현재 프레임에 대해 결과를 표시해야 합니다.

draw a paint image 함수는 object size negotiation 알고리즘 도중 사용자 에이전트에 의해 호출되며, 이는 <image> 렌더링을 담당하며, snappedConcreteObjectSize는 아래와 같이 정의됩니다. concreteObjectSizebox의 구체적 객체 크기입니다. snappedConcreteObjectSize는 보통 concreteObjectSize와 동일합니다. 하지만 사용자 에이전트가 픽셀 경계에 맞춰 크기를 조정할 수도 있습니다. 이 경우, 사용자 에이전트는 원래 크기에서 비례적으로 snappedConcreteObjectSize를 조정해야 하며 <paint()> 함수가 그리기를 맞춰 조정할 수 있도록 해야 합니다.

object size negotiation 알고리즘의 목적상, 페인트 이미지는 자연 치수를 갖지 않습니다.

참고: 향후 명세 버전에서 저자가 페인트 이미지의 자연 치수를 지정할 수 있는 기능이 추가될 수 있습니다. 이는 콜백 형태로 노출되어 저자가 정적 자연 치수를 정의하거나, 계산된 스타일과 크기 변경에 따라 동적으로 자연 치수를 업데이트할 수 있게 될 것입니다.

PaintSize 객체는 저자가 그려야 할 이미지의 크기를 나타냅니다. 이는 사용자 에이전트가 제공한 snappedConcreteObjectSize입니다.

참고: CSS Images 3 § 4.4 CSS 객체 크기 예제에서 구체적 객체 크기가 어떻게 계산되는지 예시를 볼 수 있습니다.

draw a paint image 함수는 사용자 에이전트가 임의의 시점, 임의의 snappedConcreteObjectSize로 추측적으로 호출할 수 있습니다. 이렇게 생성된 이미지는 표시되지 않습니다.

참고: 사용자 에이전트는 snappedConcreteObjectSize의 미래 값을 추측하기 위해 어떤 휴리스틱을 사용해도 됩니다. 예를 들어 크기가 변하지 않을 것이라 추측할 수도 있습니다.

참고: 이미지는 표시되지 않더라도 캐시될 수 있고, 이후 <paint()> 함수의 호출에서 캐시된 이미지를 사용할 수 있습니다.

[Exposed=PaintWorklet]
interface PaintSize {
    readonly attribute double width;
    readonly attribute double height;
};
사용자 에이전트가 <paint()> 함수의 box를 적절한 스태킹 레벨(관련 CSS 속성에 따라 정의됨)에 그리기를 원할 때, snappedConcreteObjectSize가 주어지면 반드시 다음 단계를 실행해야 합니다:
  1. paintFunction을 그리려는 box<paint()> 함수로 설정합니다.

  2. namepaintFunction의 첫 번째 인자로 설정합니다.

  3. documentPaintDefinitionMap을 연결된 문서문서 페인트 정의 map으로 설정합니다.

  4. documentPaintDefinitionMap[name]이 존재하지 않으면, 이미지 출력은 잘못된 이미지로 하고, 모든 단계를 중단합니다.

  5. documentDefinitionget documentPaintDefinitionMap[name]의 결과로 설정합니다.

  6. documentDefinition"invalid"이면, 이미지 출력을 잘못된 이미지로 하고, 모든 단계를 중단합니다.

  7. inputArgumentSyntaxesdocumentDefinition입력 인자 문법으로 설정합니다.

  8. inputArguments리스트로, "paint name" 인자 이후의 paintFunction의 모든 인자로 설정합니다.

  9. inputArguments가 등록된 문법 inputArgumentSyntaxes와 일치하지 않으면, 이미지 출력을 잘못된 이미지로 하고, 모든 단계를 중단합니다.

    이 단계는 다음과 같은 경우 실패할 수 있습니다:
    // paint.js
    registerPaint('failing-argument-syntax', class {
        static get inputArguments() { return ['<length>']; }
        paint(ctx, size, styleMap, args) { /* paint code here. */ }
    });
    
    <style>
        .example-1 {
            background-image: paint(failing-argument-syntax, red);
        }
        .example-2 {
            background-image: paint(failing-argument-syntax, 1px, 2px);
        }
    </style>
    <div class=example-1></div>
    <div class=example-2></div>
    <script>
        CSS.paintWorklet.addModule('paint.js');
    </script>
    

    example-1"red"가 등록된 문법과 일치하지 않으므로 잘못된 이미지가 생성됩니다.

    example-2는 함수 인자가 너무 많으므로 잘못된 이미지가 생성됩니다.

  10. workletGlobalScopePaintWorkletGlobalScope 중 하나로 설정합니다. (글로벌 스코프 선택 규칙은 § 7.1 글로벌 스코프 선택 참조)

    사용자 에이전트는 이 시점에 페인트 Worklet이 주어지면 임의로 워크렛 글로벌 스코프를 생성할 수도 있다.

  11. invoke a paint callbackname, inputArguments, snappedConcreteObjectSize, workletGlobalScope로 실행합니다. 선택적으로 병렬로 실행할 수 있습니다.

    참고: 만약 사용자 에이전트가 invoke a paint callback을 별도 스레드에서 병렬로 실행할 경우, 해당 스레드에서 사용할 수 있는 페인트 워클릿 글로벌 스코프를 선택해야 합니다.

사용자 에이전트가 name, inputArguments, snappedConcreteObjectSize, workletGlobalScopeinvoke a paint callback을 호출하려고 할 때, 반드시 다음 단계를 실행해야 합니다:
  1. paintDefinitionMapworkletGlobalScope페인트 정의 map으로 설정합니다.

  2. paintDefinitionMap[name]이 존재하지 않으면, 다음 단계를 실행합니다:

    1. 테스크를 큐에 추가하여 다음 단계를 실행합니다:

      1. documentPaintDefinitionMap을 연결된 문서문서 페인트 정의 map으로 설정합니다.

      2. documentPaintDefinitionMap[name]을 "invalid"로 설정합니다.

      3. 사용자 에이전트는 PaintWorkletGlobalScope에 클래스가 등록되지 않았음을 디버깅 콘솔에 오류로 표시해야 합니다.

    2. 이미지 출력을 잘못된 이미지로 하고, 모든 단계를 중단합니다.

    참고: 이 단계는 어떤 페인트 워클릿 글로벌 스코프가 registerPaint(name, paintCtor)를 받지 못한 경우를 처리합니다(다른 글로벌 스코프는 받았을 수 있음). 다른 글로벌 스코프에서 호출된 페인트 콜백은 성공할 수 있지만, 다음 프레임의 draw a paint image 호출에서는 성공하지 않을 수 있습니다.

  3. definitionget paintDefinitionMap[name]의 결과로 설정합니다.

  4. paintClassInstanceMapworkletGlobalScope페인트 클래스 인스턴스 map으로 설정합니다.

  5. paintInstanceget paintClassInstanceMap[|name|]의 결과로 설정합니다. paintInstance가 null이면, 다음 단계를 실행합니다:

    1. definition생성자 유효 플래그가 false이면, 이미지 출력을 잘못된 이미지로 하고, 모든 단계를 중단합니다.

    2. paintCtordefinition클래스 생성자로 설정합니다.

    3. paintInstanceConstruct(paintCtor)의 결과로 설정합니다.

      construct가 예외를 발생시키면, definition생성자 유효 플래그를 false로 설정하고, 이미지 출력을 잘못된 이미지로 하고, 모든 단계를 중단합니다.

    4. paintClassInstanceMap[name]을 paintInstance로 설정합니다.

  6. inputPropertiesdefinition입력 속성으로 설정합니다.

  7. styleMapStylePropertyMapReadOnly로 새로 생성하여, inputProperties에 나열된 속성들의 계산된 값만 포함시킵니다.

  8. renderingContextcreate a PaintRenderingContext2D object로 생성합니다:

    • "width" - snappedConcreteObjectSize가 제공한 너비

    • "height" - snappedConcreteObjectSize가 제공한 높이

    • "paintRenderingContext2DSettings" - definition이 제공한 PaintRenderingContext2DSettings 객체

    참고: renderingContext는 paint 호출 간에 재사용되지 않습니다. 암묵적으로 renderingContext에는 저장된 데이터나 상태가 없습니다. 예를 들어 컨텍스트에 clip을 설정하고 다음 호출 때 같은 clip 적용을 기대할 수 없습니다.

    참고: 암묵적으로 이는 paint 메서드가 끝나면 renderingContext가 사실상 "무효화"됨을 의미합니다. 저자 코드가 renderingContext 참조를 유지하고 메서드를 호출해도, 현재 이미지나 이후 이미지에 아무런 영향이 없습니다.

  9. paintSizePaintSize로 새로 생성하고, snappedConcreteObjectSize가 정의한 너비와 높이로 초기화합니다.

  10. 이 단계에서는 paintSize, styleMap, inputArguments가 이전 호출과 동일하다면, 이전 호출의 이미지를 재사용할 수 있습니다. 이 경우, 이미지 출력을 캐시된 이미지로 하고 모든 단계를 중단합니다.

    아래 예제에서 div-1div-2 모두 자바스크립트 인자가 동일한 paint 함수를 사용합니다. 사용자 에이전트는 한 번의 호출 결과를 캐시해 두 요소에 모두 사용할 수 있습니다.
    // paint.js
    registerPaint('simple', class {
        paint(ctx, size) {
            ctx.fillStyle = 'green';
            ctx.fillRect(0, 0, size.width, size.height);
        }
    });
    
    <style>
        .div-1 {
            width: 50px;
            height: 50px;
            background-image: paint(simple);
        }
        .div-2 {
            width: 100px;
            height: 100px;
    
            background-size: 50% 50%;
            background-image: paint(simple);
        }
    </style>
    <div class=div-1></div>
    <div class=div-2></div>
    <script>
        CSS.paintWorklet.addModule('paint.js');
    </script>
    
  11. paintFunctionCallbackdefinitionpaint 함수로 설정합니다.

  12. Invoke paintFunctionCallback을 인자 «renderingContext, paintSize, styleMap, inputArguments»로 호출하며, paintInstance콜백 this 값으로 사용합니다.

    paintFunctionCallback이 허용 가능한 시간 내에 완료되지 않으면(사용자 에이전트가 판단, 즉 "장시간 실행되는 스크립트") 사용자 에이전트는 스크립트를 종료하고, 이미지 출력을 잘못된 이미지로 하고, 모든 단계를 중단할 수 있습니다.

    참고: 사용자 에이전트는 디버깅 도구 내에서 저자가 페인트 클래스를 얼마나 비용이 많이 드는지 보여주는 툴을 제공할 수 있습니다. 또한 "응답 없음 스크립트" 대화상자를 보여줄 수도 있습니다.

  13. 이미지 출력은 메서드에 전달된 renderingContext에서 생성됩니다.

    예외가 발생하면 이미지 출력을 잘못된 이미지로 합니다.

참고: 결과 이미지의 내용은 접근성을 고려하여 설계되지 않았습니다. 저자는 표준 접근성 API를 통해 필요한 정보를 전달할 수 있습니다.

7.1. 글로벌 스코프 선택

사용자 에이전트가 페인트 PaintWorkletGlobalScope를 paint Worklet글로벌 스코프 리스트에서 선택해야 할 때 반드시 다음을 수행해야 합니다:

참고: 이러한 규칙은 저자가 글로벌 객체나 클래스에 재생성 불가능한 상태를 저장하는 것에 의존하지 않도록 하기 위해 존재합니다. 자세한 내용은 worklets 명세의 코드 멱등성 논의를 참고하세요.

8. 예제

8.1. 예제 1: 색상 원

아래 예제는 <paint()> 함수가 애니메이션될 수 있다는 점을 활용합니다. 예를 들어, 아래 예제에서 textarea에 포커스가 가면 --circle-color 속성이 deepskyblue에서 purple로 전환됩니다.

이 기능은 단순 전환뿐 아니라 CSS 애니메이션, Web Animations API에도 적용됩니다.

<!DOCTYPE html>
<style>
    #example {
    --circle-color: deepskyblue;

    background-image: paint(circle);
    font-family: sans-serif;
    font-size: 36px;
    transition: --circle-color 1s;
    }

    #example:focus {
    --circle-color: purple;
    }
</style>

<textarea id="example">
    CSS is awesome.
</textarea>

<script>
    CSS.registerProperty({
        name: '--circle-color',
        syntax: '<color>',
        initialValue: 'black',
        inherits: false
    });
    CSS.paintWorklet.addModule('circle.js');
</script>
// circle.js
registerPaint('circle', class {
    static get inputProperties() { return ['--circle-color']; }
    paint(ctx, geom, properties) {
    // 채우기 색상 변경
    const color = properties.get('--circle-color');
    ctx.fillStyle = color.cssText;

    // 중심점과 반지름 계산
    const x = geom.width / 2;
    const y = geom.height / 2;
    const radius = Math.min(x, y);

    // 원 그리기 \o/
    ctx.beginPath();
    ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
    ctx.fill();
    }
});

8.2. 예제 2: 이미지 플레이스홀더

저자는 이미지가 로딩 중일 때 paint를 사용하여 플레이스홀더 이미지를 그릴 수 있습니다.

<!DOCTYPE html>
<style>
#example {
    --image: url('#someUrlWhichIsLoading');
    background-image: paint(image-with-placeholder);
}
</style>

<div id="example"></div>

<script>
    CSS.registerProperty({
        name: '--image',
        syntax: '<image> | none',
        initialValue: 'none',
    });
    CSS.paintWorklet.addModule('image-placeholder.js');
</script>
// image-placeholder.js
registerPaint('image-with-placeholder', class {
    static get inputProperties() { return ['--image']; }
    paint(ctx, geom, properties) {
        const img = properties.get('--image');

        switch (img.state) {
            case 'ready':
                // 이미지가 로드됨! 이미지를 그림.
                ctx.drawImage(img, 0, 0, geom.width, geom.height);
                break;
            case 'pending':
                // 이미지가 로딩 중, 산을 그림.
                drawMountains(ctx);
                break;
            case 'invalid':
            default:
                // 이미지가 잘못됨(예: 로딩 실패), 슬픈 얼굴을 그림.
                drawSadFace(ctx);
                break;
        }
    }
});

8.3. 예제 3: 호

<!DOCTYPE html>
<style>
#example {
    width: 200px;
    height: 200px;

    background-image:
    paint(arc, purple, 0.4turn, 0.8turn, 40px, 15px),
    paint(arc, blue, -20deg, 170deg, 30px, 20px),
    paint(arc, red, 45deg, 220deg, 50px, 10px);
}
</style>

<div id="example"></div>

<script>
    CSS.paintWorklet.addModule('arc.js');
</script>
// arc.js
registerPaint('arc', class {
    static get inputArguments() {
    return [
        '<color>',
        '<angle>',  // 시작 각도
        '<angle>',  // 끝 각도
        '<length>', // 반지름
        '<length>', // 선 두께
    ];
    }

    paint(ctx, geom, _, args) {
    ctx.strokeStyle = args[0].cssText;

    // 중심점 계산
    const x = geom.width / 2;
    const y = geom.height / 2;

    // 시작/끝 각도를 라디안으로 변환
    const startAngle = this.convertAngle(args[1]) - Math.PI / 2;
    const endAngle = this.convertAngle(args[2]) - Math.PI / 2;

    // 반지름과 선 두께를 px로 변환
    const radius = this.convertLength(args[3]);
    const lineWidth = this.convertLength(args[4]);

    ctx.lineWidth = lineWidth;

    ctx.beginPath();
    ctx.arc(x, y, radius, startAngle, endAngle, false);
    ctx.stroke();
    }

    convertAngle(angle) {
    switch (angle.unit) {
        case 'deg':
        return angle.value * Math.PI / 180;
        case 'rad':
        return angle.value;
        case 'grad':
        return angle.value * Math.PI / 200;
        case 'turn':
        return angle.value * Math.PI / 0.5;
        default:
        throw Error(`알 수 없는 각도 단위: ${angle.unit}`);
    }
    }

    convertLength(length) {
    switch (length.type) {
        case 'px':
        return length.value;
        default:
        throw Error(`알 수 없는 길이 타입: ${length.type}`);
    }
    }
});

8.4. 예제 4: 크기에 따라 다른 색상

<h1>
    Heading 1
</h1>
<h1>
    Another heading
</h1>

<style>
h1 {
    background-image: paint(heading-color);
}
</style>

<script>
    CSS.paintWorklet.addModule('heading-color.js');
</script>
// heading-color.js
registerPaint('heading-color', class {
    static get inputProperties() { return []; }
    paint(ctx, geom, properties) {
        // 이미지의 너비와 높이에 따라 색상 선택
        const width = geom.width;
        const height = geom.height;
        const color = colorArray[(width * height) % colorArray.length];

        // 단색 이미지만 그림
        ctx.fillStyle = color;
        ctx.fillRect(0, 0, width, height);
    }
});

8.5. 예제 5: 요소 영역 밖 그리기

border-image 속성을 사용하면 요소 영역 밖에 그릴 수 있습니다.

<style>
#overdraw {
    --border-width: 10;

    border-style: solid;
    border-width: calc(var(--border-width) * 1px);

    border-image-source: paint(overdraw);
    border-image-slice: 0 fill;
    border-image-outset: calc(var(--border-width) * 1px);

    width: 200px;
    height: 200px;
}
</style>
<div id="overdraw"></div>
<script>
    CSS.paintWorklet.addModule('overdraw.js');
</script>
// overdraw.js
registerPaint('overdraw', class {
    static get inputProperties() { return ['--border-width']; }
    paint(ctx, geom, properties) {
        const borderWidth = parseInt(properties.get('--border-width'));
        ctx.shadowColor = 'rgba(0,0,0,0.25)';
        ctx.shadowBlur = borderWidth;

        ctx.fillStyle = 'rgba(255, 255, 255, 1)';
        ctx.fillRect(borderWidth,
                        borderWidth,
                        geom.width - 2 * borderWidth,
                        geom.height - 2 * borderWidth);
    }
});

9. 보안 고려사항

이 기능들에 의해 새롭게 도입된 알려진 보안 문제는 없습니다.

10. 개인정보 보호 고려사항

11. 변경 사항

2018년 8월 9일 CR 발표 이후의 변경 사항:

적합성

문서 규약

적합성 요구사항은 설명적 주장과 RFC 2119 용어의 조합으로 표현됩니다. 핵심 단어 “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, “OPTIONAL”은 이 문서의 규범적 부분에서 RFC 2119에 정의된 대로 해석되어야 합니다. 하지만 가독성을 위해, 이 단어들은 본 명세서에서 모두 대문자로 표시되지 않습니다.

이 명세서의 모든 텍스트는 명시적으로 비규범적임, 예시, 노트로 표시된 섹션을 제외하고는 규범적입니다. [RFC2119]

이 명세서의 예시는 “예를 들어”라는 말로 시작하거나 class="example"로 규범적 텍스트와 구분하여 표시됩니다. 예시:

이것은 정보 제공용 예시입니다.

정보 제공용 노트는 “Note”라는 말로 시작하며 class="note"로 규범적 텍스트와 구분됩니다. 예시:

Note, 이것은 정보 제공용 노트입니다.

권고 사항은 규범적 섹션이며 특별히 주의를 끌도록 스타일링되어 있고, <strong class="advisement">로 다른 규범적 텍스트와 구분됩니다. 예시: UA는 반드시 접근 가능한 대체 수단을 제공해야 합니다.

적합성 클래스

이 명세서의 적합성은 세 가지 적합성 클래스로 정의됩니다:

스타일 시트
CSS 스타일 시트.
렌더러
UA로, 스타일 시트의 의미를 해석하고 이를 사용하는 문서를 렌더링합니다.
저작 도구
UA로, 스타일 시트를 작성합니다.

스타일 시트가 이 명세서에 적합하려면, 이 모듈에서 정의된 구문을 사용하는 모든 문이 일반적인 CSS 문법과 이 모듈에서 정의된 각 기능의 개별 문법에 따라 유효해야 합니다.

렌더러가 이 명세서에 적합하려면, 해당 스타일 시트를 적절한 명세서에 정의된 대로 해석하는 것 외에도, 이 명세서에서 정의한 모든 기능을 올바르게 파싱하고 문서를 적절히 렌더링해야 합니다. 단, 디바이스의 한계로 인해 UA가 문서를 올바르게 렌더링하지 못하더라도 비적합으로 간주하지 않습니다. (예를 들어, UA는 흑백 모니터에서 색상을 렌더링할 필요는 없습니다.)

저작 도구가 이 명세서에 적합하려면, 이 모듈의 일반적인 CSS 문법과 각 기능의 개별 문법에 따라 구문적으로 올바른 스타일 시트를 작성하고, 이 모듈에서 설명한 스타일 시트의 모든 적합성 요구사항을 충족해야 합니다.

부분 구현

저자가 향후 호환 가능한 구문 분석 규칙을 활용해 대체 값을 지정할 수 있도록, CSS 렌더러는 반드시 사용 가능한 수준의 지원이 없는 모든 @규칙, 속성, 속성 값, 키워드 및 기타 구문 구조를 잘못된 것으로 간주하고 적절히 무시해야 합니다. 특히, 사용자 에이전트는 지원하지 않는 구성 요소 값을 선택적으로 무시하고 지원되는 값을 하나의 다중 값 속성 선언에서 처리하면 안 됩니다: 만약 어떤 값이 잘못된 것으로 간주된다면 (지원하지 않는 값은 반드시 그렇습니다), CSS는 전체 선언이 무시되어야 함을 요구합니다.

불안정 및 독점 기능의 구현

향후 안정적인 CSS 기능과 충돌을 피하기 위해, CSSWG는 최선의 방법을 따라 불안정한 기능 및 독점 확장을 구현할 것을 권장합니다.

비실험적 구현

명세서가 Candidate Recommendation 단계에 도달하면, 비실험적 구현이 가능해지며, 구현자는 명세에 따라 올바르게 구현된 CR 단계의 모든 기능에 대해 접두어 없는 구현을 공개해야 합니다.

CSS의 구현 간 상호 운용성을 확립하고 유지하기 위해, CSS 워킹 그룹은 비실험적인 CSS 렌더러가 구현 보고서(필요하다면 해당 구현 보고서에 사용된 테스트 케이스 포함)를 W3C에 제출할 것을 요청합니다. W3C에 제출된 테스트 케이스는 CSS 워킹 그룹의 검토와 수정 대상이 됩니다.

테스트 케이스 및 구현 보고서 제출에 대한 추가 정보는 CSS 워킹 그룹 웹사이트 https://www.w3.org/Style/CSS/Test/에서 확인할 수 있습니다. 문의는 public-css-testsuite@w3.org 메일링 리스트로 해야 합니다.

CR 종료 기준

이 명세서가 Proposed Recommendation으로 진행되기 위해서는, 각 기능에 대해 최소 두 개의 독립적이고 상호 운용 가능한 구현이 있어야 합니다. 각 기능은 서로 다른 제품 세트에서 구현될 수 있으며, 모든 기능이 단일 제품에서 구현될 필요는 없습니다. 이 기준의 목적상, 다음 용어를 정의합니다:

독립적
각 구현은 서로 다른 당사자가 개발해야 하며, 다른 적격 구현에서 사용된 코드를 공유, 재사용 또는 파생할 수 없습니다. 이 명세서의 구현에 영향을 주지 않는 코드 부분은 이 요구사항에서 제외됩니다.
상호 운용 가능
공식 CSS 테스트 스위트의 해당 테스트 케이스를 통과하거나, 구현이 웹 브라우저가 아닌 경우 동일한 테스트를 통과해야 합니다. 테스트 스위트의 모든 관련 테스트에는 해당 사용자 에이전트(UAs)가 상호 운용성을 주장하기 위해 동일한 방식으로 통과할 수 있는 동등한 테스트가 반드시 생성되어야 합니다. 동등한 테스트는 동료 검토를 위해 공개적으로 제공되어야 합니다.
구현
사용자 에이전트로서:
  1. 명세서를 구현합니다.
  2. 일반 공개용입니다. 구현은 출시 제품이거나 베타 버전, 프리뷰 릴리스 또는 "nightly build"와 같은 기타 공개 버전일 수 있습니다. 비출시 제품 릴리스는 기능을 최소 1개월 이상 구현하여 안정성을 입증해야 합니다.
  3. 실험적이지 않아야 합니다(즉, 테스트 스위트를 통과하기 위해 특별히 설계된 버전이 아니며, 앞으로 정상적으로 사용될 의도가 있어야 합니다).

명세서는 최소 6개월 동안 Candidate Recommendation 상태를 유지합니다.

색인

이 명세서에서 정의된 용어

참조로 정의된 용어

참고 문헌

규범적 참고 문헌

[CSS-BACKGROUNDS-3]
Bert Bos; Elika Etemad; Brad Kemper. CSS Backgrounds and Borders Module Level 3. 2021년 7월 26일. CR. URL: https://www.w3.org/TR/css-backgrounds-3/
[CSS-CASCADE-5]
Elika Etemad; Miriam Suzanne; Tab Atkins Jr.. CSS Cascading and Inheritance Level 5. 2021년 12월 3일. WD. URL: https://www.w3.org/TR/css-cascade-5/
[CSS-DISPLAY-3]
Tab Atkins Jr.; Elika Etemad. CSS Display Module Level 3. 2021년 9월 3일. CR. URL: https://www.w3.org/TR/css-display-3/
[CSS-IMAGES-3]
Tab Atkins Jr.; Elika Etemad; Lea Verou. CSS Images Module Level 3. 2020년 12월 17일. CR. URL: https://www.w3.org/TR/css-images-3/
[CSS-IMAGES-4]
Tab Atkins Jr.; Elika Etemad; Lea Verou. CSS Image Values and Replaced Content Module Level 4. 2017년 4월 13일. WD. URL: https://www.w3.org/TR/css-images-4/
[CSS-PROPERTIES-VALUES-API-1]
Tab Atkins Jr.; et al. CSS Properties and Values API Level 1. 2020년 10월 13일. WD. URL: https://www.w3.org/TR/css-properties-values-api-1/
[CSS-SYNTAX-3]
Tab Atkins Jr.; Simon Sapin. CSS Syntax Module Level 3. 2019년 7월 16일. CR. URL: https://www.w3.org/TR/css-syntax-3/
[CSS-TYPED-OM-1]
Shane Stephens; Tab Atkins Jr.; Naina Raisinghani. CSS Typed OM Level 1. 2018년 4월 10일. WD. URL: https://www.w3.org/TR/css-typed-om-1/
[CSS-UI-3]
Tantek Çelik; Florian Rivoal. CSS Basic User Interface Module Level 3 (CSS3 UI). 2018년 6월 21일. REC. URL: https://www.w3.org/TR/css-ui-3/
[CSS-VALUES-4]
Tab Atkins Jr.; Elika Etemad. CSS Values and Units Module Level 4. 2021년 10월 16일. WD. URL: https://www.w3.org/TR/css-values-4/
[CSS-VARIABLES-1]
Tab Atkins Jr.. CSS Custom Properties for Cascading Variables Module Level 1. 2021년 11월 11일. CR. URL: https://www.w3.org/TR/css-variables-1/
[CSSOM-1]
Daniel Glazman; Emilio Cobos Álvarez. CSS Object Model (CSSOM). 2021년 8월 26일. WD. URL: https://www.w3.org/TR/cssom-1/
[CSSOM-VIEW-1]
Simon Pieters. CSSOM View Module. 2016년 3월 17일. WD. URL: https://www.w3.org/TR/cssom-view-1/
[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. 1997년 3월. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/

IDL 색인

partial namespace CSS {
    [SameObject] readonly attribute Worklet paintWorklet;
};

[Global=(Worklet,PaintWorklet),Exposed=PaintWorklet]
interface PaintWorkletGlobalScope : WorkletGlobalScope {
    undefined registerPaint(DOMString name, VoidFunction paintCtor);
    readonly attribute unrestricted double devicePixelRatio;
};

dictionary PaintRenderingContext2DSettings {
    boolean alpha = true;
};

[Exposed=PaintWorklet]
interface PaintRenderingContext2D {
};
PaintRenderingContext2D includes CanvasState;
PaintRenderingContext2D includes CanvasTransform;
PaintRenderingContext2D includes CanvasCompositing;
PaintRenderingContext2D includes CanvasImageSmoothing;
PaintRenderingContext2D includes CanvasFillStrokeStyles;
PaintRenderingContext2D includes CanvasShadowStyles;
PaintRenderingContext2D includes CanvasRect;
PaintRenderingContext2D includes CanvasDrawPath;
PaintRenderingContext2D includes CanvasDrawImage;
PaintRenderingContext2D includes CanvasPathDrawingStyles;
PaintRenderingContext2D includes CanvasPath;

[Exposed=PaintWorklet]
interface PaintSize {
    readonly attribute double width;
    readonly attribute double height;
};