WithCodeMedia-1-pc
previous arrowprevious arrow
next arrownext arrow

WithCodeMedia-1-sp
previous arrowprevious arrow
next arrownext arrow

CSS Painting API完全ガイド|Houdini PaintWorklet・カスタム背景・CSS @property・アニメーション・SVG比較・Viteバンドルまで徹底解説

目次

この記事でわかること

  • CSS Houdiniの全体像とCSS Painting APIの位置づけ・ユースケース
  • PaintWorkletの定義・登録・CSSからの呼び出し手順(Hello World)
  • ドット/波線/グリッド/ノイズ/プログレスバーなど実践パターン実装
  • CSS @propertyと組み合わせたtransition/animationアニメーション連携
  • Viteでのバンドル方法・ブラウザサポート・SVGとの使い分け基準
生徒

CSSだけでは表現できない複雑な背景パターンを実装したいんですが、画像を使うと重いし……カスタム描画ができるAPIがあるんですか?

ペン博士

CSS Painting APIがまさにそれじゃ!CSSのHoudiniプロジェクトの一部で、PaintWorkletというJavaScriptのクラスを使って任意の描画処理をCSS内で呼び出せるぞ。画像ファイル不要で、CSS変数でパラメーターも動的に変えられるから非常に強力じゃ!さらに@propertyと組み合わせてアニメーションも実装できる!

結論:CSS Painting API(CSS Houdini)はPaintWorkletをJavaScriptで定義し、background-imageにpaint()で呼び出すことで、画像ファイル不要のカスタム背景パターンを実装できます。CSS変数でパラメーターをCSSから動的に制御でき、@propertyと組み合わせることでCSSアニメーションも可能です。

本記事では、CSS Painting APIの概要・CSS Houdiniのアーキテクチャ・PaintWorkletの登録方法・inputProperties/inputArguments・カスタムプロパティとの連携・ドット/波線/チェッカー/メッシュ/ノイズ背景の実装・CSS @propertyとのアニメーション連携・Viteでのバンドル方法・SVGとの使い分け・css-paint-polyfillによる互換対応・ブラウザサポート状況を完全解説します。


CSS Houdiniとは|CSSを拡張する低レベルAPI群

CSS Houdiniは、開発者がブラウザのCSSエンジンに直接アクセスするための低レベルAPIセットです。従来はブラウザ側だけが実装できたCSSの機能を、JavaScriptを使って開発者が独自に実装できるようになります。

>【CSS Houdiniの主なAPI】

1. CSS Painting API(本記事のメイン)
   → background-image, border-image等にカスタム描画を適用
   → PaintWorkletで任意のCanvas描画を定義
   → ブラウザサポート:Chrome/Edge ✅ / Firefox ⚠️ / Safari ❌

2. CSS Properties and Values API(@property)
   → CSSカスタムプロパティに型・初期値・継承を定義
   → アニメーション対応(transition/animation が効くようになる)
   → ブラウザサポート:Chrome/Edge/Safari ✅ / Firefox ✅(2023+)

3. CSS Layout API
   → display プロパティのカスタムレイアウトを実装
   → Masonry レイアウトなどを自前で実装可能
   → ブラウザサポート:Chrome ⚠️(実験的)

4. CSS Animation Worklet API
   → アニメーションをメインスレッドから切り離して滑らか化
   → ブラウザサポート:Chrome ⚠️(実験的)

5. CSS Parser API
   → CSSパース結果へのプログラムアクセス
   → ブラウザサポート:検討中

【Workletとは】
→ Service WorkerやWeb Workerと同様の分離された実行コンテキスト
→ メインスレッドとは別のスレッドで動作
→ DOM へのアクセスはできない(セキュリティのため)
→ グローバル変数は共有しない
CSS Painting APIのPaintWorkletとCSSの連携を示す図

CSS Painting APIとは|CSSから呼び出せるカスタム描画エンジン

>【CSS Painting APIの仕組み】

通常のCSS(画像を使う場合):
background-image: url('pattern.png')
→ 外部画像ファイルが必要。解像度が固定。
→ 小さいサイズではpixelが荒くなる。
→ CSSで動的に変更不可。

