2018-12-20に更新

Vue.jsとHTMLとCSSで3Dアクアリウムを作ってみた

ブラウザで見ることができる3Dアクアリウムを作ってみました。

この3Dの実装は基本的にはHTMLとCSSだけで出来ています。canvasも使っていません。Vue.jsも使っていますが、魚を泳がせたり視点を変更するための用途のため、3D描画自体にはあまり関係ありません。

Screenshot from 2018-07-13 23-14-23.png

記事の最後に操作できるCodePenを配置してあるので読むのが面倒な方はそちらを見てしまってください(大泣きしながら)。

作り方

HTML

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
      }
    },

CSS

CSSで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%;
}

重要なのは下記です。

transform-style

ゲームも同じですが、3Dには2つの描画パターンがあります。それは、奥行きを再現するモードと再現しないモードです。preserve-3dは再現するモードになるので、遠いものが小さく見えるようになります。

transform-origin

これは各要素を回転する際に、どこを支点とするか、を指定するものです。今回、マウス操作で視点を変更できるようにしているので画面の中央、つまり50%のところを指定しています。

何を言ってるかさっぱりわからない! という方は下記の説明をみると一目瞭然だと思います。
transform-origin - CSS: カスケーディングスタイルシート | MDN

背景

背景のCSSはこんな感じです。

.bg {
  position: absolute;
  width: 400px;
  transform: translateZ(-200px);
}

transformtranslateZを使うことで、画面の奥に配置しています。左の水の壁などは下記のようになります。

.wate-rleft {
  position: absolute;
  opacity: 0.3;
  left: -200px;
  transform: rotateY(90deg);
}

leftで左に200pxずらし、transformrotateYで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>

成果物

下記が出来上がったVue+HTML+CSSの3Dアクアリウムです。

マウスを動かすと視点が変わります。スマホの場合はタップすると一応mousemoveイベントが発生して視点を切り替えられるようです。スワイプでの操作は面倒だったので入れていません。

See the Pen 3DAquarium by dala00 (@dala00) on CodePen.

まとめ

canvasも使わずHTMLとCSSだけで3Dができるとは便利な時代になったもので驚きです。3Dモデルが使えないため利用用途としては限定的になりますが、それでも2D素材だけで十分なアプリケーションであればVueだけでとても簡単にできそうです。CodePenで試していましたが、これくらいの描画であればスマホでも対して重かったりということもなさそうでした。

ただ、画像のシェアも出来ないので素直にcanvasで描画した方が使い勝手は良いかもしれませんね。何かよい案があれば是非使ってみてください。

ツイッターでシェア
みんなに共有、忘れないようにメモ

だら@Crieit開発者

Crieitの開発者です。 Webエンジニアです(在宅)。大体10年ちょい。 記事でわかりにくいところがあればDMで質問していただくか、案件発注してください。 業務依頼、同業種の方からのコンタクトなどお気軽にご連絡ください。 業務経験有:PHP, MySQL, Laravel, React, Flutter, Vue.js, Node, RoR 趣味:Elixir, Phoenix, Nuxt, Express, GCP, AWS等色々 PHPフレームワークちいたんの作者

Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?

コメント