83
60

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

three.js超入門 第3回 マウスやスクロールでのインタラクション

Last updated at Posted at 2018-12-17

概要

この記事では「three.js超入門」と題して、three.jsの基礎からシェーダーの利用までをやっていきます。
ターゲットは主に「canvas表現を触ったことがないフロントエンドエンジニア」を想定しているので、jsの構文などの説明は省略しています。
three.jsのバージョンは執筆時点で最新のr98を使用します。

three.js超入門 第0回 3Dコンピュータグラフィックスの基礎
three.js超入門 第1回 レンダリングまでの流れ
three.js超入門 第2回 アニメーションと時間ベースでの制御
three.js超入門 第3回 マウスやスクロールでのインタラクション
three.js超入門 第4回 getBoundingClientRect()を使ったDOM要素との連携
three.js超入門 第5回 シェーダー(GLSL)の基礎
three.js超入門 第6回 ShaderMaterialでメッシュを変形、着色する
three.js超入門 第7回 シェーダーに変数を渡す
three.js超入門 第8回 シェーダーをインタラクティブに動かす
three.js超入門 第9回 シェーダーでテクスチャにエフェクトをかける

リポジトリ

前回requestAnimationFrameを使って3Dオブジェクトをアニメーションさせました。
今回はマウスやスクロールなどのイベントから取れる値を使って3Dオブジェクトをインタラクティブに動かしてみます。

下準備

ウィンドウとWebGLの座標を統一させる

前回のコードでは、以下のようにカメラの距離とメッシュのサイズを目測で決めていましたが、今回はウィンドウのマウスイベントなどを使ってインタラクションを実装していくため、WebGLの座標の単位をウィンドウと同じpxに統一します。

↓カメラの撮影範囲や位置、ジオメトリのサイズが目測値になっていた。

import { PerspectiveCamera } from 'three/src/cameras/PerspectiveCamera';

// カメラを作成 (視野角, 画面のアスペクト比, カメラに映る最短距離, カメラに映る最遠距離)
this.camera = new PerspectiveCamera(60, this.w / this.h, 1, 10);
this.camera.position.z = 3;// カメラを遠ざける
// 立方体のジオメトリを作成(幅, 高さ, 奥行き)
const geo = new BoxGeometry(1, 1, 1);

ウィンドウサイズの平面がぴったり収まるカメラ距離

camera_dist.png
直角三角形のひとつの角(fov/2)と、角の反対側の辺の長さ(height/2)がわかっていて、底辺のdistを求めたいので、三角関数のtanの定義に当てはめて値を求めます。

// 視野角をラジアンに変換
const fov    = 60;
const fovRad = (fov / 2) * (Math.PI / 180);

// 途中式
// Math.tan(fovRad)        = (height / 2) / dist;
// Math.tan(fovRad) * dist = (height / 2);
const dist = (height / 2) / Math.tan(fovRad);

ピクセル座標で3Dオブジェクトを再配置

カメラを作成するところを以下のように変更して、ジオメトリの大きさとライトの位置もあわせて変更します。

const fov    = 60;
const fovRad = (fov / 2) * (Math.PI / 180);// 視野角をラジアンに変換
const dist   = (this.h / 2) / Math.tan(fovRad);// ウィンドウぴったりのカメラ距離

// カメラを作成 (視野角, 画面のアスペクト比, カメラに映る最短距離, カメラに映る最遠距離)
this.camera = new PerspectiveCamera(fov, this.w / this.h, 1, dist * 2);
this.camera.position.z = dist;// カメラを遠ざける
this.light.position.set(400, 400, 400);// ライトの位置を設定
// 立方体のジオメトリを作成(幅, 高さ, 奥行き)
const geo = new BoxGeometry(300, 300, 300);

見た目に大きな変化はありませんが、大きさや座標をpx指定できるようになりました。
box-3.png

マウスでのインタラクション

コードを書く前に現時点でのwindow座標とWebGL座標の対応関係を確認しましょう。
PerspectiveCameraを作る際に位置の指定をしていないので、原点(0, 0, 0)を見据えたまま、ウィンドウサイズがぴったり収まるところまで後ろに下がっているので、以下のようになっています。

※注意:WebGLではy軸の向きが上向きになります。
coordinate2.png

マウス座標の取得

まず、Canvasクラスにマウス座標を保存しておく変数と、マウスが動いた時に呼ばれるmouseMoved関数を作成します。

00_empty/Canvas/index.js
// ~ 省略 ~
import { Vector2 } from 'three/src/math/Vector2';

export default class Canvas {
  constructor() {
    // マウス座標
    this.mouse = new Vector2(0, 0);
    
    // ~ 省略 ~
  }

  // ~ 省略 ~

  mouseMoved(x, y) {
    this.mouse.x =  x - (this.w / 2);// 原点を中心に持ってくる
    this.mouse.y = -y + (this.h / 2);// 軸を反転して原点を中心に持ってくる
  }
};

