You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

为全景旋转实现阻尼(惯性)效果——不依赖OrbitControls.js

嘿,这事儿好办!我来教你完全手动实现全景图的阻尼拖拽效果,不用依赖OrbitControls,代码精简还能精准把控每一步的手感~

手动实现全景图阻尼拖拽(缓动)效果

核心逻辑拆解

阻尼效果说白了就是:不让全景视角直接跳到用户拖拽的目标位置,而是让当前视角逐步趋近目标值,每次只移动两者差值的一小部分(用阻尼系数控制快慢)。这种渐进式的插值,就是平滑缓动的核心。

步骤1:定义核心状态变量

先搞几个变量来跟踪拖拽和视角的关键状态:

// 当前全景的实际视角(纬度、经度,用弧度值更方便计算)
let currentLat = 0;
let currentLon = 0;
// 用户拖拽时指向的目标视角
let targetLat = 0;
let targetLon = 0;
// 阻尼系数(0~1之间,越接近1阻尼越弱、动得越快;越接近0阻尼越强、停得越慢)
const dampingFactor = 0.15;
// 标记是否正在拖拽
let isDragging = false;
// 记录拖拽起始时的鼠标坐标和视角
let startMouseX, startMouseY;
let startLat, startLon;

步骤2:处理鼠标/触摸事件

接下来监听用户的拖拽操作,实时更新目标视角:

鼠标事件适配

// 鼠标按下时,记录初始状态
const canvas = document.getElementById('your-panorama-canvas'); // 替换成你的全景画布元素
canvas.addEventListener('mousedown', (e) => {
  isDragging = true;
  startMouseX = e.clientX;
  startMouseY = e.clientY;
  startLat = currentLat;
  startLon = currentLon;
});

// 鼠标拖动时,计算目标视角
canvas.addEventListener('mousemove', (e) => {
  if (!isDragging) return;
  
  // 计算鼠标移动的差值
  const deltaX = e.clientX - startMouseX;
  const deltaY = e.clientY - startMouseY;
  
  // 把鼠标移动量转换成视角变化(灵敏度可以自己调)
  const sensitivity = 0.002;
  targetLon = startLon - deltaX * sensitivity; // 左右拖动改变经度
  targetLat = startLat + deltaY * sensitivity; // 上下拖动改变纬度
  
  // 限制纬度范围,避免全景上下翻转(锁定在-90°到90°,对应弧度的-π/2到π/2)
  targetLat = Math.max(-Math.PI/2, Math.min(Math.PI/2, targetLat));
});

// 鼠标松开/离开画布时,停止拖拽状态
canvas.addEventListener('mouseup', () => isDragging = false);
canvas.addEventListener('mouseleave', () => isDragging = false);

触摸事件适配(移动端兼容)

canvas.addEventListener('touchstart', (e) => {
  if (e.touches.length !== 1) return; // 忽略多指触摸
  isDragging = true;
  const touch = e.touches[0];
  startMouseX = touch.clientX;
  startMouseY = touch.clientY;
  startLat = currentLat;
  startLon = currentLon;
});

canvas.addEventListener('touchmove', (e) => {
  if (!isDragging || e.touches.length !== 1) return;
  e.preventDefault(); // 阻止页面原生滚动
  const touch = e.touches[0];
  const deltaX = touch.clientX - startMouseX;
  const deltaY = touch.clientY - startMouseY;
  
  const sensitivity = 0.003; // 移动端可以稍微提高灵敏度
  targetLon = startLon - deltaX * sensitivity;
  targetLat = startLat + deltaY * sensitivity;
  targetLat = Math.max(-Math.PI/2, Math.min(Math.PI/2, targetLat));
});

canvas.addEventListener('touchend', () => isDragging = false);

步骤3:在渲染循环中应用阻尼

最后在全景图的动画渲染循环里,让当前视角逐步向目标视角逼近:

function animate() {
  requestAnimationFrame(animate);
  
  // 应用阻尼逻辑:正在拖拽时直接同步视角,拖拽结束后缓动趋近
  if (!isDragging) {
    currentLat += (targetLat - currentLat) * dampingFactor;
    currentLon += (targetLon - currentLon) * dampingFactor;
  } else {
    currentLat = targetLat;
    currentLon = targetLon;
  }
  
  // 用更新后的currentLat和currentLon去渲染你的全景图
  updatePanorama(currentLat, currentLon); // 替换成你自己的全景更新函数
}

// 启动动画循环
animate();

自定义手感调优小技巧

  • 阻尼系数:想要拖拽后慢慢停下就调小(比如0.1),想要轻快的跟手感就调大(比如0.3)
  • 灵敏度:根据画布大小调整,确保拖拽的移动幅度符合预期
  • 视角限制:纬度范围一定要锁死,不然全景图会出现上下翻转的奇怪效果

这种实现方式完全自定义,没有多余的依赖代码,所有事件和参数都能精准控制,完美满足你的需求~

内容的提问来源于stack exchange,提问作者Vad

火山引擎 最新活动