CSS Painting API(カスタム描画):
background-image: paint(myPattern)
→ JavaScriptのPaintWorkletで任意の描画を実行
→ ファイル不要・サイズに応じた動的描画(ベクター的)
→ CSS変数でパラメーターをCSSから渡せる
→ CSS transition/animationの値変化に合わせて再描画

【Canvas APIとの違い】
Canvas API(通常):
→ <canvas> 要素に描画(DOM要素)
→ メインスレッドで実行
→ CSS で位置・サイズを制御

CSS Painting API:
→ CSSプロパティの値として描画(DOM要素ではない)
→ ワークレットスレッドで実行(パフォーマンス良好)
→ 要素のサイズ変更に自動追従(リサイズ対応)

【ユースケース一覧】
✅ カスタム背景パターン(ドット・波・メッシュ・ハッチング・ノイズ)
✅ カスタムボーダー・アウトライン(波線・破線・ジグザグ)
✅ 動的なグラデーション(データに基づいた動的な配色)
✅ データビジュアライゼーション(簡易的なプログレスバー・ミニチャート)
✅ カスタムハイライト・テキスト装飾

基本的な使い方|Hello World 的な実装

ステップ1:PaintWorkletを定義する(worklet.js)

>// paint-worklet.js
// ※ このファイルはグローバルスコープが通常のJSと異なる
//    - document, window はアクセス不可
//    - registerPaint() でWorkletを登録

class CheckerboardPainter {
  // CSSから渡せるカスタムプロパティを定義(inputPropertiesに指定)
  static get inputProperties() {
    return ['--checkerboard-size', '--checkerboard-color']
  }

  // paint() が実際の描画処理
  // ctx: PaintRenderingContext2D(Canvas2D互換)
  // geometry: 描画領域のサイズ(geometry.width, geometry.height)
  // properties: inputPropertiesで指定したCSS変数の値
  paint(ctx, geometry, properties) {
    // CSS変数を取得
    const rawSize = properties.get('--checkerboard-size')
    const size = rawSize ? parseInt(rawSize.toString()) : 20
    const rawColor = properties.get('--checkerboard-color')
    const color = rawColor ? rawColor.toString().trim() : '#cccccc'

    const cols = Math.ceil(geometry.width / size) + 1
    const rows = Math.ceil(geometry.height / size) + 1

    // 市松模様:行列の合計が偶数のセルを塗る
    for (let row = 0; row <= rows; row++) {
      for (let col = 0; col <= cols; col++) {
        if ((row + col) % 2 === 0) {
          ctx.fillStyle = color
          ctx.fillRect(col * size, row * size, size, size)
        }
      }
    }
  }
}

// ワークレットに名前を付けて登録(paint()から呼び出すときの名前)
registerPaint('checkerboard', CheckerboardPainter)

ステップ2:HTMLからWorkletを読み込む

><!-- index.html -->
<script>
  // CSS Painting APIのサポート確認
  if ('paintWorklet' in CSS) {
    // PaintWorkletモジュールを非同期で読み込む
    CSS.paintWorklet.addModule('./paint-worklet.js')
      .then(() => console.log('PaintWorklet loaded'))
      .catch(err => console.error('Failed to load worklet:', err))
  } else {
    console.warn('CSS Painting API is not supported in this browser')
    // Polyfillを読み込む(後述)
  }
</script>

ステップ3:CSSからpaint()を呼び出す

>/* style.css */

.checkerboard-bg {
  /* CSS変数でパラメーターを渡す */
  --checkerboard-size: 30;
  --checkerboard-color: #3B82F620;  /* 半透明の青 */

  /* paint() でWorkletを呼び出す(registerPaintの名前と一致) */
  background-image: paint(checkerboard);
  background-color: #ffffff;  /* フォールバック(未対応ブラウザ用) */

  width: 100%;
  height: 300px;
  border-radius: 8px;
}

/* ホバーで色を変える(CSS変数の変更で自動的にpaintが再実行される!) */
.checkerboard-bg:hover {
  --checkerboard-color: #EF444420;
  --checkerboard-size: 15;
  transition: none;  /* CSS変数はtransitionできない(後述の@propertyで解決)*/
}

