点击上方蓝字关注我们
|
|
|
|
|
|
<htmllang="zh-CN"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1.0"> <title>3D骰子三视图教学演示</title> <scriptsrc="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Microsoft YaHei', sans-serif; } body { background-color: #f5f7fa; display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100vh; padding: 20px; overflow-x: hidden; } .container { width: 100%; max-width: 1400px; height: calc(100vw * 9 / 16); max-height: 787.5px; background-color: white; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); display: flex; flex-wrap: wrap; overflow: hidden; position: relative; } .main-view { flex: 1; min-width: 300px; height: 100%; position: relative; } #mainCanvas { width: 100%; height: 100%; display: block; } .ortho-views { display: flex; flex-direction: column; width: 280px; height: 100%; padding: 20px; background-color: #fafafa; border-left: 1px solid #eee; } .ortho-view { width: 100%; height: 33.333%; margin-bottom: 15px; background-color: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); overflow: hidden; position: relative; cursor: pointer; transition: all 0.3s ease; } .ortho-view:last-child { margin-bottom: 0; } .ortho-view.active { box-shadow: 0 0 0 2px #e74c3c, 0 4px 12px rgba(231,76,60,0.2); } .ortho-view canvas { width: 100%; height: 100%; display: block; } .view-label { position: absolute; top: 10px; left: 10px; background-color: rgba(0,0,0,0.7); color: white; padding: 4px 10px; border-radius: 20px; font-size: 14px; z-index: 10; } .view-label.front { background-color: #3498db; } .view-label.top { background-color: #2ecc71; } .view-label.left { background-color: #f39c12; } .controls { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); display: flex; gap: 15px; z-index: 100; } .btn { padding: 10px 20px; background-color: #3498db; color: white; border: none; border-radius: 25px; font-size: 16px; cursor: pointer; transition: all 0.2s ease; box-shadow: 0 2px 6px rgba(52,152,219,0.3); } .btn:hover { background-color: #2980b9; transform: translateY(-2px); box-shadow: 0 4px 8px rgba(52,152,219,0.4); } .btn.locked { background-color: #e74c3c; } .btn.locked:hover { background-color: #c0392b; } .instructions { margin-top: 20px; max-width: 800px; text-align: center; color: #555; line-height: 1.6; } .instructions h3 { color: #2c3e50; margin-bottom: 10px; } .instructions p { font-size: 14px; margin-bottom: 5px; } /* 响应式调整 */ @media (max-width: 992px) { .container { height: auto; flex-direction: column; } .ortho-views { width: 100%; flex-direction: row; height: 220px; border-left: none; border-top: 1px solid #eee; } .ortho-view { width: 33.333%; height: 100%; margin-bottom: 0; margin-right: 15px; } .ortho-view:last-child { margin-right: 0; } } @media (max-width: 576px) { .ortho-views { height: auto; flex-direction: column; } .ortho-view { width: 100%; margin-right: 0; } .controls { flex-wrap: wrap; justify-content: center; } .btn { width: 100%; max-width: 200px; } } </style></head><body> <divclass="container"> <divclass="main-view"> <canvasid="mainCanvas"></canvas> </div> <divclass="ortho-views"> <divclass="ortho-view"id="frontView"> <divclass="view-label front">主视图</div> <canvasid="frontCanvas"></canvas> </div> <divclass="ortho-view"id="topView"> <divclass="view-label top">俯视图</div> <canvasid="topCanvas"></canvas> </div> <divclass="ortho-view"id="leftView"> <divclass="view-label left">左视图</div> <canvasid="leftCanvas"></canvas> </div> </div> <divclass="controls"> <buttonclass="btn"id="lockBtn">视角锁定</button> </div> </div> <divclass="instructions"> <h3>操作说明</h3> <p>1. 鼠标拖拽/触屏滑动中央3D骰子可自由旋转,三视图将实时同步更新</p> <p>2. 点击任意视图框,骰子将自动旋转到对应视角并高亮显示</p> <p>3. 点击"视角锁定"按钮可固定当前视角,再次点击解锁</p> <p>4. 骰子面说明:前面(1点)·上面(3点)·右面(2点)·左面(4点)·后面(6点)·下面(5点)</p> </div> <script> // 全局变量 let scene, camera, renderer, cube; let orthoCameras = {}; let orthoRenderers = {}; let isDragging = false; let previousMousePosition = { x: 0, y: 0 }; let cubeRotation = { x: 0, y: 0 }; let isLocked = false; let targetRotation = { x: 0, y: 0 }; let isRotatingToView = false; // 初始化场景 function init() { // 主场景 scene = new THREE.Scene(); scene.background = new THREE.Color(0xf8f9fa); // 主相机 camera = new THREE.PerspectiveCamera(75, document.getElementById('mainCanvas').clientWidth / document.getElementById('mainCanvas').clientHeight, 0.1, 1000); camera.position.z = 5; // 主渲染器 renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('mainCanvas'), antialias: true }); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(document.getElementById('mainCanvas').clientWidth, document.getElementById('mainCanvas').clientHeight); // 创建骰子(带点数的正方体) createDice(); // 添加光源 addLights(); // 初始化三视图 initOrthoViews(); // 添加事件监听 addEventListeners(); // 开始渲染循环 animate(); } // 创建骰子(带点数纹理) function createDice() { const diceSize = 2; // 骰子材质(6个面,每个面有点数) const materials = [ new THREE.MeshLambertMaterial({ color: 0xffffff, map: createDotTexture(1) }), // 前面 (1点) new THREE.MeshLambertMaterial({ color: 0xffffff, map: createDotTexture(6) }), // 后面 (6点) new THREE.MeshLambertMaterial({ color: 0xffffff, map: createDotTexture(3) }), // 上面 (3点) new THREE.MeshLambertMaterial({ color: 0xffffff, map: createDotTexture(5) }), // 下面 (5点) new THREE.MeshLambertMaterial({ color: 0xffffff, map: createDotTexture(2) }), // 右面 (2点) new THREE.MeshLambertMaterial({ color: 0xffffff, map: createDotTexture(4) }) // 左面 (4点) ]; // 创建正方体几何体 const geometry = new THREE.BoxGeometry(diceSize, diceSize, diceSize); cube = new THREE.Mesh(geometry, materials); // 设置初始旋转 cubeRotation.x = Math.PI / 6; cubeRotation.y = Math.PI / 4; cube.rotation.x = cubeRotation.x; cube.rotation.y = cubeRotation.y; scene.add(cube); } // 创建点数纹理 function createDotTexture(dotCount) { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = 256; canvas.height = 256; // 背景白色 ctx.fillStyle = '#ffffff'; ctx.fillRect(0, 0, 256, 256); // 圆点样式 ctx.fillStyle = '#000000'; const dotRadius = 20; const center = 128; const offset = 60; // 根据点数绘制圆点 switch(dotCount) { case 1: // 中心点 drawDot(ctx, center, center, dotRadius); break; case 2: // 对角 drawDot(ctx, center - offset, center - offset, dotRadius); drawDot(ctx, center + offset, center + offset, dotRadius); break; case 3: // 对角+中心 drawDot(ctx, center - offset, center - offset, dotRadius); drawDot(ctx, center, center, dotRadius); drawDot(ctx, center + offset, center + offset, dotRadius); break; case 4: // 四角 drawDot(ctx, center - offset, center - offset, dotRadius); drawDot(ctx, center + offset, center - offset, dotRadius); drawDot(ctx, center - offset, center + offset, dotRadius); drawDot(ctx, center + offset, center + offset, dotRadius); break; case 5: // 四角+中心 drawDot(ctx, center - offset, center - offset, dotRadius); drawDot(ctx, center + offset, center - offset, dotRadius); drawDot(ctx, center - offset, center + offset, dotRadius); drawDot(ctx, center + offset, center + offset, dotRadius); drawDot(ctx, center, center, dotRadius); break; case 6: // 三行两列 drawDot(ctx, center - offset, center - offset*1.5, dotRadius); drawDot(ctx, center + offset, center - offset*1.5, dotRadius); drawDot(ctx, center - offset, center, dotRadius); drawDot(ctx, center + offset, center, dotRadius); drawDot(ctx, center - offset, center + offset*1.5, dotRadius); drawDot(ctx, center + offset, center + offset*1.5, dotRadius); break; } // 创建纹理 const texture = new THREE.CanvasTexture(canvas); texture.needsUpdate = true; return texture; } // 绘制单个圆点 function drawDot(ctx, x, y, radius) { ctx.beginPath(); ctx.arc(x, y, radius, 0, Math.PI * 2); ctx.fill(); ctx.closePath(); } // 添加光源 function addLights() { // 环境光 const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); scene.add(ambientLight); // 方向光1 const directionalLight1 = new THREE.DirectionalLight(0xffffff, 0.8); directionalLight1.position.set(5, 5, 5); scene.add(directionalLight1); // 方向光2 const directionalLight2 = new THREE.DirectionalLight(0xffffff, 0.4); directionalLight2.position.set(-5, -5, 5); scene.add(directionalLight2); } // 初始化三视图 function initOrthoViews() { const viewInfos = [ { id: 'front', canvasId: 'frontCanvas', label: '主视图', position: new THREE.Vector3(0, 0, 5), rotation: { x: 0, y: 0 } }, { id: 'top', canvasId: 'topCanvas', label: '俯视图', position: new THREE.Vector3(0, 5, 0), rotation: { x: -Math.PI/2, y: 0 } }, { id: 'left', canvasId: 'leftCanvas', label: '左视图', position: new THREE.Vector3(-5, 0, 0), rotation: { x: 0, y: Math.PI/2 } } ]; viewInfos.forEach(info => { // 创建正交相机 const canvas = document.getElementById(info.canvasId); const aspect = canvas.clientWidth / canvas.clientHeight; const camera = new THREE.OrthographicCamera(-3 * aspect, 3 * aspect, 3, -3, 1, 10); camera.position.copy(info.position); camera.lookAt(0, 0, 0); orthoCameras[info.id] = { camera: camera, rotation: info.rotation, canvas: canvas }; // 创建渲染器 const renderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true }); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(canvas.clientWidth, canvas.clientHeight); orthoRenderers[info.id] = renderer; }); } // 添加事件监听 function addEventListeners() { // 主视图鼠标/触屏事件 const mainCanvas = document.getElementById('mainCanvas'); // 鼠标事件 mainCanvas.addEventListener('mousedown', onMouseDown); window.addEventListener('mousemove', onMouseMove); window.addEventListener('mouseup', onMouseUp); // 触屏事件 mainCanvas.addEventListener('touchstart', onTouchStart); window.addEventListener('touchmove', onTouchMove); window.addEventListener('touchend', onTouchEnd); // 视图点击事件 document.getElementById('frontView').addEventListener('click', () => rotateToView('front')); document.getElementById('topView').addEventListener('click', () => rotateToView('top')); document.getElementById('leftView').addEventListener('click', () => rotateToView('left')); // 视角锁定按钮 document.getElementById('lockBtn').addEventListener('click', toggleLock); // 窗口大小调整 window.addEventListener('resize', onWindowResize); } // 鼠标按下 function onMouseDown(event) { if (isLocked || isRotatingToView) return; isDragging = true; previousMousePosition = { x: event.clientX, y: event.clientY }; } // 鼠标移动 function onMouseMove(event) { if (!isDragging || isLocked || isRotatingToView) return; const deltaMove = { x: event.clientX - previousMousePosition.x, y: event.clientY - previousMousePosition.y }; // 更新旋转角度(取消x轴旋转限制,允许360度无限制旋转) cubeRotation.y += deltaMove.x * 0.01; cubeRotation.x += deltaMove.y * 0.01; // 更新立方体旋转(使用取模运算确保旋转角度的连续性) cube.rotation.x = cubeRotation.x % (Math.PI * 2); cube.rotation.y = cubeRotation.y % (Math.PI * 2); // 更新鼠标位置 previousMousePosition = { x: event.clientX, y: event.clientY }; } // 鼠标释放 function onMouseUp() { isDragging = false; } // 触屏开始 function onTouchStart(event) { if (isLocked || isRotatingToView) return; isDragging = true; previousMousePosition = { x: event.touches[0].clientX, y: event.touches[0].clientY }; } // 触屏移动 function onTouchMove(event) { event.preventDefault(); if (!isDragging || isLocked || isRotatingToView) return; const deltaMove = { x: event.touches[0].clientX - previousMousePosition.x, y: event.touches[0].clientY - previousMousePosition.y }; // 更新旋转角度(取消x轴旋转限制,允许360度无限制旋转) cubeRotation.y += deltaMove.x * 0.015; cubeRotation.x += deltaMove.y * 0.015; // 更新立方体旋转(使用取模运算确保旋转角度的连续性) cube.rotation.x = cubeRotation.x % (Math.PI * 2); cube.rotation.y = cubeRotation.y % (Math.PI * 2); // 更新触摸位置 previousMousePosition = { x: event.touches[0].clientX, y: event.touches[0].clientY }; } // 触屏结束 function onTouchEnd() { isDragging = false; } // 旋转到指定视图 function rotateToView(viewId) { // 移除所有视图的active类 document.querySelectorAll('.ortho-view').forEach(view => { view.classList.remove('active'); }); // 给当前视图添加active类 document.getElementById(`${viewId}View`).classList.add('active'); // 设置目标旋转角度 switch(viewId) { case 'front': targetRotation.x = 0; targetRotation.y = 0; break; case 'top': targetRotation.x = -Math.PI/2; targetRotation.y = 0; break; case 'left': targetRotation.x = 0; targetRotation.y = Math.PI/2; break; } isRotatingToView = true; } // 切换视角锁定 function toggleLock() { isLocked = !isLocked; const btn = document.getElementById('lockBtn'); if (isLocked) { btn.textContent = '解锁视角'; btn.classList.add('locked'); } else { btn.textContent = '视角锁定'; btn.classList.remove('locked'); } } // 窗口大小调整 function onWindowResize() { // 更新主相机和渲染器 const mainCanvas = document.getElementById('mainCanvas'); camera.aspect = mainCanvas.clientWidth / mainCanvas.clientHeight; camera.updateProjectionMatrix(); renderer.setSize(mainCanvas.clientWidth, mainCanvas.clientHeight); // 更新三视图 Object.keys(orthoCameras).forEach(id => { const info = orthoCameras[id]; const aspect = info.canvas.clientWidth / info.canvas.clientHeight; info.camera.left = -3 * aspect; info.camera.right = 3 * aspect; info.camera.updateProjectionMatrix(); orthoRenderers[id].setSize(info.canvas.clientWidth, info.canvas.clientHeight); }); } // 动画循环 function animate() { requestAnimationFrame(animate); // 如果正在旋转到指定视图,使用缓动动画 if (isRotatingToView) { const easeFactor = 0.1; // 计算当前旋转与目标旋转的差值 const deltaX = targetRotation.x - cube.rotation.x; const deltaY = targetRotation.y - cube.rotation.y; // 更新旋转 cube.rotation.x += deltaX * easeFactor; cube.rotation.y += deltaY * easeFactor; // 更新cubeRotation cubeRotation.x = cube.rotation.x; cubeRotation.y = cube.rotation.y; // 当差值足够小时,结束动画 if (Math.abs(deltaX) < 0.001 && Math.abs(deltaY) < 0.001) { cube.rotation.x = targetRotation.x; cube.rotation.y = targetRotation.y; cubeRotation.x = targetRotation.x; cubeRotation.y = targetRotation.y; isRotatingToView = false; } } // 渲染主视图 renderer.render(scene, camera); // 渲染三视图 Object.keys(orthoRenderers).forEach(id => { orthoRenderers[id].render(scene, orthoCameras[id].camera); }); } // 初始化页面 window.addEventListener('load', init); </script></body></html>
-
数学学科:函数图像动态演示
-
物理学科:运动定律可视化
-
化学学科:分子结构3D展示
-
生物学科:生命过程模拟
-
教师从知识传授者转变为学习引导者,更专注于教学设计;
-
学生通过亲身参与互动探索,建立起更牢固的知识体系。

