threejs画面拖动事件判断

  前因:想实现一个小功能,有一个参数 lockTiles,当鼠标在屏幕上拖动时,参数 lockTiles 设置为 true,停止拖动时,lockTiles 重设为 false

  思考了一下,这个功能并不难,有两个方向可以实现这个功能:

  • 根据相机是否移动来设置
  • 设置鼠标监听事件,使用 mousedownmousemovemouseup 来判断是否进行了拖动

  不过在对鼠标进行事件监听时遇到了一些坑点。。

1. 根据相机是否移动来判断是否进行了拖拽

  查阅了 ThreeJS 文档,发现在轨道控制器 OrbitControls 中有几个事件可以用于判断相机是否进行了移动:

  • change:当相机位置发生改变时触发
  • start:在对相机进行交互的开始时触发
  • end:在停止对相机进行交互时触发

  基于我们的需求,这边使用的是 startend 事件。

  代码很简单:

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;
    }
}

  不过,对于这种实现方式,当我们使用滚轮拉近和拉远相机时也会触发 startend 事件,这个不是我们想要的效果:(lockTiles 的设置会反映到右上角的勾选框中,不过在滚轮滚动时,因为触发的太快因此看上去都是一直未被勾选的)

flash1

2.设置鼠标监听事件

  拖拽操作可以分解为几个步骤:

  1. 鼠标左键按下
  2. 鼠标进行拖动
  3. 鼠标左键抬起

  因此我们完全可以监听鼠标的动作来判断是否进行了拖拽操作。

  具体是在鼠标按下时设置一个时间戳,当鼠标移动时,判断当前时间减去之前设置的时间戳是否大于某个阈值,若大于,我们认为此时进行了画面拖拽操作,也就是根据时间差来判断。

  在 threejs 中,我们的画布 canvas 就是 renderer.domElement,对其设置事件监听即可。

  这时候遇到了坑点,正常来说 canvas 元素是支持鼠标事件 mousedownmouseup 的,不过在实际测试时,发现 mousedownmouseup 并没有工作,只有 mousemove 起效果了。

  最后,经过多次尝试和google,发现可以使用 pointerdownpointerup 事件来替代。(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();
}

  这样实现的话,只有画面在进行拖拽时能触发回调,而滚轮滚动时则不会触发:

flash2

小结

  在 ThreeJS 中对画面的拖拽判断可以使用 OrbitControls 中的事件 startend 来判断,也可以使用 DOM 中的 pointerdownpointermovepointerup 来判断,而使用 mousedownmouseup 事件是没有效果的。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

 目录