実践パターン集|よく使われる背景パターンの実装

パターン1:ドット背景

>// dot-pattern-worklet.js

class DotPatternPainter {
  static get inputProperties() {
    return ['--dot-size', '--dot-color', '--dot-gap', '--dot-opacity']
  }

  paint(ctx, geometry, properties) {
    const size = parseFloat(properties.get('--dot-size')) || 4
    const color = properties.get('--dot-color').toString().trim() || '#94A3B8'
    const gap = parseFloat(properties.get('--dot-gap')) || 20
    const opacity = parseFloat(properties.get('--dot-opacity')) || 1

    ctx.globalAlpha = Math.max(0, Math.min(1, opacity))

    for (let y = gap / 2; y < geometry.height; y += gap) {
      for (let x = gap / 2; x < geometry.width; x += gap) {
        ctx.beginPath()
        ctx.arc(x, y, size / 2, 0, 2 * Math.PI)
        ctx.fillStyle = color
        ctx.fill()
      }
    }
  }
}

registerPaint('dot-pattern', DotPatternPainter)

/* CSS での使い方 */
/*
.hero-section {
  --dot-size: 3;
  --dot-color: #6366F1;
  --dot-gap: 24;
  --dot-opacity: 0.4;
  background-color: #0f0f23;
  background-image: paint(dot-pattern);
}
*/

パターン2:波線ボーダー

>// wavy-border-worklet.js

class WavyBorderPainter {
  static get inputProperties() {
    return [
      '--wave-amplitude',
      '--wave-frequency',
      '--border-color',
      '--border-width',
      '--wave-phase',  // アニメーション用の位相
    ]
  }

  paint(ctx, geometry, properties) {
    const amplitude = parseFloat(properties.get('--wave-amplitude')) || 5
    const frequency = parseFloat(properties.get('--wave-frequency')) || 0.05
    const color = properties.get('--border-color').toString().trim() || '#3B82F6'
    const lineWidth = parseFloat(properties.get('--border-width')) || 2
    const phase = parseFloat(properties.get('--wave-phase')) || 0

    const drawWavyLine = (y) => {
      ctx.beginPath()
      ctx.moveTo(0, y)
      for (let x = 0; x <= geometry.width; x++) {
        const waveY = y + Math.sin((x * frequency) + phase) * amplitude
        ctx.lineTo(x, waveY)
      }
      ctx.strokeStyle = color
      ctx.lineWidth = lineWidth
      ctx.stroke()
    }

    // 上辺に波線ボーダーを描く
    drawWavyLine(amplitude + lineWidth)
    // 下辺に波線ボーダーを描く
    drawWavyLine(geometry.height - amplitude - lineWidth)
  }
}

registerPaint('wavy-border', WavyBorderPainter)

パターン3:グリッド背景(建築設計ドラフト風)

>// grid-pattern-worklet.js

class GridPatternPainter {
  static get inputProperties() {
    return [
      '--grid-size',
      '--grid-color',
      '--grid-major-every',  // 何グリッドごとに太線を引くか
      '--grid-major-color',
    ]
  }

  paint(ctx, geometry, properties) {
    const gridSize = parseFloat(properties.get('--grid-size')) || 16
    const gridColor = properties.get('--grid-color').toString().trim() || '#e2e8f0'
    const majorEvery = parseInt(properties.get('--grid-major-every')) || 4
    const majorColor = properties.get('--grid-major-color').toString().trim() || '#cbd5e1'

    // 縦線を描く
    for (let x = 0; x <= geometry.width; x += gridSize) {
      const isMajor = (x / gridSize) % majorEvery === 0
      ctx.beginPath()
      ctx.moveTo(x, 0)
      ctx.lineTo(x, geometry.height)
      ctx.strokeStyle = isMajor ? majorColor : gridColor
      ctx.lineWidth = isMajor ? 1.5 : 0.5
      ctx.stroke()
    }

    // 横線を描く
    for (let y = 0; y <= geometry.height; y += gridSize) {
      const isMajor = (y / gridSize) % majorEvery === 0
      ctx.beginPath()
      ctx.moveTo(0, y)
      ctx.lineTo(geometry.width, y)
      ctx.strokeStyle = isMajor ? majorColor : gridColor
      ctx.lineWidth = isMajor ? 1.5 : 0.5
      ctx.stroke()
    }
  }
}

