概要
この記事では「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回 DOM要素との連携
three.js超入門 第5回 シェーダー(GLSL)の基礎
three.js超入門 第6回 ShaderMaterialでメッシュを変形、着色する
three.js超入門 第7回 シェーダーに変数を渡す
three.js超入門 第8回 シェーダーをインタラクティブに動かす
three.js超入門 第9回 シェーダーでテクスチャにエフェクトをかける
前回はウィンドウのマウスやスクロールなどのイベントから取れる値を使って3Dオブジェクトをインタラクティブに動かしました。
今回はDOM要素の位置と大きさをthree.jsのオブジェクトに反映させて、3DオブジェクトをDOM要素の背景として利用してみます。
下準備
常に回転していると邪魔なので、まず回転に関する記述をコメントアウトしておきます。
export default class Canvas {
constructor() {
// ~ 省略 ~
// マテリアルを作成
const mat = new MeshLambertMaterial({ color: 0xffffff });
// ジオメトリとマテリアルからメッシュを作成
this.mesh = new Mesh(geo, mat);
// this.mesh.rotation.x = Math.PI / 4;
// this.mesh.rotation.y = Math.PI / 4;
// ~ 省略 ~
}
render() {
// 次のフレームを要求
requestAnimationFrame(() => { this.render(); });
// ミリ秒から秒に変換
// const sec = performance.now() / 1000;
// 1秒で45°回転する
// this.mesh.rotation.x = sec * (Math.PI / 4);
// this.mesh.rotation.y = sec * (Math.PI / 4);
// スクロールに追従させる
// this.mesh.position.y = this.scrollY * 0.5;
// 画面に表示
this.renderer.render(this.scene, this.camera);
}
// ~ 省略 ~
};
DOM要素のウィンドウ上の矩形を取得
Canvas
クラス内でDOM
要素を取得するために、コンストラクタでid
を引数で取ってくるように変更します。
import Canvas from './Canvas';
export default class Page00 {
constructor() {
const canvas = new Canvas('scroll-container_title');
// ~ 省略 ~
}
};
getElementById()
で対象のDOM要素を取得したら、element.getBoundingClientRect()でその要素の現在のウィンドウ上の矩形(DOMRect
)を取得します。
element.getBoundingClientRect()
で取得できる値は、ウィンドウの絶対座標なので、スクロールをするたびに値が更新されます。(css
でいうところのposition: fixed;
にしたときの座標をイメージするとわかりやすいかとおもいます。)
export default class Canvas {
constructor(elementId) {
// elementIdのついたDOM要素を取得
this.element = document.getElementById(elementId);
const rect = this.element.getBoundingClientRect();
// ~ 省略 ~
}
};
rect
をコンソールにログ出しすると以下のようになっています。
x, y
とtop, left
には同じ値が入っていますが、x, y
は使用できるブラウザが限られているので、top, left
を使います。
DOMRectのサイズをジオメトリに反映
DOMRect
の幅と高さをBoxGeometry
に反映させます。
this.mesh.position.z = -depth / 2
とすることで、直方体の半分前に出ている奥行きを補正して、z = 0
のところに面がくるように補正しています。
// ~ 省略 ~
export default class Canvas {
constructor(elementId) {
// elementIdのついたDOM要素を取得
this.element = document.getElementById(elementId);
const rect = this.element.getBoundingClientRect();
// ~ 省略 ~
// DOMRectサイズの直方体ジオメトリを作成(幅, 高さ, 奥行き)
const depth = 300;
const geo = new BoxGeometry(rect.width, rect.height, depth);
// ~ 省略 ~
// ジオメトリとマテリアルからメッシュを作成
this.mesh = new Mesh(geo, mat);
this.mesh.position.z = -depth / 2;// 奥行きの半分前に出ているのを下げる
// ~ 省略 ~
}
// ~ 省略 ~
};
最初の位置はまだずれていますが、スクロールして位置を合わせて検証ツールで見るとDOMとメッシュのサイズが一致していることがわかります。
DOMRectの位置をメッシュに反映
メッシュの位置は中心指定で、DOMRect
の位置は左上指定なので、DOMRect
の中心座標を求めてウィンドウの中心との差分をオフセットさせることで位置合わせができます。
// ~ 省略 ~
export default class Canvas {
constructor(elementId) {
// elementIdのついたDOM要素を取得
this.element = document.getElementById(elementId);
const rect = this.element.getBoundingClientRect();
// ~ 省略 ~
// ジオメトリとマテリアルからメッシュを作成
this.mesh = new Mesh(geo, mat);
// this.mesh.position.z = -depth / 2;// 奥行きの半分前に出ているのを下げる(↓に統合)
// ウィンドウ中心からDOMRect中心へのベクトルを求めてオフセットする
const center = new Vector2(rect.x + rect.width / 2, rect.y + rect.height / 2);
const diff = new Vector2(center.x - this.w / 2, center.y - this.h / 2);
this.mesh.position.set(diff.x, -diff.y, -depth / 2);
// ~ 省略 ~
}
// ~ 省略 ~
};
DOMRectの位置、サイズを維持しつつスクロール追従させる
メッシュの位置を設定する部分にデフォルトのスクロール量(this.scrollY
)を足して、オフセット値を変数に保存します。
this.mesh.position.set(diff.x, -(diff.y + this.scrollY), -depth / 2);
this.offsetY = this.mesh.position.y;
render
関数内でコメントアウトしていたスクロール追従の部分のコメントを外して、this.offsetY
を足します。
this.mesh.position.y = this.offsetY + this.scrollY;
これでDOM要素にぴったりついてくる3Dオブジェクトができました!
ライティングによる表現はcss
では実現が難しいので、ボックスではなく平面を置くだけでも普通のWebサイトとは違う、一風変わった表現ができるとおもいます。