Web Design 演習Ⅱ - 2024 | Personals

Three Fall Image

準備

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: 〇〇;
                }