registerPaint('grid-pattern', GridPatternPainter)

パターン4:ノイズ・テクスチャ背景

>// noise-texture-worklet.js
// シンプルなValue Noiseを実装(Perlinノイズの簡易版)

class NoiseTexturePainter {
  static get inputProperties() {
    return ['--noise-scale', '--noise-color', '--noise-opacity', '--noise-seed']
  }

  // シンプルな疑似ランダム関数(シード値で再現性あり)
  random(x, y, seed) {
    const n = Math.sin(x * 127.1 + y * 311.7 + seed * 74.3) * 43758.5453
    return n - Math.floor(n)
  }

  // バイリニア補間
  smoothNoise(x, y, seed) {
    const ix = Math.floor(x)
    const iy = Math.floor(y)
    const fx = x - ix
    const fy = y - iy
    const ux = fx * fx * (3 - 2 * fx)  // Hermite smoothstep
    const uy = fy * fy * (3 - 2 * fy)

    const a = this.random(ix, iy, seed)
    const b = this.random(ix + 1, iy, seed)
    const c = this.random(ix, iy + 1, seed)
    const d = this.random(ix + 1, iy + 1, seed)

    return a + (b - a) * ux + (c - a) * uy + (d - a + b + c - a - b - c) * ux * uy
  }

  paint(ctx, geometry, properties) {
    const scale = parseFloat(properties.get('--noise-scale')) || 4
    const colorStr = properties.get('--noise-color').toString().trim() || '0,0,0'
    const opacity = parseFloat(properties.get('--noise-opacity')) || 0.15
    const seed = parseFloat(properties.get('--noise-seed')) || 42

    // ピクセルバッファを直接操作(高速)
    const imageData = ctx.createImageData(geometry.width, geometry.height)
    const data = imageData.data

    const [r, g, b] = colorStr.split(',').map(v => parseInt(v.trim()))

    for (let y = 0; y < geometry.height; y++) {
      for (let x = 0; x < geometry.width; x++) {
        const noiseValue = this.smoothNoise(x / scale, y / scale, seed)
        const alpha = Math.floor(noiseValue * 255 * opacity)

        const idx = (y * geometry.width + x) * 4
        data[idx] = r ?? 128
        data[idx + 1] = g ?? 128
        data[idx + 2] = b ?? 128
        data[idx + 3] = alpha
      }
    }

    ctx.putImageData(imageData, 0, 0)
  }
}

registerPaint('noise-texture', NoiseTexturePainter)

パターン5:カスタムプログレスバー

>// progress-bar-worklet.js
// CSSアニメーションでプログレスを動かせるカスタムプログレスバー

class ProgressBarPainter {
  static get inputProperties() {
    return [
      '--progress',       // 0〜1の値
      '--bar-color',
      '--track-color',
      '--bar-radius',
      '--show-gradient',  // グラデーションを使うか
    ]
  }

  paint(ctx, geometry, properties) {
    const progress = Math.max(0, Math.min(1,
      parseFloat(properties.get('--progress')) || 0
    ))
    const barColor = properties.get('--bar-color').toString().trim() || '#3B82F6'
    const trackColor = properties.get('--track-color').toString().trim() || '#E2E8F0'
    const barRadius = parseFloat(properties.get('--bar-radius')) || geometry.height / 2
    const showGradient = properties.get('--show-gradient').toString().trim() === '1'

    const { width, height } = geometry

    // トラック(背景)を描画
    ctx.beginPath()
    ctx.roundRect(0, 0, width, height, barRadius)
    ctx.fillStyle = trackColor
    ctx.fill()

    // プログレスバーを描画
    const barWidth = width * progress
    if (barWidth > 0) {
      ctx.beginPath()
      ctx.roundRect(0, 0, barWidth, height, barRadius)

      if (showGradient && barWidth > 0) {
        const gradient = ctx.createLinearGradient(0, 0, barWidth, 0)
        gradient.addColorStop(0, barColor + '99')
        gradient.addColorStop(1, barColor)
        ctx.fillStyle = gradient
      } else {
        ctx.fillStyle = barColor
      }
      ctx.fill()
    }
  }
}

