HTML Canvas要素とJavaScriptを使うと、手軽にクリエイティブコーディングをはじめられます。
前回の記事「パーリンノイズを使いこなせ」に引き続き、先月7月25日に開催されたイベント「Frontend de KANPAI! #4」の登壇内容を記事として紹介します。

本記事ではHTML CanvasとJavaScriptの理解につながることを目標に、次のパーティクル表現の作成テクニックを解説します。サンプルのソースコードはすべてGitHubにて公開していますので、あわせて参照ください。
▲ 完成版サンプル。実装する上で重要な表現のエッセンスだけを絞って解説します
ステップ① パーティクル表現
シンプルなグラフィックから手順を理解していきましょう。パーティクルを定義するのに、座標と速度、寿命を用意します。オブジェクトにプロパティーを定義するだけです。
particles.push(particle); |
アニメーション用の関数requestAnimationFrame()
によって、時間経過でupdate()
関数を呼び出します。関数のなかでは、パーティクルに対して重力を加えたり、摩擦を計算します。いつか消えるように寿命も減らしておくことも必要です。
for (let i = 0; i < particles.length; i++) { |
const particle = particles[i]; |
particle.y += particle.vy; |
if (particle.life <= 0) { |
描画は次のようにHTML Canvasに描きます。配列particles
に格納したパーティクルの情報を使います。
context.clearRect(0, 0, stageW, stageH); |
particles.forEach(particle => { |
context.fillStyle = `white`; |
context.arc(particle.x, particle.y, 100, 0, Math.PI * 2, false ); |
ステップ② 寿命でスケール変化
パーティクルの寿命を使って変化を持たせます。発生時のパーティクルは大きいサイズとし、寿命を迎えて死滅するときには小さいサイズにします。
寿命に対する生存期間の割合は計算によって求まるので、その値をスケールに割り当てます。
const scale = particle.life / MAX_LIFE; |
if (particle.life <= 0) { |
ステップ③ 数を増やす
時間経過でパーティクルがたくさん発生するようにします。発生位置に乱数を持たせて、横幅いっぱいにパーティクルを発生するようにしておきましょう。
x: stageW * Math.random(), |
vy: 30 * (Math.random() - 0.5), |
ステップ④ 形状を増やし点滅させる
形状を増やすために、パーティクル発生時に形状の種類を示すtype
プロパティーを設定します。
type = Math.floor(Math.random() * 2).toString(); |
type
プロパティーに値によって描画の形状を切り替えます。このとき、透明度は80%〜100%の値になるように乱数を使って設定します。時間経過で乱数によって透明度が変化するため、点滅しているような表現になります。
context.arc(particle.x, particle.y, ・・・); |
const alpha = Math.random() * 0.2 + 0.8; |
context.fillStyle = `hsla(0, 0%, 50%, ${alpha})`; |
context.strokeStyle = `hsla(0, 0%, 50%, ${alpha})`; |
ステップ⑤ 乱数を使いこなせ
乱数の制御テクニックを紹介します。乱数を使ってパーティクルの発生位置を調整すると、表現にバリエーションを増やせます。JavaScriptにはMath.random()
という一様分布の乱数生成命令があります。この乱数を加減乗除するといろんなバリエーションが得られます。
中央に偏らせる
const x = (Math.random() + Math.random()) / 2; |
中央に強く偏らせる
const valueA = (Math.random() + Math.random()) / 2; |
const valueB = (Math.random() + Math.random()) / 2; |
const x = (valueA + valueB) / 2; |
端に偏らせる
const base = Math.random() * Math.random() * Math.random(); |
const inverse = 1.0 - base; |
const x = Math.random() < 0.5 ? base : inverse; |
ここで紹介したランダムの計算方法は一部です。詳しくは記事「JavaScript開発に役立つ重要なランダムの数式まとめ」にまとめているので、参照ください。
ステップ⑥ 最適化手法としてのオブジェクトプール
パーティクル表現は、大量のオブジェクトの生成・破棄を繰り返します。JavaScriptにはメモリ管理の機構としてガベージコレクション(略してGCといいます)が存在します。どこからも参照されておらず不要になったゴミオブジェクトを自動的に回収することで、メモリ使用量を抑えます。
一見便利そうなガベージコレクションは、実は重たい処理です。ガベージコレクションが発生すると、数ミリ秒の負荷が発生します。数ミリ秒というのは、表現の世界ではシビアな時間です。ウェブコンテンツは一般的に60fpsであり、1フレームあたり16ミリ秒しか時間がありません。16ミリ秒で1フレームの描画更新処理を終わらせなければならないのに、数ミリ秒もガベージコレクションに時間が取られてしまうのは、厳しい状況と言わざるを得ません。
もし描画更新処理が16ミリ秒を超えてしまえば、一瞬止まったようなプチフリーズが発生します。そこで対策となるのがオブジェクトプールです。
参照が外れると、ガベージコレクションの対象となります。メモリ使用量が多いと、強烈なGCを引き起こす可能性があります。
オブジェクトプールはガベージコレクションの対象にならないように、あえて参照を保持する方法です。インスタンスはプールと呼ばれる配列に保持されるので、ガベージコレクションの対象にはなりません。
次のコードはオブジェクトプールの簡易的な実装例です。
function toPool(particle) { |
objectPool.unshift(particle); |
if (objectPool.length === 0) { |
詳しくは次の記事「ゲームにおけるガベージコレクションとの付き合い方 – Qiita」が参考になるでしょう。内容はFlashの話題ですが、JavaScriptでも同じことがいえます。いまもFlash時代の資産から学べることが多いですね。
まとめ
今回の記事はパーティクル作り方をかいつまんで説明しました。基本的には、簡単な物理演算とJavaScriptの配列管理がキモです。記事「CreateJS でパーティクルシステムの開発に挑戦しよう」ではもっと初心的なことから解説しているので、あわせて参照ください。