使用四元数(Quaternion)旋转HTML&CSS立方体的问题及优化需求
修复四元数立方体旋转问题并实现惯性效果
一、解决立方体无法正常旋转的问题
你的代码大概率踩了这几个常见坑,逐个修复:
- 四元数乘法顺序搞反了
Three.js里,物体的四元数代表当前姿态,要叠加新的局部旋转,得用premultiply(左乘),而不是直接赋值或反向乘法。如果顺序错了,旋转会绕世界轴走,或者方向完全错乱:
// 错误:直接覆盖或顺序颠倒 cube.quaternion = newQuaternion; cube.quaternion.multiply(newQuaternion); // 这会让旋转绕世界轴转 // 正确:把新旋转四元数左乘到当前姿态,保证绕自身轴转 cube.quaternion.premultiply(newQuaternion);
- 鼠标delta转旋转的逻辑不对
鼠标水平移动(dx)对应立方体绕Y轴转,垂直移动(dy)对应绕X轴转,还要把像素差值转成合理的弧度值:
const sensitivity = 0.002; // 调这个控制旋转快慢 const angleX = event.deltaY * sensitivity; // 垂直拖动 → 绕X轴 const angleY = event.deltaX * sensitivity; // 水平拖动 → 绕Y轴 // 创建对应轴的旋转四元数 const qX = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1,0,0), angleX); const qY = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,1,0), angleY); // 合并两个旋转(先绕X再绕Y,顺序可按需调整) const combinedQ = qY.multiply(qX); // 应用到立方体 cube.quaternion.premultiply(combinedQ);
- 没加拖动状态锁定
得确保只有鼠标按下拖动时才计算旋转,不然鼠标随便动都会触发:
let isDragging = false; document.addEventListener('mousedown', () => isDragging = true); document.addEventListener('mouseup', () => isDragging = false); document.addEventListener('mousemove', (e) => { if (!isDragging) return; // 这里写旋转逻辑 });
二、实现惯性旋转效果
用velocity记录旋转角速度,friction让速度逐渐衰减,拖动结束后立方体慢慢停下:
- 先定义全局状态变量
let velocity = new THREE.Vector2(0,0); // 存X/Y轴的旋转角速度 const friction = 0.95; // 摩擦力,0~1之间,越接近1减速越慢 let lastMousePos = new THREE.Vector2(0,0); // 记录上一帧鼠标位置
- 拖动时计算角速度
在mousemove里,用当前鼠标位置和上一帧的差值算速度:
document.addEventListener('mousemove', (e) => { if (!isDragging) return; const currentPos = new THREE.Vector2(e.clientX, e.clientY); // 计算鼠标移动的delta,也就是速度的基础值 velocity.x = currentPos.x - lastMousePos.x; velocity.y = currentPos.y - lastMousePos.y; // 这里执行之前的旋转逻辑 const sensitivity = 0.002; const angleX = velocity.y * sensitivity; const angleY = velocity.x * sensitivity; const qX = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1,0,0), angleX); const qY = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,1,0), angleY); cube.quaternion.premultiply(qY.multiply(qX)); lastMousePos.copy(currentPos); });
- 动画循环里处理惯性减速
在requestAnimationFrame的循环中,鼠标松开后,让速度按摩擦力衰减,同时继续旋转:
function animate() { requestAnimationFrame(animate); if (!isDragging) { // 衰减速度 velocity.x *= friction; velocity.y *= friction; // 速度太小就停下,避免抖动 if (Math.abs(velocity.x) < 0.1 && Math.abs(velocity.y) < 0.1) { velocity.set(0,0); } else { // 用剩余速度继续旋转 const sensitivity = 0.002; const angleX = velocity.y * sensitivity; const angleY = velocity.x * sensitivity; const qX = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1,0,0), angleX); const qY = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,1,0), angleY); cube.quaternion.premultiply(qY.multiply(qX)); } } renderer.render(scene, camera); } animate();
完整可运行代码
// 初始化Three.js基础环境 const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // 创建立方体 const cube = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshBasicMaterial({color: 0x00ff00, wireframe: true})); scene.add(cube); camera.position.z = 5; // 交互状态变量 let isDragging = false; let lastMousePos = new THREE.Vector2(0,0); let velocity = new THREE.Vector2(0,0); const friction = 0.95; const rotationSensitivity = 0.002; // 鼠标事件绑定 document.addEventListener('mousedown', (e) => { isDragging = true; lastMousePos.set(e.clientX, e.clientY); }); document.addEventListener('mouseup', () => isDragging = false); document.addEventListener('mousemove', (e) => { if (!isDragging) return; const currentPos = new THREE.Vector2(e.clientX, e.clientY); velocity.x = currentPos.x - lastMousePos.x; velocity.y = currentPos.y - lastMousePos.y; // 计算并应用旋转 const angleX = velocity.y * rotationSensitivity; const angleY = velocity.x * rotationSensitivity; const qX = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1,0,0), angleX); const qY = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,1,0), angleY); cube.quaternion.premultiply(qY.multiply(qX)); lastMousePos.copy(currentPos); }); // 动画循环 function animate() { requestAnimationFrame(animate); if (!isDragging && (velocity.x !== 0 || velocity.y !== 0)) { velocity.x *= friction; velocity.y *= friction; if (Math.abs(velocity.x) < 0.1 && Math.abs(velocity.y) < 0.1) { velocity.set(0,0); } else { const angleX = velocity.y * rotationSensitivity; const angleY = velocity.x * rotationSensitivity; const qX = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1,0,0), angleX); const qY = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,1,0), angleY); cube.quaternion.premultiply(qY.multiply(qX)); } } renderer.render(scene, camera); } animate();
内容的提问来源于stack exchange,提问作者RGBCube
相关产品推荐
相关产品推荐