registerPaint('progress-bar', ProgressBarPainter)

CSS @property とのアニメーション連携

CSS変数(--my-value)は通常、transitionanimationによるトゥイーンアニメーションに対応していません。CSS @property(CSS Houdini Properties and Values API)を使って型を定義することで、CSS変数もアニメーション可能になります。

>/* CSS @property でアニメーション可能なカスタムプロパティを定義 */

@property --progress {
  syntax: '<number>';        /* 数値型として定義 */
  inherits: false;           /* 継承しない */
  initial-value: 0;          /* 初期値 */
}

@property --wave-phase {
  syntax: '<number>';
  inherits: false;
  initial-value: 0;
}

@property --noise-opacity {
  syntax: '<number>';
  inherits: false;
  initial-value: 0;
}

/* プログレスバーのアニメーション */
.progress-bar {
  --progress: 0;
  --bar-color: #3B82F6;
  --track-color: #E2E8F0;
  --bar-radius: 4;
  --show-gradient: 1;

  background-image: paint(progress-bar);
  height: 8px;
  width: 100%;

  /* @propertyで型定義された変数はアニメーション可能! */
  transition: --progress 1.5s ease-in-out;
}

.progress-bar.loaded {
  --progress: 0.75;  /* 0% → 75%に滑らかにアニメーション */
}

/* 波線ボーダーのアニメーション */
@keyframes wave-animation {
  from { --wave-phase: 0; }
  to   { --wave-phase: 6.28318; }  /* 2π(1周期) */
}

.wavy-card {
  --wave-amplitude: 4;
  --wave-frequency: 0.03;
  --border-color: #8B5CF6;
  --border-width: 2;
  --wave-phase: 0;

  background-image: paint(wavy-border);
  background-color: white;
  padding: 2rem;
  border-radius: 12px;

  animation: wave-animation 2s linear infinite;
}

/* フォールバック(CSS Painting API非対応ブラウザ) */
@supports not (background: paint(x)) {
  .progress-bar {
    background-color: #E2E8F0;
  }
  .progress-bar::before {
    content: '';
    display: block;
    height: 100%;
    background-color: #3B82F6;
    width: calc(var(--progress, 0) * 100%);
    border-radius: 4px;
    transition: width 1.5s ease-in-out;
  }
}

Viteを使ったPaintWorkletのバンドル方法

>// vite.config.ts でのPaintWorklet対応

import { defineConfig } from 'vite'

export default defineConfig({
  // Workletファイルは通常のモジュールと別扱いが必要
  build: {
    rollupOptions: {
      input: {
        main: 'index.html',
        // Workletは別エントリーとしてビルド
        paintWorklet: 'src/worklets/paint-worklet.js',
      },
      output: {
        entryFileNames: (chunkInfo) => {
          if (chunkInfo.name === 'paintWorklet') {
            return 'worklets/[name].js'  // ハッシュなし(URLが変わると困る)
          }
          return 'assets/[name]-[hash].js'
        },
      },
    },
  },
})

// 型付きWorkletの読み込み(main.ts)
async function loadPaintWorklets() {
  if (!('paintWorklet' in CSS)) {
    // Polyfill を動的にインポート
    const { default: polyfill } = await import('css-paint-polyfill')
    await polyfill
  }

  // 本番環境ではビルドされたURLを使用
  const workletUrl = import.meta.env.PROD
    ? '/worklets/paintWorklet.js'
    : new URL('./worklets/paint-worklet.js', import.meta.url).href

  await CSS.paintWorklet.addModule(workletUrl)
}

loadPaintWorklets().catch(console.error)

ブラウザサポートとPolyfill対応

ブラウザCSS Painting API@propertyLayout API
Chrome 65+✅(85+)⚠️ 実験的
Edge 79+✅(85+)⚠️ 実験的
Safari 16.4+✅(16.4+)
Firefox 128+⚠️ 実験的✅(128+)
>/* プログレッシブエンハンスメントのパターン */

