You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用四元数(Quaternion)旋转HTML&CSS立方体的问题及优化需求

修复四元数立方体旋转问题并实现惯性效果

一、解决立方体无法正常旋转的问题

你的代码大概率踩了这几个常见坑,逐个修复:

  1. 四元数乘法顺序搞反了
    Three.js里,物体的四元数代表当前姿态,要叠加新的局部旋转,得用premultiply(左乘),而不是直接赋值或反向乘法。如果顺序错了,旋转会绕世界轴走,或者方向完全错乱:
// 错误:直接覆盖或顺序颠倒
cube.quaternion = newQuaternion;
cube.quaternion.multiply(newQuaternion); // 这会让旋转绕世界轴转

// 正确:把新旋转四元数左乘到当前姿态,保证绕自身轴转
cube.quaternion.premultiply(newQuaternion);
  1. 鼠标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);
  1. 没加拖动状态锁定
    得确保只有鼠标按下拖动时才计算旋转,不然鼠标随便动都会触发:
let isDragging = false;
document.addEventListener('mousedown', () => isDragging = true);
document.addEventListener('mouseup', () => isDragging = false);
document.addEventListener('mousemove', (e) => {
  if (!isDragging) return;
  // 这里写旋转逻辑
});

二、实现惯性旋转效果

用velocity记录旋转角速度,friction让速度逐渐衰减,拖动结束后立方体慢慢停下:

  1. 先定义全局状态变量
let velocity = new THREE.Vector2(0,0); // 存X/Y轴的旋转角速度
const friction = 0.95; // 摩擦力,0~1之间,越接近1减速越慢
let lastMousePos = new THREE.Vector2(0,0); // 记录上一帧鼠标位置
  1. 拖动时计算角速度
    在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);
});
  1. 动画循环里处理惯性减速
    在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 16:33:18