mcpField
D
dither-background

dither-background

github.com/mengtoデザイン・UI7.1k1.5k 回インストール3 日前に更新
リポジトリ

dither-background について

ダークな画面の背景向けに、モノクロのディザ背景を作るガイドです。見える四角いピクセルと順序付きパターン、ゆっくり漂う有機的な形状を重視します。

主な機能

  1. 014×4 Bayer風の順序付きディザ
  2. 02黒に近い色とグレースケール
  3. 03四角いピクセルと有機的な波形
  4. 04周辺の明るさを調整するビネット
  5. 05Canvasの動きと性能調整

活用シーン

  1. 01ダークな画面に背景レイヤーを追加
  2. 02ヒーロー要素やデータ表示を引き立てる
  3. 03モノクロのプロシージャルテクスチャ制作

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/mengto/skills --skill dither-background

インストール後、Claude Code で/dither-backgroundを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

demo/
index.html
PROMPT.md
source.json

詳細

ディザ背景

使用する場面

  • 暗いインターフェースに、雰囲気のあるモノクロの背景レイヤーが必要な場合。
  • 拡大された四角いピクセルと、目に見える組織的ディザリングを表現したい場合。
  • カラフルなグラデーションを使わず、有機的な波や雲のような塊、またはプロシージャルな奥行きを表現したい場合。
  • 枠付きの UI、ヒーローコンテンツ、データオーバーレイを背景から引き立てたい場合。

ビジュアルの目標

  • ほぼ黒のベースに、チャコールの中間調、柔らかなグレーの広がり、ときおり白いハイライトを加えます。
  • 小さなフィルムグレインではなく、はっきり見える四角いピクセルセルを使います。
  • 4x4 のバイヤー型ディザパターン、または同等の組織的な閾値処理を使います。
  • ランダムなテレビノイズではなく、広がりのある有機的な波や雲のような塊にします。
  • ビネットをかけ、明るい塊を中央または中心からずらした位置に配置します。

HTML と CSS

<canvas class="dither-background" data-dither-background></canvas>
.dither-background {
  position: fixed;
  inset: 0;
  z-index: 0;
  width: 100vw;
  height: 100vh;
  background: #030303;
  pointer-events: none;
}

.page-content {
  position: relative;
  z-index: 1;
}

Canvas のレシピ

動きやプロシージャルな奥行きが必要な場合は、実際の canvas を使います。

const BAYER_4X4 = [
   0,  8,  2, 10,
  12,  4, 14,  6,
   3, 11,  1,  9,
  15,  7, 13,  5,
].map((value) => (value + 0.5) / 16);

function smoothstep(edge0, edge1, value) {
  const t = Math.max(0, Math.min(1, (value - edge0) / (edge1 - edge0)));
  return t * t * (3 - 2 * t);
}

function noise2(x, y) {
  const value = Math.sin(x * 127.1 + y * 311.7) * 43758.5453123;
  return value - Math.floor(value);
}

function valueNoise(x, y) {
  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);
  const uy = fy * fy * (3 - 2 * fy);

  const a = noise2(ix, iy);
  const b = noise2(ix + 1, iy);
  const c = noise2(ix, iy + 1);
  const d = noise2(ix + 1, iy + 1);
  return (
    a * (1 - ux) * (1 - uy) +
    b * ux * (1 - uy) +
    c * (1 - ux) * uy +
    d * ux * uy
  );
}

function fbm(x, y) {
  let value = 0;
  let amplitude = 0.5;
  let frequency = 1;

  for (let octave = 0; octave < 4; octave++) {
    value += valueNoise(x * frequency, y * frequency) * amplitude;
    frequency *= 2.02;
    amplitude *= 0.5;
  }

  return value;
}

