Menggabungkan WebGL Dalam HTML – 3

Karena kita akan membuat efek animasi ripple pada masing-masing image, kita perlu merender object three.js tepat disetiap image.

Buka file js/app.js, pada fungsi constructor tambahkan perintah untuk mengambil data gambar dari web.

this.images = [...document.querySelectorAll('img')];

kemudian tambahkan fungsi addImages() untuk mengumpulkan informasi dari image dan membuat object Plane yang disesuaikan dengan ukuran tiap image.

addImages(){
    this.imageStore = this.images.map(img=>{
        let bounds = img.getBoundingClientRect();
        let geometry = new THREE.PlaneBufferGeometry(bounds.width, bounds.height, 1, 1);

        let texture = new THREE.Texture(img);
        texture.needsUpdate = true;

        let material = new THREE.MeshBasicMaterial({
            //color: 0xff0000, 
            map : texture,
        });
        let mesh = new THREE.Mesh(geometry, material);

        this.scene.add(mesh);

        return{
            img: img,
            mesh: mesh,
            top: bounds.top,
            left: bounds.left,
            width: bounds.width,
            height: bounds.height,
        }
    });
}

Kemudian fungsi setPosition(). Fungsi ini untuk menempatkan masing-masing object tepat dibawah image dari html.

Perhatian

  • sistem kordinat pada web browser dimulai dari top,left. Sementara koordinat WebGL adalah center dari scene.
  • Element image web juga dimulai dari top left, sementara object WebGL adalah center dari object.

Jadi untuk mengatur posisi masing-masing object WebGL kita harus melakukan perhitungan shifting seperti dibawah.

setPosition(){
    this.imageStore.forEach(o=>{
        o.mesh.position.y = -o.top + this.height/2 - o.height/2;
        o.mesh.position.x = o.left - this.width/2 + o.width/2;
    });
}

Preload Image dan Font

Karena web menggunakan eksternal font, kita harus memastikan font sudah selesai diload. Karena ukuran font dapat mengubah koordinat dari image.

Selain itu kita juga harus memastikan image sudah selesai diload oleh browser, baru kemudian kita jalankan fungsi-fungsi WebGL.

Untuk keperluan ini kita akan gunakan library untuk membantu memastikan image dan font selesai diload.

$ npm install imagesloaded

$ npm i fontfaceobserver

Pada fungsi constructor, tambahkan code untuk cek image dan font selesai diload. Lalu jalankan fungsi-fungsi WebGL jika sudah selesai. Kita akan gunakan promise.

const fontOpen = new Promise(resolve => {
    new FontFaceObserver("Open Sans").load().then(() => {
        resolve();
    });
});

const fontPlayfair = new Promise(resolve => {
    new FontFaceObserver("Playfair Display").load().then(() => {
        resolve();
    });
});

// Preload images
const preloadImages = new Promise((resolve, reject) => {
    imagesLoaded(document.querySelectorAll("img"), { background: true }, resolve);
});

let allDone = [fontOpen,fontPlayfair,preloadImages]

Promise.all(allDone).then(()=>{
    this.addImages();
    this.setPosition();

    this.resize();
    this.setupResize();
    // this.addObject();
    this.render();
});

Berikut hasil akhir js/app.js

import * as THREE from 'three';
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js';
import imagesloaded from 'imagesloaded';
import FontFaceObserver from 'fontfaceobserver';
import fragment from './shaders/fragment.glsl';
import vertex from './shaders/vertex.glsl';
import ocean from './../img/ocean.jpg';

export default class Sketch{
	constructor(opt){
		this.time = 0;
		this.container = opt.dom;

		this.scene = new THREE.Scene();

		this.width = this.container.offsetWidth;
		this.height = this.container.offsetHeight;

		this.camera = new THREE.PerspectiveCamera( 70, this.width / this.height, 100, 2000 );
		
		this.camera.position.z = 600;
		this.camera.fov = 2*Math.atan((this.height/2)/600)*(180/Math.PI);

		this.renderer = new THREE.WebGLRenderer({ 
			antialias: true,
			alpha: true,
		});
		
		this.container.appendChild( this.renderer.domElement );
	
		this.controls = new OrbitControls(this.camera, this.renderer.domElement);

		this.images = [...document.querySelectorAll('img')];

        const fontOpen = new Promise(resolve => {
			new FontFaceObserver("Open Sans").load().then(() => {
				resolve();
			});
		});

		const fontPlayfair = new Promise(resolve => {
			new FontFaceObserver("Playfair Display").load().then(() => {
				resolve();
			});
		});

		// Preload images
		const preloadImages = new Promise((resolve, reject) => {
			imagesloaded(document.querySelectorAll("img"), { background: true }, resolve);
		});

		let allDone = [fontOpen,fontPlayfair,preloadImages]
		
		Promise.all(allDone).then(()=>{
			this.addImages();
			this.setPosition();
	
			this.resize();
			this.setupResize();
			//this.addObject();
			this.render();
		});
		

	}
	
	setupResize(){
		window.addEventListener('resize', this.resize.bind(this));
	}

	resize(){
		this.width = this.container.offsetWidth;
		this.height = this.container.offsetHeight;
		this.renderer.setSize (this.width, this.height);
		this.camera.aspect = this.width/this.height;
		this.camera.updateProjectionMatrix();
	}

	// addObject(){
	// 	this.geometry = new THREE.PlaneBufferGeometry(100, 100, 10, 10);
	// 	this.material = new THREE.MeshNormalMaterial();

	// 	this.material = new THREE.ShaderMaterial({
	// 		uniforms:{
	// 			time: {value: 0},
	// 			oceanTexture: {value: new THREE.TextureLoader().load(ocean)},
	// 		},
	// 		side: THREE.DoubleSide,
	// 		fragmentShader: fragment,
	// 		vertexShader: vertex,
	// 		wireframe: true,
	// 	});

	// 	this.mesh = new THREE.Mesh( this.geometry, this.material );
	// 	this.scene.add( this.mesh );
	// }

	render(){
		this.time+=0.05;

		// this.mesh.rotation.x = this.time / 2000;
		// this.mesh.rotation.y = this.time / 1000;

		// this.material.uniforms.time.value = this.time;
	
		this.renderer.render( this.scene, this.camera );

		window.requestAnimationFrame(this.render.bind(this));
	}

	addImages(){
		this.imageStore = this.images.map(img=>{
			let bounds = img.getBoundingClientRect();
			let geometry = new THREE.PlaneBufferGeometry(bounds.width, bounds.height, 1, 1);

			let texture = new THREE.Texture(img);
			texture.needsUpdate = true;

			let material = new THREE.MeshBasicMaterial({
				//color: 0xff0000, 
				map : texture,
			});
			let mesh = new THREE.Mesh(geometry, material);

			this.scene.add(mesh);

			return{
				img: img,
				mesh: mesh,
				top: bounds.top,
				left: bounds.left,
				width: bounds.width,
				height: bounds.height,
			}
		});
	}

	setPosition(){
		this.imageStore.forEach(o=>{
			o.mesh.position.y = -o.top + this.height/2 - o.height/2;
			o.mesh.position.x = o.left - this.width/2 + o.width/2;
		});
	}
}

new Sketch({
	dom: document.getElementById('container')
});

Anda bisa hapus fungsi yang tidak digunakan, pada code diatas masih disertakan dalam bentuk comment sebagai referensi.

Pada modul berikutnya kita akan menyamakan event scrolling antara webGL dan html.

Sharing is caring:

Leave a Comment