Three.js?3D模型展示與交互實(shí)現(xiàn)完整代碼
前言
本文將詳細(xì)解析一個(gè)基于Three.js的3D模型展示與交互系統(tǒng)的完整實(shí)現(xiàn)代碼,該系統(tǒng)包含了模型加載、環(huán)境光照、用戶交互、響應(yīng)式設(shè)計(jì)等多個(gè)功能模塊。
一、代碼概述
這段代碼實(shí)現(xiàn)了一個(gè)完整的3D產(chǎn)品展示系統(tǒng),主要功能包括:
創(chuàng)建3D場(chǎng)景并設(shè)置正交相機(jī)
加載并顯示3D模型
實(shí)現(xiàn)模型旋轉(zhuǎn)交互(鼠標(biāo)拖拽和慣性效果)
添加加載動(dòng)畫(huà)
實(shí)現(xiàn)響應(yīng)式布局
自定義光標(biāo)效果
二、核心代碼解析
1. 場(chǎng)景初始化
這部分代碼初始化了Three.js的核心組件:
const scene = new THREE.Scene();
const boxElement = document.querySelector(".productModel_box");
const width = boxElement.clientWidth;
const height = boxElement.clientHeight;
// 設(shè)置正交相機(jī)
const aspect = width / height;
const frustumSize = 10;
const camera = new THREE.OrthographicCamera(
(frustumSize * aspect) / -2,
(frustumSize * aspect) / 2,
frustumSize / 2,
frustumSize / -2,
0.1,
1000
);
camera.position.z = 7.5;
// 創(chuàng)建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(width, height);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.toneMapping = THREE.NoToneMapping;
boxElement.appendChild(renderer.domElement);scene: 3D場(chǎng)景容器camera: 使用正交投影相機(jī)(OrthographicCamera),適合產(chǎn)品展示renderer: WebGL渲染器,開(kāi)啟抗鋸齒和透明背景
2. 加載動(dòng)畫(huà)實(shí)現(xiàn)
function createLoadingCube() {
const loadingScene = new THREE.Scene();
const loadingCamera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000);
loadingCamera.position.z = 5;
const loadingRenderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
loadingRenderer.setClearAlpha(0);
loadingRenderer.setSize(100, 100);
document.getElementById("loading-cube").appendChild(loadingRenderer.domElement);
const cube = new THREE.Mesh(
new THREE.BoxGeometry(2, 2, 2),
new THREE.MeshBasicMaterial({ color: 0xfe5000, wireframe: true })
);
loadingScene.add(cube);
loadingScene.add(new THREE.AmbientLight(0xffffff, 1));
function animateLoadingCube() {
requestAnimationFrame(animateLoadingCube);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
loadingRenderer.render(loadingScene, loadingCamera);
}
animateLoadingCube();
return function () {
document.getElementById("loading-cube").removeChild(loadingRenderer.domElement);
};
}
const cleanupLoadingCube = createLoadingCube();這段代碼創(chuàng)建了一個(gè)旋轉(zhuǎn)的線框立方體作為加載動(dòng)畫(huà):
創(chuàng)建獨(dú)立的場(chǎng)景、相機(jī)和渲染器
添加一個(gè)橙色線框立方體
實(shí)現(xiàn)立方體旋轉(zhuǎn)動(dòng)畫(huà)
返回清理函數(shù),用于加載完成后移除動(dòng)畫(huà)
3. 環(huán)境光照設(shè)置
const pmremGenerator = new THREE.PMREMGenerator(renderer);
new THREE.RGBELoader().load(
"https://cdn.shopify.com/s/files/1/0753/2393/2896/files/photo_studio_01_2k.hdr?v=1746778998",
(hdrTexture) => {
const envMap = pmremGenerator.fromEquirectangular(hdrTexture).texture;
scene.environment = envMap;
hdrTexture.dispose();
pmremGenerator.dispose();
}
);使用HDR環(huán)境貼圖創(chuàng)建逼真的光照效果:
使用PMREMGenerator預(yù)處理環(huán)境貼圖
加載2K分辨率的HDR貼圖
將處理后的環(huán)境貼圖應(yīng)用到場(chǎng)景
4. 模型加載與設(shè)置
const loader = new THREE.GLTFLoader();
const dracoLoader = new THREE.DRACOLoader();
dracoLoader.setDecoderPath("https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/libs/draco/");
loader.setDRACOLoader(dracoLoader);
let model = null;
let initialRotation = { x: 0, y: Math.PI };
let hasPlayedScrollAnimation = false;
loader.load(`{{ section.settings.modelLink }}`, (gltf) => {
model = gltf.scene;
model.scale.set(7.5, 7.5, 7.5);
model.position.set(0, -3.5, 0);
model.traverse((child) => {
if (child.isMesh && child.material) {
// 可調(diào)整材質(zhì)屬性
}
});
scene.add(model);
initialRotation = { x: model.rotation.x, y: model.rotation.y };
setTimeout(() => rotateY180(), 500);
setupScrollTriggerWithGSAP();
gsap.to("#loading-container", {
opacity: 0,
duration: 0.5,
onComplete: () => {
document.getElementById("loading-container").style.display = "none";
cleanupLoadingCube();
},
});
});模型加載流程:
使用GLTFLoader和DRACOLoader加載壓縮的GLTF模型
設(shè)置模型縮放和位置
遍歷模型所有子元素,可調(diào)整材質(zhì)屬性
添加模型到場(chǎng)景
加載完成后隱藏加載動(dòng)畫(huà)
執(zhí)行初始旋轉(zhuǎn)動(dòng)畫(huà)(rotateY180)
5. 交互系統(tǒng)實(shí)現(xiàn)
let isDragging = false;
let previousMousePosition = { x: 0, y: 0 };
let targetRotation = { x: 0, y: 0 };
let currentRotation = { x: 0, y: 0 };
const maxRotationX = Math.PI / 3;
const rotationSensitivity = 0.003;
let velocity = { x: 0, y: 0 };
const friction = 0.95;
// 鼠標(biāo)移動(dòng)處理
document.addEventListener("mousemove", (e) => {
mouseX = e.clientX;
mouseY = e.clientY;
if (isInsideBox && !isOverReturnIcon) {
updateCursorPosition(mouseX, mouseY);
cursor.style.display = "flex";
document.body.classList.remove("normal-cursor");
} else {
cursor.style.display = "none";
document.body.classList.add("normal-cursor");
}
if (!isDragging || !model) return;
const deltaMove = {
x: e.clientX - previousMousePosition.x,
y: e.clientY - previousMousePosition.y,
};
velocity = {
x: -deltaMove.y * rotationSensitivity * 0.5,
y: deltaMove.x * rotationSensitivity * 0.5,
};
targetRotation.y += deltaMove.x * rotationSensitivity;
targetRotation.x += deltaMove.y * rotationSensitivity;
targetRotation.x = Math.max(-maxRotationX, Math.min(maxRotationX, targetRotation.x));
previousMousePosition = { x: e.clientX, y: e.clientY };
});
// 動(dòng)畫(huà)循環(huán)
function animate() {
requestAnimationFrame(animate);
if (!isDragging) {
targetRotation.y += velocity.y;
targetRotation.x += velocity.x;
velocity.x *= friction;
velocity.y *= friction;
}
currentRotation.x += (targetRotation.x - currentRotation.x) * 0.1;
currentRotation.y += (targetRotation.y - currentRotation.y) * 0.1;
if (model) {
model.rotation.x = currentRotation.x;
model.rotation.y = currentRotation.y;
}
renderer.render(scene, camera);
}交互系統(tǒng)關(guān)鍵點(diǎn):
鼠標(biāo)拖拽實(shí)現(xiàn)模型旋轉(zhuǎn)
添加旋轉(zhuǎn)慣性效果(velocity和friction)
限制X軸旋轉(zhuǎn)角度(maxRotationX)
平滑過(guò)渡效果(使用currentRotation和targetRotation插值)
自定義光標(biāo)效果
三、功能擴(kuò)展點(diǎn)
這段代碼還可以進(jìn)一步擴(kuò)展:
模型材質(zhì)自定義:在模型加載后的traverse回調(diào)中可以修改材質(zhì)屬性
滾動(dòng)動(dòng)畫(huà):setupScrollTriggerWithGSAP函數(shù)可以擴(kuò)展為基于滾動(dòng)的動(dòng)畫(huà)
多點(diǎn)觸控:當(dāng)前只支持單點(diǎn)觸控,可以擴(kuò)展為多點(diǎn)觸控縮放
模型點(diǎn)擊事件:添加模型特定部件的點(diǎn)擊交互
四、總結(jié)
這個(gè)3D模型展示系統(tǒng)實(shí)現(xiàn)了:
專業(yè)的加載流程(加載動(dòng)畫(huà)+進(jìn)度提示)
高質(zhì)量的渲染效果(HDR環(huán)境光)
流暢的交互體驗(yàn)(拖拽+慣性)
響應(yīng)式設(shè)計(jì)(窗口大小變化適配)
移動(dòng)端支持(觸摸事件處理)
通過(guò)分析這段代碼,我們可以學(xué)習(xí)到如何使用Three.js構(gòu)建一個(gè)完整的產(chǎn)品3D展示系統(tǒng),涵蓋了從加載到交互的完整流程。
到此這篇關(guān)于Three.js 3D模型展示與交互實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Three.js 3D模型展示與交互內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
IE FF OPERA都可用的彈出層實(shí)現(xiàn)代碼
多瀏覽器的彈出層效果核心代碼。需要的朋友可以測(cè)試下這個(gè)是從正在使用的網(wǎng)站中扒下來(lái)的。2009-09-09
javascript css styleFloat和cssFloat
在寫(xiě)js操作css的過(guò)程中發(fā)現(xiàn)float屬性在IE和firefox下對(duì)應(yīng)的js腳本是不一樣的,IE下對(duì)應(yīng)得是 styleFloat,firefox,chorme,safari下對(duì)應(yīng)的是cssFloat,可用in運(yùn)算符去檢測(cè)style是否包含此屬性。2010-03-03
前端彈出對(duì)話框 js實(shí)現(xiàn)ajax交互
這篇文章主要為大家詳細(xì)介紹了前端彈出對(duì)話框,js實(shí)現(xiàn)ajax交互,感興趣的小伙伴們可以參考一下2016-09-09
js之input[type=file]選擇重復(fù)的文件,無(wú)法觸發(fā)change事件問(wèn)題
這篇文章主要介紹了js之input[type=file]選擇重復(fù)的文件,無(wú)法觸發(fā)change事件問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05
基于JavaScript實(shí)現(xiàn)網(wǎng)頁(yè)倒計(jì)時(shí)自動(dòng)跳轉(zhuǎn)代碼
這篇文章主要介紹了基于JavaScript實(shí)現(xiàn)網(wǎng)頁(yè)倒計(jì)時(shí)自動(dòng)跳轉(zhuǎn)代碼 的相關(guān)資料,需要的朋友可以參考下2015-12-12
SwfUpload在IE10上不出現(xiàn)上傳按鈕的解決方法
在測(cè)試中發(fā)現(xiàn)使用了SwfUpload實(shí)現(xiàn)的無(wú)刷新上傳功能,在IE10上竟然無(wú)法使用了,難道SwfUpload不支持嗎?下面與大家分享下通過(guò)修改SwfUplad.JS文件讓其支持ie102013-06-06

