🐛
D3.jsでキモく動き回る線虫を作る
概要
今回は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