🐛

D3.jsでキモく動き回る線虫を作る

2022/03/19に公開

概要

今回はD3.jsを使って線虫みたいなものが動き回るアニメーションの作り方を紹介します。

完成イメージはこちらのデモをご覧ください

コードの解説

最初にパラメータ設定を行います。
今回は線虫の数を30にしてありますが、皆様のグロ耐性に応じて増減させてください。

let n = 30; //線虫の数
let worms = d3.range(n).map(function(d,i){
	let scale = 10
	return {
		x:Math.random() * window.innerWidth, //初期位置(x軸)
		y:Math.random() * window.innerHeight, //初期位置(y軸)
		scale:scale, //大きさ
		vx: Math.random() * 2 - 1, //移動方向(x軸)
		vy: Math.random() * 2 - 1, //移動方向(y軸)
		diff_x:scale*0.5,
		diff_y:scale*1.0,
		path:"",
		count:0
	};
});

今回はsvgのpathを使って線虫を表現します。
pathの内容と線虫の位置を更新する関数を用意します。

function update_paths(){
	worms.forEach(function(d,i){
		let newPath = "M";
		switch(d.count % 4){
		case 0:
			newPath = newPath + 
				[d.diff_x * 0, 0] + "C" +
				[d.diff_x * 1, d.diff_y] + " " +
				[d.diff_x * 2, d.diff_y] + " " +
				[d.diff_x * 3, d.diff_y] + "C" +
				[d.diff_x * 4, d.diff_y] + " " +
				[d.diff_x * 5, d.diff_y] + " " +
				[d.diff_x * 6, 0] + "C" +
				[d.diff_x * 7, -d.diff_y] + " " +
				[d.diff_x * 8, -d.diff_y] + " " +
				[d.diff_x * 9, -d.diff_y] + "C" +
				[d.diff_x * 10, -d.diff_y] + " " +
				[d.diff_x * 11, -d.diff_y] + " " +
				[d.diff_x * 12, 0];
			break;
		case 1:
			newPath = newPath + 
				[d.diff_x * 0, d.diff_y] + "C" +
				[d.diff_x * 1, d.diff_y] + " " +
				[d.diff_x * 2, d.diff_y] + " " +
				[d.diff_x * 3, 0] + "C" +
				[d.diff_x * 4, -d.diff_y] + " " +
				[d.diff_x * 5, -d.diff_y] + " " +
				[d.diff_x * 6, -d.diff_y] + "C" +
				[d.diff_x * 7, -d.diff_y] + " " +
				[d.diff_x * 8, -d.diff_y] + " " +
				[d.diff_x * 9, 0] + "C" +
				[d.diff_x * 10, d.diff_y] + " " +
				[d.diff_x * 11, d.diff_y] + " " +
				[d.diff_x * 12, d.diff_y];
			break;
		case 2:
			newPath = newPath + 
				[d.diff_x * 0, 0] + "C" +
				[d.diff_x * 1, -d.diff_y] + " " +
				[d.diff_x * 2, -d.diff_y] + " " +
				[d.diff_x * 3, -d.diff_y] + "C" +
				[d.diff_x * 4, -d.diff_y] + " " +
				[d.diff_x * 5, -d.diff_y] + " " +
				[d.diff_x * 6, 0] + "C" +
				[d.diff_x * 7, d.diff_y] + " " +
				[d.diff_x * 8, d.diff_y] + " " +
				[d.diff_x * 9, d.diff_y] + "C" +
				[d.diff_x * 10, d.diff_y] + " " +
				[d.diff_x * 11, d.diff_y] + " " +
				[d.diff_x * 12, 0];
			break;
		case 3:
			newPath = newPath + 
				[d.diff_x * 0, -d.diff_y] + "C" +
				[d.diff_x * 1, -d.diff_y] + " " +
				[d.diff_x * 2, -d.diff_y] + " " +
				[d.diff_x * 3, 0] + "C" +
				[d.diff_x * 4, d.diff_y] + " " +
				[d.diff_x * 5, d.diff_y] + " " +
				[d.diff_x * 6, d.diff_y] + "C" +
				[d.diff_x * 7, d.diff_y] + " " +
				[d.diff_x * 8, d.diff_y] + " " +
				[d.diff_x * 9, 0] + "C" +
				[d.diff_x * 10, -d.diff_y] + " " +
				[d.diff_x * 11, -d.diff_y] + " " +
				[d.diff_x * 12, -d.diff_y];
			break;
		}

		// Bounce off the walls.
		if (d.x < 0 || d.x > window.innerWidth) d.vx *= -1;
		if (d.y < 0 || d.y > window.innerHeight) d.vy *= -1;

		d.x += ~~(d.scale*d.vx);
		d.y += ~~(d.scale*d.vy);
		d.path= newPath;
		d.count ++;
	});
}

コードだけだとわかりにくいですが、この関数で下図のようなベジェ曲線を生成しています。
縦長の四角形1マスの横幅がdiff_x、縦幅がdiff_yに相当します。

countがpathを更新した回数となっており、count % 4に応じて曲線の形が下図の0→1→2→3→0→1…のように繰り返すことで線虫のうねうねした動きを表現します。

また、これと同時に進行方向情報に従って線虫の位置情報を更新して、画面端まで達したときには跳ね返る(進行方向を逆にする)ようにしています。

後は下記のように初回の描画を行って…

update_paths(); 
svg = d3.select('#container').select('svg')
let svg_path = svg.selectAll("path")
		.data(worms).enter()
		.append("svg:path")
		.attr("d",function(d,i){
			return d.path;
		})
		.attr('stroke',function(d,i){
			return 'rgba(100,100,100,1)';
		})
		.attr('fill','none')
		.attr("stroke-width",function(d){
			return '1px';
		})
		.attr('stroke-linecap','round')
		.attr("transform", function(d) {
			return "translate(" + [d.x, d.y] + ")rotate(" + Math.atan2(d.vy, d.vx) * 180 / Math.PI + ")";
		});

定期的にpathを更新してtransitionでアニメーションさせることで、線虫が元気に動き回ってくれます!

animate();
function animate(){
	update_paths();
	svg_path
		.transition()
		.ease(d3.easeLinear)
		.duration(200)
		.attr("d",function(d,i){
			return d.path;
		})
		.attr("transform", function(d) {
			return "translate(" + [d.x, d.y] + 
				")rotate(" + Math.atan2(d.vy, d.vx) * 180 / Math.PI + ")";
		});

	setTimeout(animate,200);

補足

cssのstroke-dashoffsetやtransformを使って今回紹介したものと似たようなアニメーションを作ることも可能なようです。

今回の方法であればpathの各座標を調整したり乱数を掛けたりすれば、自分の思い通りにキモい動きにできると思いますので、皆様でお好みのカスタマイズをしてご利用ください。

Discussion