jQueryで円軌道アニメーション
こういう軌道上をクルクル回るアニメーションを作ってみました。
確認事項
- 大玉の周りを小球が規則的に回転する
- 小球の回転を3Dっぽくする
- 小球が大玉の後ろに回り込んだら重なり順も対応する
- 球はいくつでも増やせる
- ブラウザ間(特にIE)に差異がないようにする(ただし、多少のことは目をつぶる)
コード
HTML
<article id="container">
<div class="ball01"></div>
<div class="ball02"></div>
<div class="ball03"></div>
<div class="ball04"></div>
<div class="ball05"></div>
<div class="big"></div>
</article>
CSS
#container {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
}
#container [class^=ball] {
position: absolute;
top: 50%;
left: 50%;
margin-left: -40px;
margin-top: -40px;
width: 80px;
height: 80px;
border-radius: 40px;
background: red;
z-index: 11;
}
#container .big {
position: absolute;
top: 50%;
left: 50%;
margin-left: -96px;
margin-top: -96px;
width: 192px;
height: 192px;
border-radius: 96px;
background: blue;
z-index: 10;
}
JS
$(function(){
//ボール
var $balls = $("#container [class^=ball]");
//y=0のときの拡大率
var defaultScale = 1.2;
//半径。コンテナの幅からボールの幅を引く
var radius;
//ロード時・リサイズ時に半径を更新
$(window).on("load resize",function() {
radius = ($("#container").width() - $ball01.width() * defaultScale) / 2;
});
//1周にかかる時間 /ms
var duration = 6000;
//1秒に何回動く?
var frame = 24;
//経過時間をカウント
var count = 0;
//ボールの数を数える
var ballNum = $balls.length;
//ボールが1周するフレーム数
var framesAtOnce = duration * frame / 1000;
//ボールが複数の場合、時間差が入る
var delay;
//ボール固有の経過フレーム数
var ballFrames;
//アニメーション
var carousel = function(){
$balls.each(function(time){
delay = (time - 1) / ballNum;
ballFrames = count + framesAtOnce * delay;
//大玉の後ろ == アニメーションのフレーム数が折り返し地点の後ならz-index小さく
ballFrames % framesAtOnce > framesAtOnce / 2
? $(this).css({ zIndex: 1 }) : $(this).css({ zIndex: 11 });
//css指定
$(this).css({
transform: tXtY(ballFrames),
});
});
setTimeout(carousel, 1000 / frame);// 1/frame秒毎に関数呼び出し
count++;
}
//transformの値を計算する関数
function tXtY(count) {
//ラジアンを計算
var rad = (count * 1000 / duration / frame) * 2 * Math.PI;
//座標
var tX = radius * Math.floor(Math.cos(rad) * 100) / 100;
var tY = radius * Math.floor(Math.sin(rad) * 100) / 100 * 0.4;
//文字列を返す
return 'translate(' + tX + 'px, ' + tY + 'px)'
+ 'scale(' + (Math.floor(Math.sin(rad) * 100) / 100 * (defaultScale - 0.9) + defaultScale) + ')';
}
//発火
carousel();
});
ポイント
変数が多くてややこしいですが、このコードのポイントは以下の3つがあります。
- ボールを円に沿って滑らかに動かす
- ボールの数を簡単に増減させられる
- 時間などの値を変更可能にする
2と3はコードが長くなる代わりに組み込むのは簡単ですが、
1のボールを円に沿って滑らかに動かすには多少の数学知識が必要となります。
次の項目でボールを丸く動かす方法について説明します。
参考にしたサイト
以下のサイトを参考にしました。
ボールの座標の求め方
円上の座標をどうやって求めるかですが、これは三角関数を使って求めます。
三角関数とは
三角関数というのは、直角三角形の辺の長さや角度を求める関数の総称のことです。
サイン・コサイン・タンジェントとかいうやつですね。
今回はその内サインとコサインを使います。
三角関数を使って座標を求めるには
x座標を求めるのににコサイン、y座標を求めるのにサインを使います。
それぞれ余弦定理、正弦定理と呼ぶこともあるようですが、今回はそのままコサイン、サインでいきます。
今回のJSだとこの部分です。
var tX = radius * Math.floor(Math.cos(rad) * 100) / 100;
var tY = radius * Math.floor(Math.sin(rad) * 100) / 100 * 0.4;
tXだけに着目すると
Math.cos(rad)
この関数でコサインを計算しています。
Math.cos()関数でコサインを計算するときは、引数にラジアンを指定するのが普通です。
ラジアンとは
また新しい単位が出てきましたが、ラジアンというのは角度の単位です。
例えば図のように中心の座標(0,0)、半径100という円の、角度30度のラジアンを求めます。
ラジアンは角度から変換ができ、360度 = 2π = 2 * Math.PI なので
ラジアン = 30 * (2 * Math.PI / 360) = 30 * (Math.PI / 180)
と求められます。
x座標を求める
コサインでx座標を求めるには
Math.cos(ラジアン)
つまり
Math.cos( 30 * (Math.PI / 180) )
これで求められます。
コサインは -1 ~ 1 の値を返すので
100 * Math.cos( 30 * (Math.PI / 180) ) = 86.60254037844388
ということで円の角度が30度のとき = 円の外周を 1/12 回ったときのxの座標はだいたい86.6位ということが分かりました。
三角関数をJSに応用する
JSでx座標を求めるのに使ったラジアンはこのようになっています。
var rad = (count * 1000 / duration / frame) * 2 * Math.PI;
1000 / duration / frame = 1 / 144 と考えるとこうなります。
var rad = count / 72 * Math.PI;
count = 144 で円弧を1周したときと同じ 2π になります。
また2周目以降ですが、361度は1度と同じことなので、count = 145 と count = 1 のコサインの値は同じになります。
サインはコサインと使い方はほぼ同じ
サインもコサインと同じく、
Math.sin(rad)
を使ってy座標の値を求めます。
小数点以下の扱いについて
先ほど86.60254037844388という数値が登場しましたが、実際には小数点以下の数字が果てしなく続いています。
円の角度が90度の時、すなわち
Math.cos( 90 * (Math.PI / 180) )
を計算すると、Math.cos()関数は0を返すはずなのですが、実際は
6.123233995736766e-17
という値を返してしまいます。
これに対処するには小数点以下をn桁までに制限する必要があります。
JSでは Math.floor() を応用します。
以下は小数点第二位まで表示し、第三位以下は切り捨てる記述です。
Math.floor(Math.cos( 90 * (Math.PI / 180) ) * 100) / 100
さっきのバグった計算と比較してみます。
まとめ
やはり曲線上の座標をもとめるのは難しいですが、これくらいのレベルの三角関数なら自在に使いこなしたいと思いました。
中学校か高校で習いますけど、使ってないと忘れますね…。