ブラウザで見ることができる3Dアクアリウムを作ってみました。
これは基本的にはHTMLとCSSだけで出来ています。canvasも使っていません。Vue.jsも使っていますが、魚を泳がせたり視点を変更するための用途のため、3D描画自体にはあまり関係ありません。
記事の最後に操作できるCodePenを配置してあるので読むのが面倒な方はそちらを見てしまってください(大泣きしながら)。
HTMLは単にそれぞれの素材を配置しているだけです。
<div id="app" @mousemove="onMouseMoved" @touchmove="onMouseMoved">
<div class="container" :style="rotation">
<img class="bg" src="bg.jpg">
<img class="water front" src="water.jpg">
<img class="water side left" src="water.jpg">
<img class="water side right" src="water.jpg">
<img class="water top" src="water.jpg">
<img class="ground" src="ground.jpg">
<img v-for="fish in fishes" class="fish" :style="fishStyle(fish)" :src="fish.image">
</div>
</div>
見ての通り上記のようにfishesに魚のデータが入っていてそれを描画しているだけです。fishesは下記のようにVueコンポーネントのステートとして持たせてあります。
new Vue({
el: "#app",
data() {
return {
rotationX: 0.0,
rotationY: 0.0,
fishes: [this.generateFish(), this.generateFish()]
}
},
generateFishは下記のように魚のデータをランダムで初期化しています。
generateFish() {
return {
image: fishImages[Math.floor(Math.random() * fishImages.length)],
x: Math.floor(50 + Math.random() * 200),
y: -50 + Math.floor(Math.random() * 100),
z: -100 + Math.floor(Math.random() * 200),
ax: Math.floor(Math.random() * 2) == 0 ? -1 : 1
}
},
3Dにしたい部分をまずこれで囲んでいます。
.container {
position: relative;
margin-left: auto;
margin-right: auto;
transform-origin: 50%;
transform-style: preserve-3d;
display: flex;
align-items: center;
justify-content: center;
width: 400px;
height: 100%;
}
重要なのは下記です。
ゲームも同じですが、3Dには2つの描画パターンがあります。それは、奥行きを再現するモードと再現しないモードです。preserve-3d
は再現するモードになるので、遠いものが小さく見えるようになります。
これは各要素を回転する際に、どこを支点とするか、を指定するものです。今回、マウス操作で視点を変更できるようにしているので画面の中央、つまり50%のところを指定しています。
何を言ってるかさっぱりわからない! という方は下記の説明をみると一目瞭然だと思います。 transform-origin - CSS: カスケーディングスタイルシート | MDN
背景のCSSはこんな感じです。
.bg {
position: absolute;
width: 400px;
transform: translateZ(-200px);
}
transform
のtranslateZ
を使うことで、画面の奥に配置しています。左の水の壁などは下記のようになります。
.wate-rleft {
position: absolute;
opacity: 0.3;
left: -200px;
transform: rotateY(90deg);
}
left
で左に200pxずらし、transform
のrotateY
で90度回転させることでうまいこと前後の壁とくっつけて箱型になるようにしています。
魚の位置はタイマーで下記のメソッドを呼び出して位置を動かしてあげます。
moveFish(fish) {
if (fish.ax < 0) {
if (fish.x <= 30) {
fish.ax = -fish.ax;
}
} else {
if (fish.x >= 300) {
fish.ax = -fish.ax;
}
}
fish.x += fish.ax;
}
あとはこれをstyle属性にしてあげるだけです。
fishStory(fish) {
const flip = fish.ax < 0 ? '1' : '-1';
return {
left: `${fish.x}px`,
transform: `scaleX(${flip}) translateY(${fish.y}px) translateZ(${fish.z}px)`
};
},
右に移動する時は画像を反転させるためscaleX(-1)
、あとはX, Y, Z座標それぞれに配置するだけです。描画はVueが勝手にやってくれます。
ほんとはCSSのanimationでゆらゆらと上下に揺らせていたのですが、どうもtransformの動作を壊してしまうようなのでやむなく削除しました。揺らすなら自分でそこも計算してyにプラスしてあげないといけないのかもしれませんね。
クリックで魚を追加できるようにしておきました。いらすとやさんの魚で、何色かあったのでランダムな色で養殖します。
<div class="footer">
<button class="btn btn-default" @click="fishes.push(generateFish())">お魚さん生成</button>
</div>
出来上がったのが下記のものです。
マウスを動かすと視点が変わります。スマホの場合はタップすると一応mousemoveイベントが発生して視点を切り替えられるようです。スワイプでの操作は面倒だったので入れていません。
See the Pen 3DAquarium by dala00 (@dala00) on CodePen.
canvasも使わずHTMLとCSSだけで3Dができるとは便利な時代になったもので驚きです。3Dモデルが使えないため利用用途としては限定的になりますが、それでも2D素材だけで十分なアプリケーションであればVueだけでとても簡単にできそうです。CodePenで試していましたが、これくらいの描画であればスマホでも対して重かったりということもなさそうでした。
ただ、画像のシェアも出来ないので素直にcanvasで描画した方が使い勝手は良いかもしれませんね。何かよい案があれば是非使ってみてください。