Back

ウェブサイトにシンプルな雪の降るエフェクトを追加する方法

ウェブサイトにシンプルな雪の降るエフェクトを追加する方法

季節感のあるホリデーウェブサイトアニメーションは訪問者を楽しませることができますが、ほとんどのチュートリアルは本番環境で重要なことを無視しています。それは、パフォーマンス、アクセシビリティ、そしてユーザーエクスペリエンスです。装飾的な背景アニメーションがCore Web Vitalsを悪化させたり、モーション軽減を好むユーザーを困らせたりすることは避けたいものです。

このガイドでは、ユーザー設定を尊重し、非表示時に一時停止し、邪魔にならない軽量なcanvas雪降りエフェクトの構築方法を紹介します。また、よりシンプルなCSS雪降りエフェクトが適している場合についても学びます。

重要なポイント

  • 少数のパーティクルを超えると、canvasはDOMベースのアプローチよりも確実にスケールします
  • ユーザーのアクセシビリティ設定を尊重するため、常にprefers-reduced-motionに対応してください
  • Page Visibility APIを使用してバックグラウンドタブでアニメーションを一時停止し、リソースを節約してください
  • 純粋なCSS雪降りは最小限の実装(5〜10個の雪片)には有効ですが、スケールしません

JavaScript雪降りアニメーションにCanvasを使う理由

DOMベースのアプローチでは、各雪片に対して個別の要素を作成します。これは少数のパーティクルには有効ですが、数十個や数百個にスケールすると、継続的なDOM操作、レイアウトの再計算、要素の作成と削除によるメモリ圧迫が発生します。

canvas雪降りエフェクトは、すべてを単一の要素に描画します。レンダリングループを制御し、パーティクルの状態をプレーンな配列で管理し、DOMのオーバーヘッドを完全に回避できます。少数のパーティクルを超えてスケールしたり、よりスムーズなモーションが必要になったりすると、canvasがより予測可能なデフォルトの選択肢になります。

理解すべきトレードオフ:

  • CanvasにはJavaScriptが必要です—JSがなければ雪は降りません
  • canvas内のテキストはスクリーンリーダーからアクセスできません(純粋に装飾的なエフェクトには問題ありません)
  • CSSアニメーションは「無料」ではありません—依然としてCPU/GPUリソースを消費します

Canvas要素のセットアップ

CSSでcanvasをコンテンツの背後に配置します:

#snowfall {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: -1;
}

pointer-events: noneルールにより、canvasがスクロール、クリック、その他のユーザーインタラクションを妨げないことを保証します。

<canvas id="snowfall" aria-hidden="true"></canvas>

aria-hidden="true"を追加することで、この純粋に装飾的な要素を支援技術に無視するよう指示します。

アニメーションループの構築

適切なガードレールを備えた最小限の実装は以下の通りです:

const canvas = document.getElementById('snowfall');
const ctx = canvas.getContext('2d');
let flakes = [];
let animationId = null;

function resize() {
  const dpr = window.devicePixelRatio || 1;
  canvas.width = window.innerWidth * dpr;
  canvas.height = window.innerHeight * dpr;
  ctx.setTransform(1, 0, 0, 1, 0, 0);
  ctx.scale(dpr, dpr);
}

function createFlake() {
  return {
    x: Math.random() * window.innerWidth,
    y: -10,
    radius: Math.random() * 3 + 1,
    speed: Math.random() * 1 + 0.5,
    opacity: Math.random() * 0.6 + 0.4
  };
}

function update() {
  ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
  
  if (flakes.length < 80 && Math.random() > 0.95) {
    flakes.push(createFlake());
  }
  
  flakes = flakes.filter(f => {
    f.y += f.speed;
    ctx.beginPath();
    ctx.arc(f.x, f.y, f.radius, 0, Math.PI * 2);
    ctx.fillStyle = `rgba(255, 255, 255, ${f.opacity})`;
    ctx.fill();
    return f.y < window.innerHeight + 10;
  });
  
  animationId = requestAnimationFrame(update);
}

resize();
window.addEventListener('resize', resize);

