【Three.js】Tweenを使ってオブジェクトを動かす
カッコいい!!
はじめに
ツイーンでもなく、ツウィーンでもなく、トゥウィーンでもなく、トゥイーンだそうです。
Tween.jsというJavaScriptのライブラリがあります。これを使うとプロパティの変化を簡単に定義することができます。
例えば、あるオブジェクトをx軸方向に移動させるとします。普通だったら描画ループ内でx座標を少しずつ変更してオブジェクトをアニメーションさせることになります。
Tweenの場合は、x座標の開始点と終了点だけ決めてあげれば、その中間点の計算はTweenがやってくれます。大量のオブジェクトを扱う場合に、その管理がとても楽になるという優れモノ。これは使わない手はない。というワケで早速使ってみました。
今回はthree.jsの公式サイトを参考にさせてもらいました。
できた
デモサイトはこちら。
「解散」を押すとパネルがランダムな位置に移動して、「集合」を押すと画像のようにキレイに整列します。このパネルの移動にTweenを使っています。
解説
/***** パネルのデータ群 *****/
var panelData = [
"輝",-3,1,0,
"き",-2,1,0,
"続",-1,1,0,
"け",0,1,0,
"る",1,1,0,
"ワ",-3,0,0,
"ン",-2,0,0,
"ダ",-1,0,0,
"ー",0,0,0,
"ウ",1,0,0,
"ー",2,0,0,
"マ",3,0,0,
"ン",4,0,0,
"キ",-3,-1,0,
"ヨ",-2,-1,0,
"子",-1,-1,0,
"私",-3,1,-1,
"は",-2,1,-1,
"人",-3,0,-1,
"の",-2,0,-1,
"心",-1,0,-1,
"に",0,0,-1,
"寄",-3,-1,-1,
"り",-2,-1,-1,
"添",-1,-1,-1,
"う",0,-1,-1,
"仕",-3,-2,-1,
"事",-2,-2,-1,
"を",-1,-2,-1,
"す",0,-2,-1,
"る",1,-2,-1
];
まずはパネルのデータ群です。配列の要素4つ分が1つのパネルの情報になります。「表示する文字」「x座標」「y座標」「z座標」です。この座標に係数を掛けることで、集合時のパネルの位置が決まります。
/***** パネル *****/
var panels = [];
for (var i = 0; i < panelData.length; i+=4) {
// パネルを生成
var panelElem = document.createElement('div');
panelElem.className = 'panel';
// 文字を生成
var charElem = document.createElement('div');
charElem.className = 'char';
charElem.textContent = panelData[i];
// 文字をパネルの中に入れる
panelElem.appendChild(charElem);
// パネルオブジェクトを生成
var panelObj = new THREE.CSS3DObject(panelElem);
// 初期位置はランダム
panelObj.position.x = Math.random() * 2000 - 1000;
panelObj.position.y = Math.random() * 2000 - 1000;
panelObj.position.z = Math.random() * 2000 - 1000;
// シーンに追加
scene.add(panelObj);
// 配列に格納
panels.push(panelObj);
}
次はパネルを作る処理です。"document.createElement"で<div>要素を生成し、それを使ってCSS3DObjectを作ります。このオブジェクトがパネルになります。
あとでループ処理を使って、パネルの1つ1つにあれこれと設定をするので、全てのパネルを配列に格納しておきます。
// 集合ボタン
document.getElementById('gather').addEventListener('click', function() {
gather();
});
// 解散ボタン
document.getElementById('dismiss').addEventListener('click', function() {
dismiss();
});
次はボタンにイベントリスナーを設定します。クリックしたらそれぞれの移動処理を呼び出すだけです。
// 集合
function gather() {
TWEEN.removeAll();
var n = 0;
for (var i = 0; i < panels.length; i++) {
new TWEEN.Tween(panels[i].position)
.to({x: panelData[n+1] * 180, y: panelData[n+2] * 270, z: panelData[n+3] * 500},1000)
.easing(TWEEN.Easing.Exponential.InOut)
.start();
n += 4;
}
}
// 解散
function dismiss() {
TWEEN.removeAll();
for (var i = 0; i < panels.length; i++) {
new TWEEN.Tween(panels[i].position)
.to({x: Math.random() * 2000 - 1000, y: Math.random() * 2000 - 1000, z: Math.random() * 2000 - 1000},1000)
.easing(TWEEN.Easing.Exponential.InOut)
.start();
}
}
そしてこれが移動処理。for文で"panels.length"として、全てのパネルにTweenの設定をしていきます。
コンストラクタに移動前の座標を、"to()"に移動後の座標を渡してあげて"start()"で移動開始です。"easing()"で設定するのは動き方です。今回のは「ゆっくり~速い~ゆっくり」という動きになります。他にも「ずっと一定」とか「最初速くて最後ゆっくり」とか色々な種類があります。
処理の始めに"TWEEN.removeAll()"とありますね。これが無いと「集合」「解除」「集合」みたいにボタンを連打されるとパネルの挙動がおかしくなります。
“removeAll()"を書いておけば、前のTweenの処理を中断して、その位置から新しいTweenを実行するのでそれが解消されます。
// 描画
var clock = new THREE.Clock();
function render() {
var delta = clock.getDelta();
trackballControls.update(delta);
TWEEN.update();
requestAnimationFrame(render);
renderer.render(scene, camera);
}
Tweenの設定ができたら、描画関数内で"TWEEN.update()"を呼びます。これでTweenに設定した通りにアニメーションが行われます。なんとも便利なモノです。Tweenの解説は以上になります。参考までに全コードも載せておきます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>DOM要素の移動</title>
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<script type="text/javascript" src="../libs/three.min.js"></script>
<script type="text/javascript" src="../libs/renderers/CSS3DRenderer.js"></script>
<script type="text/javascript" src="../libs/Tween.js"></script>
<script type="text/javascript" src="../libs/controls/TrackballControls.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
}
#screen {
background-color: #000000;
}
.panel {
width: 120px;
height: 180px;
background-color: rgba(255, 255, 255, 0.1);
box-shadow: 0px 0px 20px rgba(255, 255, 255, 1.0);
border: 1px solid rgba(255, 255, 255, 0.5);
}
.panel .char {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 60px;
font-weight: bold;
color: rgba(255, 255, 255, 0.5);
text-shadow: 0px 0px 10px rgba(255, 255, 255, 1.0);
}
#menu {
display: flex;
position: absolute;
width: 100%;
bottom: 50px;
justify-content: center;
}
#menu .button {
padding: 5px 10px;
cursor: pointer;
color: rgb(255, 255, 0);
border: 1px solid rgba(255, 255, 50, 1.0);
background-color: rgba(255, 255, 50, 0.1);
margin: 0px 10px;
}
#menu .button:active {
background-color: rgba(255,255, 50,0.5);
}
</style>
</head>
<body>
<!-- 3D空間描画領域 -->
<div id="screen"></div>
<!-- ボタン -->
<div id="menu">
<div id="gather" class="button">集合</div>
<div id="dismiss" class="button">解散</div>
</div>
<script type="text/javascript">
window.onload = function() {
/***** パネルのデータ群 *****/
var panelData = [
"輝",-3,1,0,
"き",-2,1,0,
"続",-1,1,0,
"け",0,1,0,
"る",1,1,0,
"ワ",-3,0,0,
"ン",-2,0,0,
"ダ",-1,0,0,
"ー",0,0,0,
"ウ",1,0,0,
"ー",2,0,0,
"マ",3,0,0,
"ン",4,0,0,
"キ",-3,-1,0,
"ヨ",-2,-1,0,
"子",-1,-1,0,
"私",-3,1,-1,
"は",-2,1,-1,
"人",-3,0,-1,
"の",-2,0,-1,
"心",-1,0,-1,
"に",0,0,-1,
"寄",-3,-1,-1,
"り",-2,-1,-1,
"添",-1,-1,-1,
"う",0,-1,-1,
"仕",-3,-2,-1,
"事",-2,-2,-1,
"を",-1,-2,-1,
"す",0,-2,-1,
"る",1,-2,-1
];
/***** シーン *****/
var scene = new THREE.Scene();
/***** パネル *****/
var panels = [];
for (var i = 0; i < panelData.length; i+=4) {
// パネルを生成
var panelElem = document.createElement('div');
panelElem.className = 'panel';
// 文字を生成
var charElem = document.createElement('div');
charElem.className = 'char';
charElem.textContent = panelData[i];
// 文字をパネルの中に入れる
panelElem.appendChild(charElem);
// パネルオブジェクトを生成
var panelObj = new THREE.CSS3DObject(panelElem);
// 初期位置はランダム
panelObj.position.x = Math.random() * 2000 - 1000;
panelObj.position.y = Math.random() * 2000 - 1000;
panelObj.position.z = Math.random() * 2000 - 1000;
// シーンに追加
scene.add(panelObj);
// 配列に格納
panels.push(panelObj);
}
/***** カメラ *****/
// アスペクト比
var aspRatio = window.innerWidth / window.innerHeight;
// 視野角
var fov;
if (aspRatio > 1) {
fov = 20;
} else if (aspRatio > 0.8) {
fov = 30;
} else if (aspRatio > 0.6) {
fov = 40;
} else if (aspRatio > 0.5) {
fov = 50;
} else {
fov = 60;
}
var camera = new THREE.PerspectiveCamera(fov, aspRatio, 1, 10000);
camera.position.z = 3000;
/***** レンダラー *****/
var renderer = new THREE.CSS3DRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById("screen").appendChild(renderer.domElement);
/***** カメラのコントロール *****/
var trackballControls = new THREE.TrackballControls(camera);
trackballControls.rotateSpeed = 3.0;
trackballControls.panSpeed = 0.2;
/***** 関数 *****/
// 描画
var clock = new THREE.Clock();
function render() {
var delta = clock.getDelta();
trackballControls.update(delta);
TWEEN.update();
requestAnimationFrame(render);
renderer.render(scene, camera);
}
// 集合
function gather() {
TWEEN.removeAll();
var n = 0;
for (var i = 0; i < panels.length; i++) {
new TWEEN.Tween(panels[i].position)
.to({x: panelData[n+1] * 180, y: panelData[n+2] * 270, z: panelData[n+3] * 500},1000)
.easing(TWEEN.Easing.Exponential.InOut)
.start();
n += 4;
}
}
// 解散
function dismiss() {
TWEEN.removeAll();
for (var i = 0; i < panels.length; i++) {
new TWEEN.Tween(panels[i].position)
.to({x: Math.random() * 2000 - 1000, y: Math.random() * 2000 - 1000, z: Math.random() * 2000 - 1000},1000)
.easing(TWEEN.Easing.Exponential.InOut)
.start();
}
}
/***** イベントリスナー *****/
// ウィンドウサイズ変更
window.addEventListener("resize", function() {
camera.aspect = window.Width / window.Height;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
render();
});
// 集合ボタン
document.getElementById('gather').addEventListener('click', function() {
gather();
});
// 解散ボタン
document.getElementById('dismiss').addEventListener('click', function() {
dismiss();
});
// 描画
render();
}
</script>
</body>
</html>
あとがき
余談になりますが、このパネルは3D空間にあるとは言っても、普通のwebサイトで使っているのと同じHTML要素です。なのでCSSが効きます。
.panel {
width: 120px;
height: 180px;
background-color: rgba(255, 255, 255, 0.1);
box-shadow: 0px 0px 20px rgba(255, 255, 255, 1.0);
border: 1px solid rgba(255, 255, 255, 0.5);
}
.panel .char {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 60px;
font-weight: bold;
color: rgba(255, 255, 255, 0.5);
text-shadow: 0px 0px 10px rgba(255, 255, 255, 1.0);
}
こんな感じでパネルとその中の文字にCSSで装飾をする事ができます。という事はあんな事やこんな事もできてしまうのです。ワクワクしますよねぇ。これは。
今回は以上です。ありがとうございました。
おしゃれ度
★★★★☆
ディスカッション
コメント一覧
まだ、コメントがありません