html文件中添加部分样式:
<style type="text/css"> .productsList img { padding: 5px; border: 4px solid rgba(255, 255, 255, 0.3); width: 70px; height: 70px; margin: 0 10px; scale: 1; transition: all 0.3s; } .productsList img:hover { scale: 1.1; } .productDetails a { display: inline-block; padding: 5px 60px; border: 4px solid rgba(255, 255, 255, 0.3); background-color: rgba(4, 64, 150, 0); border-radius: 10px; color: white; transition: all 0.5s; scale: 1; } .productDetails a:hover { scale: 1.1; background-color: rgba(4, 64, 150, 1); border: 4px solid rgba(255, 255, 255, 1); } </style>
同时,需要在html页面上准备id为app的div,用于展示模型:
<div id="app"> </div>
在执行主程序之前,还需要导入importmap文件:
<script type="importmap"> { "imports": { "three": "https://unpkg.com/three@0.138.0/build/three.module.js", "gLTFLoader": "https://unpkg.com/three@0.138.0/examples/jsm/loaders/GLTFLoader.js" } } </script>
导入模块化程序:
<script type="module"> import * as THREE from 'three'; import { GLTFLoader } from 'gLTFLoader'; const w = window.innerWidth const h = window.innerHeight - 80 var textureLoader = new THREE.TextureLoader(); var backgroundTexture = textureLoader.load('bgix5.jpg'); // 3D容器--scene(场景) const scene = new THREE.Scene() scene.background = backgroundTexture // 坐标系 const axes = new THREE.AxesHelper(2, 2, 2) scene.add(axes) // 产品数据数组,包含产品名称、模型文件路径、缩略图url、产品参数、产品详情url let productsData = [ { 'name': 'TXZC2型智能压力开关', 'model': '/files/txzc.gltf', 'imgUrl': 'https://cz-tianli.com/files/slt1.png', 'parameter': '测量范围:-0.1~100MPa<br/>工作温度:-25℃~80℃<br>显示:四位LCD<br>供电电源:DC24V或AC220V<br/>防护等级:IP65<br/>防爆等级:Exd Ⅱ CT6 Gb', 'productLink': 'https://cz-tianli.com/txzcdzylkg/135.html' }, { 'name': 'TL0202机械压力开关', 'model': '/files/ylkg.gltf', 'imgUrl': 'https://cz-tianli.com/files/slt1.png', 'parameter': '测量范围:-0.1~2.5MPa<br>工作温度:-40℃~80℃<br>防护等级:IP65或IP54<br>防爆等级:Exd Ⅱ CT4~T6 Gb', 'productLink': 'https://cz-tianli.com/ptxjxylkg/383.html' } ] const loader = new GLTFLoader() let product = 'product' let isRotate = true let isDragging = false // 导入产品模型 function loadModel(productIndex = 0) { loader.load( productsData[productIndex]['model'], (gltf) => { product = gltf.scene // 初始位置和缩放调整 product.scale.set(0.5, 0.5, 0.5) product.position.y = 1.5 scene.add(product) } ) isRotate = true } loadModel() // 光线 const light = new THREE.AmbientLight(0xffffff) light.position.set(1, 3, 6) scene.add(light) const diectLight = new THREE.DirectionalLight(0xffffff, 1) diectLight.position.set(15, 10, 15) scene.add(diectLight) const diectLight1 = new THREE.DirectionalLight(0xffffff, 1) diectLight1.position.set(-15, 10, 15) scene.add(diectLight1) const diectLight2 = new THREE.DirectionalLight(0xffffff, 1) diectLight2.position.set(-15, 10, -15) scene.add(diectLight2) const diectLight3 = new THREE.DirectionalLight(0xffffff, 1) diectLight3.position.set(15, 10, -15) scene.add(diectLight3) // 相机 const canema = new THREE.PerspectiveCamera(75, w / h, 0.1, 1500) canema.position.set(0, 0, 10) canema.lookAt(0, 0, 0) // 渲染器 const renderer = new THREE.WebGLRenderer({ antialias:true }) renderer.setSize(w, h) renderer.render(scene, canema) document.querySelector('#app').append(renderer.domElement) // 模型鼠标交互 let preX = 0 let preY = 0 let currentX = 0 let currentY = 0 let totalX = 0 let totalY = 0 let deltaX = 0 let deltaY = 0 document.querySelector('#app').addEventListener('mousedown', function (e) { isRotate = false isDragging = true preX = e.clientX preY = e.clientY deltaX = 0 deltaY = 0 }) document.querySelector('#app').addEventListener('mouseup', function (e) { isDragging = false }) document.querySelector('#app').addEventListener('mousemove', function (e) { if (isDragging) { currentX = e.clientX currentY = e.clientY product.children.forEach(kg => { totalX += (deltaX) * 0.001 totalY += (deltaY) * 0.001 kg.rotation.y = totalX kg.rotation.x = totalY }) deltaX = currentX - preX deltaY = currentY - preY preX = currentX preY = currentY } }) // 创建一个HTML元素用于显示产品标题文本 let textElement = document.createElement('div') textElement.style.position = 'absolute' textElement.style.color = 'white' textElement.style.top = h * 0.6 + 'px' textElement.style.left = w * 0.15 + 'px' textElement.style.fontSize = '30px' textElement.style.fontWeight = 'bold' document.body.appendChild(textElement) // 创建一个HTML元素用于显示产品参数文本 let textElement1 = document.createElement('div') textElement1.style.position = 'absolute' textElement1.style.color = 'white' textElement1.style.top = h * 0.6 + 50 + 'px' textElement1.style.left = w * 0.15 + 'px' textElement1.style.fontSize = '20px' document.body.appendChild(textElement1) // 创建一个HTML元素用于跳转产品详情页面 let textElement2 = document.createElement('div') textElement2.className = 'productDetails' textElement2.style.position = 'absolute' textElement2.style.top = h * 0.6 + 240 + 'px' textElement2.style.left = w * 0.15 + 'px' textElement2.style.fontSize = '20px' document.body.appendChild(textElement2) // 底部产品导航 let productsNav = document.createElement('div') productsNav.className = 'productsList' productsNav.style.position = 'absolute' productsNav.style.bottom = '30px' productsNav.style.width = '100%' productsNav.style.textAlign = 'center' document.body.appendChild(productsNav) let imgstr = '' for (let img in productsData) { imgstr += '<img src="' + productsData[img]['imgUrl'] + '" />' } productsNav.innerHTML = imgstr // 更新初始文字 textElement.innerHTML = productsData[0]['name'] textElement1.innerHTML = productsData[0]['parameter'] textElement2.innerHTML = '<a href="' + productsData[0]['productLink'] + '">产品详情</a>' let productsListenList = document.querySelectorAll('.productsList img') for (let productIndex = 0; productIndex < productsListenList.length; productIndex++) { productsListenList[productIndex].addEventListener('click', function (e) { textElement.innerHTML = productsData[productIndex]['name'] textElement1.innerHTML = productsData[productIndex]['parameter'] textElement2.innerHTML = '<a href="' + productsData[0]['productLink'] + '">产品详情</a>' scene.remove(product) loadModel(productIndex) product = productsData[productIndex]['name'] }) } // 运动方式requestAnimationFrame,尽可能接近1/60秒的刷新频率 function tick() { const time = clock.getElapsedTime() if (typeof (product) === 'objct') { product.rotation.z = time } if (typeof (product) != 'string' && isRotate === true) { product.children.forEach(kg => { totalX = time * 0.5 kg.rotation.y = totalX }) } renderer.render(scene, canema) requestAnimationFrame(tick) } // clock是threejs考虑刷新率之后进行优化的频率,优先考虑这种方式 const clock = new THREE.Clock() tick() </script>