次に、Page00クラスでwindowmousemoveイベントにイベントリスナーを登録し、その中で↑で作ったcanvas.mouseMovedを呼びます。
mousemoveイベントから取得できる値(e.clientXe.clientY)がwindow座標なので、canvas.mouseMoved関数の中でWebGL座標に変換する必要があるというわけです。

00_empty/index.js
import Canvas from './Canvas';

export default class Page00 {
  constructor() {
    const canvas = new Canvas();

    window.addEventListener('mousemove', e => {
      canvas.mouseMoved(e.clientX, e.clientY);
    });
  }
};

マウス座標でライトを動かす

まず、ライト座標の初期値のxy0にしておきます。

this.light.position.set(0, 0, 400);// ライトの位置を設定

mouseMoved関数内で、マウス座標が更新された後にライトの座標にマウス座標をそのまま代入します。

mouseMoved(x, y) {
  this.mouse.x =  x - (this.w / 2);// 原点を中心に持ってくる
  this.mouse.y = -y + (this.h / 2);// 軸を反転して原点を中心に持ってくる

  // ライトの xy座標 をマウス位置にする
  this.light.position.x = this.mouse.x;
  this.light.position.y = this.mouse.y;
}

マウスにライトが付いてくるようになりました!
Kapture 2018-12-13 at 18.27.57.gif

3DCGが勝手に動いているだけでなく、自分の操作に反応するだけでちょっと楽しいですね。

スクロールでのインタラクション

スクロール量の取得

まず、スクロールさせるためにpublic/00_empty/index.htmlにスクロール用のコンテナを追加します。

public/00_empty/index.html
<body>
  <!-- この中にcanvasが入ります -->
  <div id="canvas-container"></div>

  <!-- スクロール用コンテナ -->
  <div id="scroll-container"></div>
  
  <script src="/resource/js/vendor.bundle.js"></script>
  <script src="/resource/js/common.bundle.js"></script>
</body>

スクロールコンテナ用のスタイルはすでにpublic/resource/css/common.cssに記述済みなので、追加する必要はありません。対応する箇所は以下になります。

public/resource/css/common.css
#scroll-container {
  position: relative;
  width: 100%;
  height: 200%;
}

Canvasクラスには現在のスクロール量を保存しておく変数と、スクロールしたときに呼ばれるscrolled関数を用意しておきます。

00_empty/Canvas/index.js
// ~ 省略 ~

export default class Canvas {
  constructor() {
    // スクロール量
    this.scrollY = 0;

    // ~ 省略 ~
  }

  // ~ 省略 ~

  scrolled(y) {
    this.scrollY = y;
  }
};

そして、Page00クラスでウィンドウのscrollイベントで↑で作ったcanvas.scrolled関数を呼び出します。
ページを表示したときにすでに前回のスクロール位置まで移動しているケースがあるので、イベント発生時だけでなく、ページ表示時にもcanvas.scrolled関数を呼んでスクロール位置を更新しておくことを忘れないようにしましょう。

00_empty/index.js
import Canvas from './Canvas';

export default class Page00 {
  constructor() {
    const canvas = new Canvas();
    
    canvas.scrolled(window.scrollY);
    
    window.addEventListener('mousemove', e => {
      canvas.mouseMoved(e.clientX, e.clientY);
    });
    
    window.addEventListener('scroll', e => {
      canvas.scrolled(window.scrollY);
    });
  }
};

スクロール量でメッシュを動かす

Canvasクラスのrender関数内でthis.mesh.position.yにスクロール量を代入してメッシュの位置を更新します。

00_empty/Canvas/index.js
render() {
  // ~ 省略 ~

  // スクロールに追従させる
  this.mesh.position.y = this.scrollY;
  
  // 画面に表示
  this.renderer.render(this.scene, this.camera);
}

スクロールに追従するようになりました!
Kapture 2018-12-17 at 12.17.40.gif
ちなみに、ここでthis.mesh.position.yに反映させるスクロール量を増やしたり減らしたりすることで簡単にパララックス的な動きを作ることができます。

DOM要素がないとパララックスがわかりにくいので、先にindex.htmlh1を追加しておきます。

index.html
<!-- スクロール用コンテナ -->
<div id="scroll-container">
  <h1 id="scroll-container_title">THREEJS<br>TUTORIAL<br>03</h1>
</div>

こちらもスタイルはcommon.cssに記述済みです。

common.css
#scroll-container_title {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);

  color: #fff;
  font-size: 7em;
  letter-spacing: 0.05em;
  font-family: sans-serif;
  font-style: italic;
  font-weight: bold;
}
// スクロール量より多く動く
this.mesh.position.y = this.scrollY * 1.5;

Kapture 2018-12-17 at 12.56.34.gif

// スクロール量より少なく動く
this.mesh.position.y = this.scrollY * 0.5;

Kapture 2018-12-17 at 12.57.08.gif

今回はマウス座標やスクロール量をそのままライトのxy座標とメッシュのy座標に反映させましたが、サイズや回転などに入れてみてもおもしろいとおもいます。

83
60
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
83
60

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?