これは、devicePixelRatioに合わせてcanvasバッファをスケーリングすることで、高DPIスクリーンを正しく処理します。ctx.setTransform(1, 0, 0, 1, 0, 0)呼び出しは、新しいスケールを適用する前に変換マトリックスをリセットし、ウィンドウのリサイズ時の累積スケーリングを防ぎます。

必須のパフォーマンスとアクセシビリティのガードレール

ユーザー設定の尊重

prefers-reduced-motionを設定したユーザーは、アニメーションを減らすことを明示的に要求しています。それを尊重しましょう:

const reducedMotionQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
let prefersReduced = reducedMotionQuery.matches;

reducedMotionQuery.addEventListener('change', (e) => {
  prefersReduced = e.matches;
  if (prefersReduced) {
    cancelAnimationFrame(animationId);
    ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
  } else if (!document.hidden) {
    update();
  }
});

if (!prefersReduced) {
  update();
}

この実装は、ユーザーのモーション設定の変更をリッスンし、ページが開いている間に設定が変更された場合にアニメーションが動的に応答できるようにします。

非表示時の一時停止

バックグラウンドタブでアニメーションを実行すると、バッテリーとCPUを無駄に消費します。Page Visibility APIがこれを解決します:

document.addEventListener('visibilitychange', () => {
  if (document.hidden) {
    cancelAnimationFrame(animationId);
  } else if (!prefersReduced) {
    update();
  }
});

CSS雪降りエフェクトが適している場合

非常に小規模な実装の場合—おそらくヒーローセクションに5〜10個の雪片—純粋なCSSアプローチはJavaScriptを完全に回避できます:

.snowflake {
  position: absolute;
  color: white;
  animation: fall 8s linear infinite;
}

@keyframes fall {
  to { transform: translateY(100vh); }
}

これは最小限の装飾的なユースケースには有効ですが、スケールしません。各要素は依然として合成をトリガーし、密度と動作のプログラム的な制御が失われます。

カスタマイズオプション

サイトの美観に合わせてこれらの値を調整してください:

  • 密度: 80の上限と0.95の生成閾値を変更
  • 速度範囲: Math.random() * 1 + 0.5の計算を変更
  • サイズ: 半径の計算を調整
  • スコープ: window.innerWidth/Heightの代わりに特定のコンテナをターゲットに

まとめ

ホリデーウェブサイトアニメーションは、体験を損なうことなく、体験を向上させるべきです。スケーラブルなパフォーマンスのためにcanvasを使用し、prefers-reduced-motionを尊重し、ページが表示されていないときは一時停止し、エフェクトを非インタラクティブに保ちましょう。控えめなパーティクル数から始め、ブラウザが処理できると想定するのではなく、実際のデバイステストに基づいて調整してください。

よくある質問

適切に実装されたcanvasアニメーションは、Core Web Vitalsへの影響は最小限です。canvasは単一の要素にレンダリングし、requestAnimationFrameを使用するため、レイアウトシフトを引き起こしたり、メインスレッドをブロックしたりしません。パーティクル数を妥当な範囲(100未満)に保ち、タブが非表示のときにアニメーションを一時停止して、良好なパフォーマンススコアを維持してください。

はい。各雪片オブジェクトにドリフトプロパティを追加し、アニメーションループでy位置と並行してx位置を更新します。自然な見た目の振動には、時間ベースのオフセットを持つMath.sinを使用するか、一定の風には一定の水平値を適用します。バラエティのために雪片ごとにドリフト値をランダム化してください。

window.innerWidthとwindow.innerHeightをターゲットコンテナの寸法に置き換えます。getBoundingRectを使用してコンテナのサイズと位置を取得します。canvasのCSSをposition fixedからposition absoluteに変更し、ターゲットコンテナ要素の内部に配置します。

これは、canvasバッファサイズがディスプレイのピクセル密度と一致しない場合に発生します。コードサンプルのdevicePixelRatioスケーリングは、より大きなcanvasバッファを作成し、描画コンテキストをスケーリングすることでこれを修正します。resize関数でこのスケーリングを適用し、各スケール操作の前に変換マトリックスをリセットしてください。

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay