こんにちはWEBの佐野です。
早いもので今年もGW間近になりましたが、今年もオーツーをどうぞよろしくお願いいたします。
さて今回はJavaScriptで幾何学的な動きをするパーティクルを作ってみたので共有したいと思います。
WEBサイトで使う機会は少ないと思いますがCanvasで実装していて学習にちょうど良い難易度だと感じたので、コードも載せていますがぜひ一度ご自身でも作ってみてほしいと思います。
もくじ
今回の実装内容
まずは今回作ったパーティクルをご覧ください。
See the Pen
Untitled by otwo (@otwo)
on CodePen.
いかがでしょうか、それらしい見栄えになってると思います!
今回は以下の仕様で作成しています。
・リサイズに対応
シンプルに作っていきましょう!
次に私が作ったソースコードを出しているので、もし一度自分で作ってみたい方はここでストップしてください。
ソースコード
と言うことで早速私が作ったソースコードです。
ここにはJavaScriptのコードしか載せていないので、HTMLやCSSのコードは上のCodepenでご確認ください。
// 各種パラメーター
const ratio = 10; // 画面に表示するパーティクル数の計算に利用
const min = 0.3; // 移動スピードの最小値
const max = 2; // 移動スピードの最大値
const dist = 200; // 線で繋がるまでの距離
// 以下、処理
const cvs = document.getElementById("canvas");
const ctx = cvs.getContext("2d");
let intParticle = 0; // 表示するパーティクル数
let aryParticle = []; // パーティクル要素の情報
let canvas = {}; // canvasの縦横の情報
// 初期処理
function init(){
calc();
draw();
}
init();
// リサイズ処理
window.onresize = function(){
calc();
}
// canvasサイズの調整
function calc(){
canvas.width = cvs.width = document.body.clientWidth;
canvas.height = cvs.height = document.body.clientHeight;
intParticle = Math.floor((canvas.width / 300 * ratio) + (canvas.height / 300 * ratio));
if(aryParticle.length < intParticle){
create(aryParticle.length);
}
}
// パーティカル要素を生成
function create(start){
for(let i = start;i < intParticle;i++){
aryParticle.push({
position: { // 初期位置
x: random(0, canvas.width),
y: random(0, canvas.height)
},
direction: { // 進行方向
x: random(min, max, true) * ((random(0, 1)? -1: 1)),
y: random(min, max, true) * ((random(0, 1)? -1: 1))
},
circle: 2 // パーティクルの半径
});
}
}
// 指定範囲ないから乱数を生成
function random(min, max, deci = false){
let result = Math.random() * (max + 1 - min) + min;
return (deci)? result: Math.floor(result);
}
// 描画処理
function draw(){
ctx.clearRect(0, 0, canvas.width, canvas.height);
for(let i = 0;i < intParticle;i++){
let _p = aryParticle[i];
// 縁を描画
ctx.beginPath();
ctx.fillStyle = "#ffffff";
ctx.globalAlpha = 1;
ctx.arc(_p.position.x, _p.position.y, _p.circle, 0, 2 * Math.PI);
ctx.fill();
// パーティクルの更新
aryParticle[i].position.x += _p.direction.x;
aryParticle[i].position.y += _p.direction.y;
// 画面の端に当たったら方向を帰る
if(_p.position.x < 0 || _p.position.x > canvas.width){
aryParticle[i].direction.x *= -1;
}
if(_p.position.y < 0 || _p.position.y > canvas.height){
aryParticle[i].direction.y *= -1;
}
// リサイズでパーティクルが完全に見切れた場合
if(_p.position.x < -_p.circle) aryParticle[i].position.x = _p.circle;
if(_p.position.x > canvas.width + _p.circle) aryParticle[i].position.x = canvas.width - (_p.circle * 2);
if(_p.position.y < -_p.circle) aryParticle[i].position.y = _p.circle;
if(_p.position.y > canvas.height + _p.circle) aryParticle[i].position.y = canvas.height - (_p.circle * 2);
// 線で繋ぐ
for(let _i = 0;_i < intParticle;_i++){
let _n = aryParticle[_i];
if(i != _i){
let _dist = Math.abs(_p.position.x - _n.position.x) + Math.abs(_p.position.y - _n.position.y);
if(_dist < dist){
ctx.beginPath();
ctx.globalAlpha = ((dist-_dist)>(dist/2)? 1: 1-(((dist/2)-(dist-_dist))/(dist/2)));
ctx.strokeStyle = "#ffffff";
ctx.moveTo(_p.position.x, _p.position.y);
ctx.lineTo(_n.position.x, _n.position.y);
ctx.stroke();
}
}
}
}
requestAnimationFrame(draw);
}
var requestAnimationFrame = ( function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback ){
window.setTimeout( callback, 1000 / 60 );
};
} )();
コードは短ければいいと言うものでもないですが、ブログの見栄えも考えて一部ショートハンド(書式の省略)を使っています。
ショートハンドについては過去記事で紹介しているので、お時間あればご覧ください。
あと、過去のCanvas系の記事も載せておくので気が向いたらどうぞ!
アニメーションにはrequestAnimationFrame
Canvasは要素を描画→削除→描画…を繰り返すことパラパラ漫画の様にアニメーションを表現しています。
そのループにsetTimeoutやsetIntervalでも可能ですが、ここではrequestAnimationFrameメソッドを利用しており、JavaScriptでアニメーションを実装するなら必ず覚えておいた方がいいメソッドになるので覚えておきましょう!
requestAnimationFrameメソッド
requestAnimationFrameはアニメーションであることを定義して、負荷を少なく処理をループしてくれる便利なメソッドです。
使い方はこんな感じですごく簡単です。
function loop(){
// ▼ここに処理
// ▲ここに処理
requestAnimationFrame(loop); // 引数はcallback
}
requestAnimationFrameはsetIntervalなどと違って、発火のタイミング(ミリ秒)を指定することができません。
動きとしては描画処理がすべて完了し、次の描画準備ができたら処理を開始すると言う動きをしていています。
さらにはブラウザが非アクティブの時は処理を停止するなどメモリ消費を抑えてくれるため、ぜひ覚えておきたいメソッドですね!
※余談ですが、setIntervalで10ミリ秒を指定して組んでみましたがFireFoxでは処理が重すぎてカクカクでした。
requestAnimationFrameのプレフィックス
107行目からこの記述ですが、requestAnimationFrameはプレフィックスがあるので、それを定義しています。
これがなくても動きますが、用意されているのであれば定義しておく方が良いでしょう。
var requestAnimationFrame = ( function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback ){
window.setTimeout( callback, 1000 / 60 );
};
} )();
requestAnimationFrameをコールした時に各ブラウザのプレフィックスが実行され、記述していないブラウザ以外では1秒間に60回処理されるように定義しています。
他のサイトも拝見しましたが、ほとんどがこの書式だったので理解が難しい場合にはとりあえずでもおまじないとして覚えておきましょう!
リサイズについて
今回の要件でリサイズ対応を含めていて、特記することもないのですが簡単にリサイズについて説明しておきます。
Canvasはリサイズに対応していない
1つ注意しておきたい点が、Canvasはリサイズに対応していないということ。
なのでリサイズを意識する場合は処理を自分で組む必要があります。
と言っても難しいこともなく、リサイズに合わせてCanvasタグに指定してるサイズ情報を更新してあげるだけです。
// canvasサイズの調整
function calc(){
canvas.width = cvs.width = document.body.clientWidth;
canvas.height = cvs.height = document.body.clientHeight;
intParticle = Math.floor((canvas.width / 300 * ratio) + (canvas.height / 300 * ratio));
if(aryParticle.length < intParticle){
create(aryParticle.length);
}
}
サンプルコードのこの個所になりますが、3,4行目でリサイズに合わせてCanvasオブジェクトのwidthとheightをbodyサイズで更新しています。
表示するパーティクル数を更新
リサイズ時に表示用域に合わせてパーティクル数を更新しています。
これは純粋に100個表示とした時にパソコンだと丁度よくてもスマホだと多すぎて見づらい…と言う状況への対応です。
5行目になりますが、300となっているのはざっくりバランス取れそうな数字を入れているだけなので数字を変更してもらっても問題ありません。
この挙動はparticle.jsと言うプラグインの動きを参考にさせていただきました。
自分で考えた挙動で実装するのも勉強になりますが、実績のあるプラグインを参考にするのもアイデアが広がって勉強になりますね!
いかがだったでしょうか。
今回は色も動きもシンプルにしているので少し変化を加えるだけでも見栄えも変わってオシャレになります。
こうした動きのあるプログラムは作っていても楽しいですし気づきも多く、学習にもってこいですね!
参考になれば幸いです、では!