threejs画面拖动事件判断
前因:想实现一个小功能,有一个参数 lockTiles
,当鼠标在屏幕上拖动时,参数 lockTiles
设置为 true
,停止拖动时,lockTiles
重设为 false
。
思考了一下,这个功能并不难,有两个方向可以实现这个功能:
- 根据相机是否移动来设置
- 设置鼠标监听事件,使用
mousedown
、mousemove
和mouseup
来判断是否进行了拖动
不过在对鼠标进行事件监听时遇到了一些坑点。。
1. 根据相机是否移动来判断是否进行了拖拽
查阅了 ThreeJS 文档,发现在轨道控制器 OrbitControls 中有几个事件可以用于判断相机是否进行了移动:
change
:当相机位置发生改变时触发start
:在对相机进行交互的开始时触发end
:在停止对相机进行交互时触发
基于我们的需求,这边使用的是 start
和 end
事件。
代码很简单:
controls = new OrbitControls( camera, renderer.domElement );
controls.addEventListener('start', lock);
controls.addEventListener('end', unlock);
function lock(e) {
if (!param.lockTiles) {
console.log('lock')
param.lockTiles = true;
}
}
function unlock(e) {
if (param.lockTiles) {
console.log('unlock')
param.lockTiles = false;
}
}
不过,对于这种实现方式,当我们使用滚轮拉近和拉远相机时也会触发 start
和 end
事件,这个不是我们想要的效果:(lockTiles
的设置会反映到右上角的勾选框中,不过在滚轮滚动时,因为触发的太快因此看上去都是一直未被勾选的)
2.设置鼠标监听事件
拖拽操作可以分解为几个步骤:
- 鼠标左键按下
- 鼠标进行拖动
- 鼠标左键抬起
因此我们完全可以监听鼠标的动作来判断是否进行了拖拽操作。
具体是在鼠标按下时设置一个时间戳,当鼠标移动时,判断当前时间减去之前设置的时间戳是否大于某个阈值,若大于,我们认为此时进行了画面拖拽操作,也就是根据时间差来判断。
在 threejs 中,我们的画布 canvas
就是 renderer.domElement
,对其设置事件监听即可。
这时候遇到了坑点,正常来说 canvas
元素是支持鼠标事件 mousedown
和 mouseup
的,不过在实际测试时,发现 mousedown
和 mouseup
并没有工作,只有 mousemove
起效果了。
最后,经过多次尝试和google,发现可以使用 pointerdown
和 pointerup
事件来替代。(pointer event)
PointerEvent
接口继承了所有MouseEvent
中的属性,以保障原有为鼠标事件所开发的内容能更加有效的迁移到指针事件。
然后,基于 pointer
事件,写了一个简单的拖拽触发判断类:
export default class MouseDragCheck {
constructor(props) {
this.dom = props.dom;
this.downCb = props.downCb; // 几个事件触发时的回调函数
this.upCb = props.upCb;
this.moveCb = props.moveCb;
this.startClickDown = -1; // 鼠标按下的时间戳
this.dragInterval = 100; // 鼠标拖动的毫秒间隔,大于 100ms 认为它在拖动
}
addEventListeners = () => {
const dom = this.dom;
dom.addEventListener('pointerdown', this.mouseDown, false);
dom.addEventListener('pointermove', this.mouseMove, false);
dom.addEventListener('pointerup', this.mouseUp, false);
};
removeEventListeners = () => {
const dom = this.dom;
dom.removeEventListener('pointerdown', this.mouseDown, false);
dom.removeEventListener('pointermove', this.mouseMove, false);
dom.removeEventListener('pointerup', this.mouseUp, false);
};
mouseDown = (e) => {
this.startClickDown = new Date().getTime();
if (this.downCb) {
this.downCb(e);
}
};
mouseMove = (e) => {
const cur = new Date().getTime();
if (this.startClickDown !== -1 && cur - this.startClickDown > this.dragInterval) {
if (this.moveCb) {
this.moveCb(e);
}
}
};
mouseUp = (e) => {
this.startClickDown = -1;
if (this.upCb) {
this.upCb(e);
}
};
}
然后调用这个类来完成我们对 lockTiles
属性设置的需求:
let dragCheck = new DragCheck({
dom: renderer.domElement,
moveCb: lock,
upCb: unlock
});
...
if (track) {
dragCheck.addEventListeners();
} else {
dragCheck.removeEventListeners();
}
这样实现的话,只有画面在进行拖拽时能触发回调,而滚轮滚动时则不会触发:
小结
在 ThreeJS 中对画面的拖拽判断可以使用 OrbitControls
中的事件 start
、end
来判断,也可以使用 DOM 中的 pointerdown
、pointermove
和 pointerup
来判断,而使用 mousedown
、mouseup
事件是没有效果的。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!