how to create basic 360 landing using three.js, nuxt.js and vue3
大家好,我喜歡分享如何使用 Three.js、Nuxt.js 和 Vue 3 創建基本的 360 度著陸頁。
安裝新的 Nuxt.js 項目
npx nuxi@latest init simple-app
cd simple-app
npm i
安裝 Three.js
npm i three -D
現在我們準備好了,讓我們為 360 度著陸頁建立一個新元件。
npx nuxi add component Landing
現在我們將像這樣建立元件內容。
<script setup lang="ts">
import {
PerspectiveCamera,
Scene,
SphereGeometry,
TextureLoader,
MeshBasicMaterial,
Mesh,
WebGLRenderer,
MathUtils,
} from "three";
const container = ref()
const props = defineProps({
panoramaUrl:{
type: String,
default: "img/hdri.webp"
},
});
let camera, scene, renderer;
let isUserInteracting = false,
onPointerDownMouseX = 0, onPointerDownMouseY = 0,
lon = 0, onPointerDownLon = 0,
lat = 0, onPointerDownLat = 0,
phi = 0, theta = 0;
camera = new PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1100);
scene = new Scene();
const geometry = new SphereGeometry(1000, 100, 100);
const reader = new FileReader();
const onWindowResize= () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
const onPointerDown = (event) => {
if (event.isPrimary === false) return;
isUserInteracting = true;
onPointerDownMouseX = event.clientX;
onPointerDownMouseY = event.clientY;
onPointerDownLon = lon;
onPointerDownLat = lat;
document.addEventListener('pointermove', onPointerMove);
document.addEventListener('pointerup', onPointerUp);
}
const onPointerMove = (event) => {
if (event.isPrimary === false) return;
lon = (onPointerDownMouseX - event.clientX) * 0.1 + onPointerDownLon;
lat = (event.clientY - onPointerDownMouseY) * 0.1 + onPointerDownLat;
}
const onPointerUp = () => {
if (event.isPrimary === false) return;
isUserInteracting = false;
document.removeEventListener('pointermove', onPointerMove);
document.removeEventListener('pointerup', onPointerUp);
}
const onDocumentMouseWheel = (event) => {
const fov = camera.fov + event.deltaY * 0.05;
camera.fov = MathUtils.clamp(fov, 10, 75);
camera.updateProjectionMatrix();
}
const animate = () => {
requestAnimationFrame(animate);
update();
}
const update = () => {
if (isUserInteracting === false) {
lon += 0.1;
}
lat = Math.max(- 85, Math.min(85, lat));
phi = MathUtils.degToRad(90 - lat);
theta = MathUtils.degToRad(lon);
const x = 500 * Math.sin(phi) * Math.cos(theta);
const y = 500 * Math.cos(phi);
const z = 500 * Math.sin(phi) * Math.sin(theta);
camera.lookAt(x, y, z);
renderer.render(scene, camera);
}
const dragover = (event) => {
event.preventDefault();
event.dataTransfer.dropEffect = 'copy';
}
const dragenter = () => {
document.body.style.opacity = 0.5;
}
const dragleave = () => {
document.body.style.opacity = 1;
}
const load1 = (event) => {
material.map.image.src = event.target.result;
material.map.needsUpdate = true;
}
const drop = () => {
event.preventDefault();
reader.addEventListener('load', load1);
reader.readAsDataURL(event.dataTransfer.files);
document.body.style.opacity = 1;
}
onMounted(() => {
init();
animate();
function init() {
// invert the geometry on the x-axis so that all of the faces point inward
geometry.scale(- 1, 1, 1);
const texture = new TextureLoader().load(props.panoramaUrl);
const material = new MeshBasicMaterial({ map: texture });
const mesh = new Mesh(geometry, material);
scene.add(mesh);
renderer = new WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.value.appendChild(renderer.domElement);
container.value.style.touchAction = 'none';
container.value.addEventListener('pointerdown', onPointerDown);
document.addEventListener('wheel', onDocumentMouseWheel);
document.addEventListener('dragover', dragover);
document.addEventListener('dragenter', dragenter);
document.addEventListener('dragleave', dragleave);
document.addEventListener('drop', drop);
window.addEventListener('resize', onWindowResize);
}
});
onUnmounted(() => {
reader.removeEventListener('load', load1);
container.value.replaceWith(container.value.cloneNode(true));
document.removeEventListener('wheel', onDocumentMouseWheel);
document.removeEventListener('dragover', dragover);
document.removeEventListener('dragenter', dragenter);
document.removeEventListener('dragleave', dragleave);
document.removeEventListener('drop', drop);
window.removeEventListener('resize', onWindowResize);
});
</script>
<template>
<div class="max-w-full h-screen w-full overflow-hidden">
<div ref="container"></div>
</div>
</template>
注意: 您可以在下面的連結中的 Three.js 官方文件中找到有關程式碼各部分的資訊。
Three.js 文檔
現在,我們需要在 public 資料夾中建立名為 img 的資料夾,然後建立使用 HDRI 模式的影像。您可以創建自己的圖像,也可以從本部分的任何資源中找到一個圖像。我基本上使用一個名為 HDRi Haven 的網站,並將圖像轉換為 WebP 格式來縮小圖像的總大小。在我的情況下,我不在乎陰影、反射和其他複雜的效果。
現在,我們的公用資料夾中有圖片 img/hdri.webp,我們準備在 index.vue 頁面中使用我們的元件。
注意: 如果我們在應用程式中使用 SSR,我們可能會遇到一些渲染問題。因此,當我們像下面這樣呼叫元件時,我們將使用 <ClientOnly>。
<ClientOnly>
<Landing/>
<template #fallback>
<!-- this will be rendered on server side -->
<p>Landing is loading...</p>
</template>
</ClientOnly>
就是這樣!我們現在有了一個基本的 360 度著陸頁。
discourse.threejs.org
页:
[1]