ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

《three.js网页可操控魔方》

2026/8/12 18:50:18 拓冰建站 浏览量
《three.js网页可操控魔方》 《three.js网页可操控魔方》3D 魔方模拟器 3D 魔方模拟器操作说明鼠标拖拽旋转视角空格打乱魔方R复原魔方U/D/L/R/F/B顺时针转一层Shift 字母逆时针转 打乱 复原步数:0script srchttps://unpkg.com/three0.160.0/build/three.min.js/script script let scene, camera, renderer, cubeGroup; let cubelets []; let moveCount 0; let isAnimating false; let rotationGroup; let faceCubelets []; const COLORS { white: 0xffffff, yellow: 0xffeb3b, red: 0xf44336, orange: 0xff9800, blue: 0x2196f3, green: 0x4caf50, black: 0x222222 }; function init() { try { scene new THREE.Scene(); scene.background new THREE.Color(0x1a1a2e); camera new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(6, 5, 8); camera.lookAt(0, 0, 0); renderer new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(window.devicePixelRatio); document.getElementById(container).appendChild(renderer.domElement); const ambientLight new THREE.AmbientLight(0xffffff, 0.6); scene.add(ambientLight); const directionalLight new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(10, 20, 10); scene.add(directionalLight); cubeGroup new THREE.Group(); scene.add(cubeGroup); createRubiksCube(); setupControls(); animate(); console.log(魔方初始化成功); } catch (e) { showError(初始化错误: e.message); } } function showError(msg) { document.getElementById(error).textContent msg; document.getElementById(error).style.display block; console.error(msg); } function createCubelet(x, y, z) { const cubeletGroup new THREE.Group(); cubeletGroup.position.set(x, y, z); cubeletGroup.userData { originalPos: {x, y, z} }; const geometry new THREE.BoxGeometry(0.95, 0.95, 0.95); const materials []; // 定义每个面的颜色: right, left, top, bottom, front, back const faceDefs [ { cond: x 1, color: red }, // 右 - 红 { cond: x -1, color: orange }, // 左 - 橙 { cond: y 1, color: white }, // 上 - 白 { cond: y -1, color: yellow }, // 下 - 黄 { cond: z 1, color: blue }, // 前 - 蓝 { cond: z -1, color: green } // 后 - 绿 ]; faceDefs.forEach(face { const material new THREE.MeshStandardMaterial({ color: face.cond ? COLORS[face.color] : COLORS.black, roughness: 0.3, metalness: 0.1 }); materials.push(material); }); const cubelet new THREE.Mesh(geometry, materials); cubeletGroup.add(cubelet); cubelets.push(cubeletGroup); cubeGroup.add(cubeletGroup); return cubeletGroup; } function createRubiksCube() { cubelets.forEach(c cubeGroup.remove(c)); cubelets []; for (let x -1; x 1; x) { for (let y -1; y 1; y) { for (let z -1; z 1; z) { createCubelet(x, y, z); } } } } function setupControls() { let isDragging false; let previousMousePosition { x: 0, y: 0 }; renderer.domElement.addEventListener(mousedown, (e) { if (isAnimating) return; isDragging true; previousMousePosition { x: e.clientX, y: e.clientY }; }); renderer.domElement.addEventListener(mousemove, (e) { if (!isDragging) return; const deltaX e.clientX - previousMousePosition.x; const deltaY e.clientY - previousMousePosition.y; cubeGroup.rotation.y deltaX * 0.01; cubeGroup.rotation.x deltaY * 0.01; previousMousePosition { x: e.clientX, y: e.clientY }; }); renderer.domElement.addEventListener(mouseup, () isDragging false); renderer.domElement.addEventListener(mouseleave, () isDragging false); renderer.domElement.addEventListener(contextmenu, (e) e.preventDefault()); document.addEventListener(keydown, (e) { if (isAnimating) return; const key e.key.toUpperCase(); const faces { U: Y, D: -Y, L: -X, R: X, F: Z, B: -Z }; if (faces[key]) { e.preventDefault(); rotateFace(faces[key], e.shiftKey ? -1 : 1); } else if (e.code Space) { e.preventDefault(); scramble(); } else if (key R !e.ctrlKey) { resetCube(); } }); } function getCubeletsOnFace(face) { return cubelets.filter(c { const pos c.userData.originalPos; switch(face) { case Y: return pos.y 1; case -Y: return pos.y -1; case X: return pos.x 1; case -X: return pos.x -1; case Z: return pos.z 1; case -Z: return pos.z -1; } }); } function rotateFace(face, direction 1, animate true) { if (isAnimating) return; isAnimating true; faceCubelets getCubeletsOnFace(face); rotationGroup new THREE.Group(); scene.add(rotationGroup); faceCubelets.forEach(cubelet { cubeGroup.remove(cubelet); rotationGroup.add(cubelet); }); const axis new THREE.Vector3( face X || face -X ? 1 : 0, face Y || face -Y ? 1 : 0, face Z || face -Z ? 1 : 0 ); if (face.startsWith(-)) axis.negate(); const targetAngle (Math.PI / 2) * direction; if (!animate) { rotationGroup.rotateOnWorldAxis(axis, targetAngle); finishRotation(); return; } const duration 200; const startTime Date.now(); function animateRotation() { const elapsed Date.now() - startTime; const progress Math.min(elapsed / duration, 1); const eased 1 - Math.pow(1 - progress, 3); rotationGroup.rotation.set(0, 0, 0); rotationGroup.rotateOnWorldAxis(axis, targetAngle * eased); if (progress 1) { requestAnimationFrame(animateRotation); } else { finishRotation(); } } animateRotation(); } function finishRotation() { const savedCubelets faceCubelets.slice(); savedCubelets.forEach(cubelet { const worldPos new THREE.Vector3(); cubelet.getWorldPosition(worldPos); const worldQuat new THREE.Quaternion(); cubelet.getWorldQuaternion(worldQuat); rotationGroup.remove(cubelet); cubeGroup.add(cubelet); cubelet.position.copy(worldPos); cubelet.quaternion.copy(worldQuat); cubelet.position.x Math.round(cubelet.position.x * 10) / 10; cubelet.position.y Math.round(cubelet.position.y * 10) / 10; cubelet.position.z Math.round(cubelet.position.z * 10) / 10; cubelet.userData.originalPos { x: cubelet.position.x, y: cubelet.position.y, z: cubelet.position.z }; }); scene.remove(rotationGroup); rotationGroup null; isAnimating false; moveCount; document.getElementById(moves).textContent moveCount; } function scramble() { if (isAnimating) return; const moves [Y, -Y, X, -X, Z, -Z]; const sequence []; for (let i 0; i 20; i) { sequence.push({ face: moves[Math.floor(Math.random() * moves.length)], direction: Math.random() 0.5 ? 1 : -1 }); } let i 0; function doNextMove() { if (i sequence.length) { rotateFace(sequence[i].face, sequence[i].direction, true); i; setTimeout(doNextMove, 250); } } doNextMove(); showStatus(魔方已打乱); } function resetCube() { if (isAnimating) return; createRubiksCube(); moveCount 0; document.getElementById(moves).textContent 0; showStatus(魔方已复原); } function showStatus(text) { const status document.getElementById(status); status.textContent text; status.style.display block; setTimeout(() status.style.display none, 2000); } function animate() { requestAnimationFrame(animate); renderer.render(scene, camera); } window.addEventListener(resize, () { camera.aspect window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); window.addEventListener(DOMContentLoaded, init); if (document.readyState ! loading) { init(); } /script