/* 1. フォールバックを先に書く */
.element {
  background-color: #f3f4f6;  /* 全ブラウザで表示されるフォールバック */
  background-image: url('pattern-fallback.svg');  /* SVGフォールバック */
}

/* 2. @supports でPainting APIが利用可能なときのみ上書き */
@supports (background: paint(my-painter)) {
  .element {
    background-image: paint(my-painter);  /* 対応ブラウザのみ */
  }
}

/* JavaScriptでの対応確認 */
if ('paintWorklet' in CSS) {
  CSS.paintWorklet.addModule('./my-worklet.js')
  document.documentElement.classList.add('css-paint-supported')
} else {
  // css-paint-polyfill で非対応ブラウザをサポート
  import('css-paint-polyfill').then(() => {
    CSS.paintWorklet.addModule('./my-worklet.js')
  })
}

/* CSS側でのフォールバック管理 */
.has-pattern {
  background-color: #1e293b;  /* デフォルト(未サポート・JS無効) */
}

.css-paint-supported .has-pattern {
  background-image: paint(dot-pattern);  /* JS有効・API対応時のみ */
}

CSS Painting API vs SVG背景|使い分けの判断基準

比較項目CSS Painting APISVGインライン/data URICanvas要素
ブラウザサポートChrome/Edge(部分的)全ブラウザ全ブラウザ
CSS変数連携◎ ネイティブ対応△ 一部対応(fill=”var()”)❌ 別途JSが必要
パフォーマンス◎ ワークレットスレッド○ ブラウザ最適化△ メインスレッド
動的パラメーター◎ CSS変数で動的変更・再描画△ SVGの再生成が必要◎ JS直接操作
アニメーション◎ @propertyと組み合わせ○ CSS animation◎ requestAnimationFrame
複雑な描画△ 比較的シンプルな描画向け◎ 任意のSVG表現◎ ピクセル単位の操作
>【使い分けの判断基準】

CSS Painting API を選ぶ場面:
✅ CSSプロパティのbackground-imageとして自然に馴染ませたい
✅ 要素のリサイズに自動追従する背景パターンを作りたい
✅ CSS変数で動的にパラメーターを変更したい
✅ CSSアニメーション(@property)と連携させたい

SVG を選ぶ場面:
✅ 全ブラウザでの完全なサポートが必要
✅ 複雑なベクター表現(曲線・テキスト・フィルタ)
✅ アクセシビリティ(title/desc属性)が必要
✅ SEOインデックスが必要なグラフィック

Canvas APIを選ぶ場面:
✅ ユーザーインタラクションが必要(クリック・ドラッグ)
✅ フレームごとのアニメーション(ゲーム・3D)
✅ ピクセル単位の処理(画像編集)

よくある質問(FAQ)

Q1. PaintWorkletでimportを使えますか?

A. PaintWorkletのコンテキストでは import ステートメントは使えません(Workletは独自の制限されたグローバルスコープを持ちます)。外部モジュールを使いたい場合は、ユーティリティ関数をWorkletファイルに直接コピーするか、Viteのビルドを使ってバンドルする必要があります。Viteでは import.meta.url を使ってWorkletファイルを別エントリーとしてビルドし、バンドルされたファイルを CSS.paintWorklet.addModule() で読み込む方法が現実的です。

Q2. inputPropertiesとinputArgumentsの違いは何ですか?

A. inputPropertiesはCSSカスタムプロパティ(--my-var)の変更を監視してWorkletに渡します。inputArgumentspaint(painter, arg1, arg2) のようにpaint()関数に直接引数を渡す方法です。inputPropertiesはCSS側で値を管理できて柔軟ですが、inputArgumentsはよりシンプルに値を渡せます。現在のブラウザ実装ではinputPropertiesの方がサポートが安定しているため、inputPropertiesの使用を推奨します。

Q3. PaintWorkletのデバッグ方法は?

A. PaintWorkletは通常のJSと異なるコンテキストで動作するため、DevToolsでのデバッグが少し特殊です。Chrome DevToolsでは「Sources」タブの「Threads」セクションに「paint-worklet.js」が表示され、ブレークポイントを設定できます。console.log() はWorklet内でも使用でき、「Console」タブに出力されます。まずconsole.logで中間値を確認しながら開発することを推奨します。