function initDitherBackground(canvas, options = {}) {
  if (!canvas) return () => {};

  const ctx = canvas.getContext("2d", { alpha: false });
  if (!ctx) return () => {};

  const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
  const cell = options.cellSize || 7;
  const maxDpr = options.maxDpr || 1.5;
  let width = 1;
  let height = 1;
  let cols = 1;
  let rows = 1;
  let rafId = 0;

  const palette = options.palette || [
    [3, 3, 3],
    [16, 16, 17],
    [34, 35, 37],
    [74, 75, 78],
    [168, 169, 171],
    [236, 236, 232],
  ];

  function resize() {
    const dpr = Math.min(window.devicePixelRatio || 1, maxDpr);
    width = Math.max(1, window.innerWidth);
    height = Math.max(1, window.innerHeight);
    canvas.width = Math.floor(width * dpr);
    canvas.height = Math.floor(height * dpr);
    canvas.style.width = `${width}px`;
    canvas.style.height = `${height}px`;
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    cols = Math.ceil(width / cell);
    rows = Math.ceil(height / cell);
  }

  function sampleField(x, y, time) {
    const nx = (x / cols - 0.5) * 2;
    const ny = (y / rows - 0.5) * 2;
    const distance = Math.sqrt(nx * nx * 0.84 + ny * ny * 1.28);
    const vignette = 1 - smoothstep(0.18, 1.15, distance);
    const drift = reduceMotion ? 0 : time * 0.018;

    const wave =
      Math.sin(nx * 2.8 + ny * 1.2 + drift) * 0.18 +
      Math.sin(nx * -1.4 + ny * 3.8 - drift * 0.8) * 0.14;
    const cloud = fbm(nx * 1.35 + drift * 0.16, ny * 1.35 - drift * 0.08);
    const ridge = smoothstep(0.48, 0.92, cloud + wave);
    const offAxisMass = smoothstep(0.98, 0.18, Math.hypot(nx + 0.22, ny - 0.08));

    return Math.max(0, Math.min(1, ridge * vignette * 0.92 + offAxisMass * 0.18));
  }

  function render(time = 0) {
    const seconds = time * 0.001;
    ctx.fillStyle = "rgb(3,3,3)";
    ctx.fillRect(0, 0, width, height);

    for (let y = 0; y < rows; y++) {
      for (let x = 0; x < cols; x++) {
        const threshold = BAYER_4X4[(y % 4) * 4 + (x % 4)];
        const brightness = sampleField(x, y, seconds);
        const stepped = Math.floor(Math.max(0, Math.min(0.999, brightness + threshold * 0.18)) * palette.length);
        const color = palette[Math.min(palette.length - 1, stepped)];
        ctx.fillStyle = `rgb(${color[0]},${color[1]},${color[2]})`;
        ctx.fillRect(x * cell, y * cell, cell, cell);
      }
    }

    if (!reduceMotion) rafId = requestAnimationFrame(render);
  }

  function handleResize() {
    cancelAnimationFrame(rafId);
    resize();
    render();
  }

  resize();
  render();
  window.addEventListener("resize", handleResize);

  return () => {
    cancelAnimationFrame(rafId);
    window.removeEventListener("resize", handleResize);
  };
}

const cleanupDither = initDitherBackground(
  document.querySelector("[data-dither-background]"),
  {
    cellSize: 7,
    maxDpr: 1.5,
  }
);

調整項目

  • セルサイズ:5px-10px。セルを大きくすると、バイヤーマトリクスがよりはっきり見えます。
  • パレット:ほぼ黒、チャコール、柔らかなグレー、ごくまれな白のハイライトのみ。
  • 形状:wave、cloud、ridge、offAxisMass を調整して、広がりのある塊を作ります。
  • ビネット:前景の可読性を高める必要がある場合は、端に向かう減衰を強めます。
  • 動き:ドリフトはゆっくりにします。時間倍率は小さくし、ちらつきは避けます。
  • パフォーマンス:フィールドを単純化する前に、cellSize を大きくするか、maxDpr に上限を設定します。

構図に関する注意

  • インターフェースの背後に canvas を配置し、pointer-events: none を設定します。
  • 枠付きの UI、ヒーローコピー、データオーバーレイの背景に雰囲気を加える用途で使います。
  • 前景のコントラストを適切に調整し、タイポグラフィをすっきり保ちます。
  • ひとつの主な明るい塊で構図を定め、画面全体を均一に明るくしないようにします。

避けること

  • 虹色のグラデーション、カラフルなノイズ、ディザ構造のない柔らかなぼかしの塊。
  • 四角いマトリクスが見えなくなるほど細かいグレインや、静止画のノイズのような斑点。
  • 前景の文字と競合する、画面全体を覆う明るいホワイトノイズ。
  • ドリフトではなくちらつきを生む、フレームごとにランダムなノイズ。
  • ビューポート全体を、同じ明るさのセルで覆うこと。

簡単な確認項目

  • 通常の視距離から、四角いバイヤーパターンが見えます。
  • フィールドが、広がりのある有機的な波や雲のような塊を形成しています。
  • パレットはモノクロで控えめです。
  • 端に向かってほぼ黒に沈んでいます。
  • 重いオーバーレイを使わなくても、前景の UI が読みやすい状態です。

コメント