準備
HTML
ダウンロードしたCSSファイルの読み込みを行い
以下のコードを実装したいファイル内で読み込んでください。
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.171/build/three.module.js"
}
}
</script>
<script type="module">
import * as THREE from 'three';
class WebGL {
isInited = false; // boolean
element = null; // HTMLElement | null | undefined
scene = null; // THREE.Scene | null
camera = null; // THREE.PerspectiveCamera | null
renderer = null; // THREE.WebGLRenderer | null
width = 0; // number
height = 0; // number
// (string) : void
constructor(selector) {
this.element = document.querySelector(selector);
}
// public ():void
init() {
if (!this.element) return;
this.width = this.element.clientWidth;
this.height = this.element.clientHeight;
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera();
this.camera.fov = 45;
this.camera.aspect = this.width / this.height;
this.camera.near = 0.1;
this.camera.far = 1000000;
this.camera.updateProjectionMatrix();
this.renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
});
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
this.renderer.setSize(this.width, this.height);
this.renderer.outputEncoding = THREE.sRGBEncoding; // png画像の透過用
this.element.appendChild(this.renderer.domElement);
// ----- カメラ位置調整
this.#setCameraPosition();
this.isInited = true;
}
// public ():void
onUpdate() {
if (!this.isInited || !this.scene || !this.camera || !this.renderer) return;
this.renderer.render(this.scene, this.camera);
}
// public ():void
onResize() {
if (!this.isInited || !this.scene || !this.camera || !this.renderer) return;
this.width = this.element.clientWidth;
this.height = this.element.clientHeight;
this.camera.aspect = this.width / this.height;
this.camera.updateProjectionMatrix();
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
this.renderer.setSize(this.width, this.height);
// ----- カメラ位置調整
this.#setCameraPosition();
}
// private ():void - カメラの位置をブラウザのサイズに合わせて調整
#setCameraPosition() {
const rad = (this.camera.fov / 2) * (Math.PI / 180);
const dist = this.height / 2 / Math.tan(rad);
this.camera.position.z = dist;
}
}
const lerp = (x, y, a) => {
return (1 - a) * x + a * y;
}
const random = (min, max) => {
return Math.random() * (max - min) + min;
};
class _Petal {
webgl = null; // WebGL | null
imageSrcs = ["../../../public/img/sample/petal-001.png", "../../../public/img/sample/petal-002.png"]; // string[]
imageSrc = ""; // string
petalScales = 0;
// Mesh
mesh = null; // Mesh | null
scale = 0; // {x:number,y:number,z:number}
position = { x: 0, y: 0, z: 0 }; // {x:number,y:number,z:number}
rotation = { x: 0, y: 0, z: 0 }; // {x:number,y:number,z:number}
opacity = 0; // number
// Frag
isInited = false; // boolean
constructor(webgl) {
this.webgl = webgl;
}
init() {
// Src
this.imageSrc =
this.imageSrcs[Math.floor(Math.random() * this.imageSrcs.length)];
// Scale
this.scale = random(0.05, 0.1);
// Position
this.position.x = random(
-this.webgl.width / 2 + (this.scale * this.webgl.width) / 2,
this.webgl.width / 2 - (this.scale * this.webgl.width) / 2
);
this.position.y = random(
-this.webgl.height / 2 - (this.scale * this.webgl.width) / 2,
this.webgl.height / 2 + (this.scale * this.webgl.width) / 2
);
this.position.z = (-this.scale * this.webgl.width) / 2;
// Rotation
this.rotation.x = random(-Math.PI, Math.PI);
this.rotation.y = random(-Math.PI, Math.PI);
this.rotation.z = random(-Math.PI, Math.PI);
}
createMesh() {
new THREE.TextureLoader().load(this.imageSrc, (texture) => {
const geometry = new THREE.PlaneGeometry(1, 1, 100, 100);
const material = new THREE.MeshBasicMaterial({
map: texture,
side: THREE.DoubleSide,
transparent: true,
});
this.mesh = new THREE.Mesh(geometry, material);
this.setMesh();
this.webgl.scene.add(this.mesh);
});
}
onUpdate() {
if (!this.mesh) return
// Position
if (this.position.y > -this.webgl.height / 2 - this.scale * this.webgl.width / 2) {
this.position.y -= 1;
} else {
this.position.x = random(
-this.webgl.width / 2 + (this.scale * this.webgl.width) / 2,
this.webgl.width / 2 - (this.scale * this.webgl.width) / 2
);
this.position.y = this.webgl.height / 2 + this.scale * this.webgl.width / 2;
}
this.mesh.position.set(this.position.x, this.position.y, this.position.z);
// Rotation
this.rotation.x += Math.PI / (180 * 2);
this.rotation.y += Math.PI / (180 * 2);
this.rotation.z += Math.PI / (180 * 2);
this.mesh.rotation.set(this.rotation.x, this.rotation.y, this.rotation.z);
// Opacity
const viewMaxY = this.webgl.height / 2 - this.scale * this.webgl.width / 2; // 描画範囲の一番上 : 完全に一番上だと見切れるので要素の半分の値の余裕を持たせる
const viewMinY = this.webgl.height / 2 - this.scale * this.webgl.width; // 上から描画される際の最小値 :
const hideMaxY = -this.webgl.height / 2 + this.scale * this.webgl.width;
const hideMinY = -this.webgl.height / 2 + this.scale * this.webgl.width / 2;
if (this.position.y < viewMaxY && this.position.y > viewMinY) {
const denominator = this.scale * this.webgl.width / 2;
const molecule = this.position.y - this.webgl.height / 2 + this.scale * this.webgl.width / 2;
this.opacity = -molecule / denominator;
this.mesh.material.opacity = this.opacity;
} else if (this.position.y < hideMaxY && this.position.y > hideMinY) {
const denominator = this.scale * this.webgl.width / 2;
const molecule = this.position.y + this.webgl.height / 2 - this.scale * this.webgl.width / 2;
this.opacity = molecule / denominator;
this.mesh.material.opacity = this.opacity;
} else if (this.position.y > viewMaxY) {
this.opacity = 0;
this.mesh.material.opacity = this.opacity;
} else if (this.position.y < hideMinY) {
this.opacity = 0;
this.mesh.material.opacity = this.opacity;
}
}
onResize() {
if (!this.mesh) return;
// Scale
const scale = this.scale * this.webgl.width;
this.mesh.scale.set(scale, scale, scale);
}
setMesh() {
// Scale
const scale = this.scale * this.webgl.width;
this.mesh.scale.set(scale, scale, scale);
// Position
this.mesh.position.set(this.position.x, this.position.y, this.position.z);
// Rotation
this.mesh.rotation.set(this.rotation.x, this.rotation.y, this.rotation.z);
}
}
class Petal {
webgl = null; // WebGL | null
petalLength = 1;
petals = []; // Petal[]
constructor(webgl) {
this.webgl = webgl;
}
init() {
for (let i = 0; i < 10; i++) {
this.petals[i] = new _Petal(this.webgl);
this.petals[i].init();
this.petals[i].createMesh();
}
}
onUpdate() {
this.petals.forEach((petal) => {
petal.onUpdate();
})
}
onResize() {
this.petals.forEach((petal) => {
petal.onResize()
})
}
}
class FallImage {
static exec() {
const element = document.querySelector("#webgl-container");
const WEBGL = new WebGL("#webgl-container");
WEBGL.init();
const PETAL = new Petal(WEBGL);
PETAL.init();
const rendering = () => {
WEBGL.onUpdate();
PETAL.onUpdate();
window.requestAnimationFrame(rendering);
};
rendering();
window.addEventListener("resize", () => {
WEBGL.onResize();
PETAL.onResize();
});
}
}
window.addEventListener("load", () => {
FallImage.exec()
})
</script>
実装
実装に必要な事は準備で行った関連ファイルの読み込みと記述・描画するための要素の作成・CSSでスタイルの指定の3つとなっています。
※サンプルではhtmlに直接スタイルを適応しています。
HTML
描画したい部分に以下の要素を作成してください
<div id="webgl-container"></div>
CSS
描画したい範囲をCSSで指定してください。
#webgl-container {
width: 〇〇;
height: 〇〇;
}