Q4. Safari非対応でも本番環境に使えますか?

A. @supports (background: paint(x)) {} を使ったプログレッシブエンハンスメントで実装すれば、SafariではCSSフォールバック(通常の背景色や画像)が表示され、Chrome/EdgeではPainting APIの豊かな表現が適用されます。またcss-paint-polyfill(〜8KB)を使えばSafariを含む多くのブラウザでPainting APIをシミュレートできます。装飾的な背景パターンであれば、Safariで少し違う見た目になってもユーザー体験には大きな影響がないため、本番環境での利用は十分現実的です。

Q5. PaintWorkletはリサイズ時に自動で再描画されますか?

A. はい、自動的に再描画されます。CSSのbackground-imageとして機能するため、要素のサイズが変わるたびにpaint()メソッドが新しいgeometry(width/height)で再呼び出しされます。これはSVG背景やCanvas要素と比べた大きなメリットで、レスポンシブレイアウトでもパターンのサイズや密度が適切に自動調整されます。


まとめ

  • CSS Houdini:ブラウザのCSSエンジンに直接アクセスするAPI群。Painting API・@property・Layout API等を含む。
  • CSS Painting API:PaintWorkletをJavaScriptで定義し、CSSから paint() で呼び出してカスタム描画を実現。
  • 実装の流れ:registerPaint()でWorkletを定義 → CSS.paintWorklet.addModule()で読み込み → CSSでbackground-image: paint(name)で呼び出す。
  • inputProperties:CSS変数をWorkletに渡すインターフェース。CSS変数の変更で自動的に再描画される。
  • @property:CSS変数に型を付与することでtransition/animationが効くようになる。Painting APIとの連携でアニメーションが実現。
  • 実装パターン:ドット背景・波線ボーダー・グリッド・ノイズ・プログレスバーなど多様なパターンが実装可能。
  • Viteバンドル:Workletファイルは別エントリーとして出力し、ハッシュなしのURLで読み込む。
  • ブラウザサポート:Chrome/Edge対応済み。Safariは非対応のためプログレッシブエンハンスメントが必須。
  • SVGとの使い分け:全ブラウザ対応・複雑な表現はSVG。CSS変数連携・要素追従・アニメーションはPainting API。

CSS Painting APIは「CSSとJavaScriptの境界を溶かす」Houdiniの核心機能です。画像ファイルなしでインタラクティブな背景パターンが実装でき、CSS変数・@property・CSSアニメーションと組み合わせることで非常に豊かな表現が可能になります。Chromeのシェアが高いBtoB・開発者向けサービスには特に効果的です。


WithCodeを体験できる初級コース公開中!

初級コース(¥49,800)が完全無料に!

  • 期間:1週間
  • 学習内容:
    ロードマップ/基礎知識/環境構築/HTML/CSS/LP・ポートフォリオ作成
    正しい学習方法で「確かな成長」を実感できるカリキュラム。

副業・フリーランスが主流になっている今こそ、自らのスキルで稼げる人材を目指してみませんか?

未経験でも心配することはありません。初級コースを受講される方の大多数はプログラミング未経験です。まずは無料カウンセリングで、悩みや不安をお聞かせください!

この記事を書いた人

WithCode(ウィズコード)は「目指すなら稼げる人材」をビジョンに、累計400名以上のフリーランスを輩出してきた超実践型プログラミングスクールです。150社以上の実案件支援を特徴にWeb制作・Webデザインなどの役立つ情報を現場のノウハウに基づいて発信していきます。

– service –WithGroupの運営サービス

  • WithCode
    - ウィズコード -

    スクール

    「未経験」から
    現場で通用する
    スキルを身に付けよう!

    詳細はこちら
  • WithFree
    - ウィズフリ -

    実案件サポート

    制作会社のサポート下で
    実務経験を積んでいこう!

    詳細はこちら
  • WithCareer
    - ウィズキャリ -

    就転職サポート

    大手エージェントのサポート下で
    キャリアアップを目指そう!

    詳細はこちら

公式サイト より
今すぐ
無料カウンセリング
予約!

目次