事件

ThingJS 系统内置了很多事件,比点击鼠标、键盘输入、层级变化等。用户可以监听这些事件,在事件回调中进行相应的业务逻辑处理。

事件介绍

全局绑定事件和局部绑定事件

用户的操作以及场景的变化,都将会触发相应的事件。你可以监听这些事件,然后在回调方法中做相应的处理。

  • 全局绑定:通过 app.on 绑定事件,可在全局下添加条件指定针对哪些物体绑定该事件,条件规则同于 query 使用的条件。
    help
    注意事项

    在全局绑定后,新创建的符合条件物体也可以生效。

    一些事件也可以不添加条件,不针对特定物体即可生效。

    例子1:

    app.on("click", function(ev) {
        console.log("you click!");
    });

    上面的例子,不添加条件,鼠标一 click 就触发。

    例子2:

    app.on("click", ".Thing", function(ev) {
        console.log("you click " + ev.object.id);
    });

    上面的例子,添加了条件,只有在符合该条件的物体上 click 才会触发,无论是已经存在的 Thing 对象,还是后面新创建的 Thing 对象都会生效。

  • 局部绑定:针对一个对象,或者 query 的查询结果(Selector),通过 on 接口绑定事件,我们叫局部绑定。同全局绑定,事件中可以加条件,表示这个事件绑定是针对这个物体子子孙孙的。

    也可以不添加条件,表示该事件是针对物体本身的

    例子3:

    obj.on("click", function(ev) {
        console.log(ev.object.name);
    });

    上面的例子,当这个物体被点击,就会触发

    例子 4:

    obj.on("click", ".Marker", function(ev) {
        console.log(ev.object.name);
    });

    上面的例子,当这个物体子孙里有 Marker 物体,当它被点击就会触发。同全局绑定 Marker 物体,绑定后 obj 的子孙里新创建的 Marker 也响应这个事件。

    例子 5:

    obj.query(".Marker").on("click", function(ev) {
        console.log(ev.object.name);
    });

    上面的例子,其实就是直接查询 obj 其下所有的 Marker 物体,遍历找到的 Marker,分别使用 on 注册事件。

    help
    注意事项

    这个方式是对 obj 下每个查询到的 Marker 物体即时绑定的,对于 obj 下后创建的 Marker,是没有影响的。

    上面的例子,其实就是直接查询 obj 其下所有的 Thing 物体,遍历找到的 Thing,分别使用类似例子 4 的方式绑定事件。

ThingJS都提供哪些事件

所有事件通过 THING.EventType 命名空间索引,也可以查询 API 中的 EventType 对应的字符串,不区分大小写。

obj.on("click", function(ev) {
    console.log(ev.object.name);
});

也可以写成

obj.on( THING.EventType.Click, function(ev) {
    console.log(ev.object.name);
});

data 的使用

例子 7:

var cb = function (ev) {
    var color = ev.data;// 获取传入的颜色
    ev.object.style.outlineColor = color;
};
// 这里将颜色作为参数传给回调函数,在回调函数中通过 ev.data 获取
app.query(".Building").on("mouseon", ".Thing", "#FF0000", cb);
app.query(".Thing").on("mouseon", ".Thing", "#00FF00", cb);

同一个事件注册多个回调

试想一下,如果我们有一个模块在物体 click 时,改变物体颜色,另外一个模块需要在物体 click 时,放大物体。两个模块又是不同人写的,那该如何注册事件呢?

//事件1:
obj.on("click", function(ev) {
    ev.object.style.color = "#FF0000";
});

//事件2:
obj.on("click", function(ev) {
    ev.object.scale = [2, 2, 2];
});

这两个事件,都会在 obj 被点击后触发。

但这种多事件注册模式会有些不方便的地方,比如具体指定取消那个事件,需要带上原回调。

var cb = function(ev) {
    console.log("you click!");
};
app.on("click", ".Building", cb);

// 卸载
app.off("click", ".Building", cb);

因此我们更推荐使用[tag]的方式。

tag

如果取消或者暂停事件,都要知道原回调函数,在大部分时候会比较麻烦,我们可以给每个事件打上 tag,在删除或者暂停时,可用 tag 直接指定你要操作的回调。

//模块1中:
obj.on("click",function(ev) {
    ev.object.style.color = "#FF0000";
},"模块1");

//模块2中:
obj.on("click",function(ev) {
    ev.object.scale = [2, 2, 2];
},"模块2");

//取消"模块1"的事件回调,不影响“模块2”的回调
obj.off("click",null ,"模块1");

利用 tag 功能,我们还能实现替换一个回调。比如,之前打了 tag 的事件,我们别处需要修改或者暂停这个事件,就可以通过 tag 来实现。

//将上例的“模块1”的回调,改成如下
obj.on("click",function(ev) {
    ev.object.style.color = "#00FF00";
    },"模块1");

系统内置了一些 tag ,供外部控制(暂停、恢复)。

比较典型的例子是 EnterLevel 事件,该事件会触发多个内置响应,如层级切换后的场景控制、飞行控制、背景变化等。

因此我们更推荐使用tag的方式。

优先级:

还是刚才的例子,两个都是 click 事件,如果我们希望保证模块 2 的事件先于模块 1 触发,该如何设置?

//模块1:
obj.on("click",function(ev) {
    ev.object.style.color = "#FF0000";
    },"模块1");

//模块2:
obj.on("click",function(ev) {
    ev.object.scale = [2, 2, 2];
    },"模块2",51);

在模块 2 的事件回调中,又添加了一个参数`priority`,我们设置为 51,一般事件默认的优先级是 50,设置的越大,越优先触发。

注册单次事件

如果需要一个事件只执行一次,就需要卸载掉,你可以使用 one 这个接口代替 on,参数和 on 是一样的。但是,如下情况需要注意:

我们提供 priority 的能力:

// 下例中是给每个楼层的注册了一次 EnterLevel 事件,即 每个楼层第一次进入时 都会响应
app.one(THING.EventType.EnterLevel, '.Floor', function (ev) {
    console.log(ev.object.id);
})
// 如果只给某个楼层注册 如下
var floor = app.query('.Floor')[0];
floor.one(THING.EventType.EnterLevel, function (ev) {
    console.log(ev.object.id);
})

移除,暂停事件

卸载事件

当我们想卸载一个事件的时候使用 off 接口。

例子 1:

app.on("click", function(event) {
console.log("you click!");
});

// 卸载
app.off("click");

例子 2:

app.on("click", ".Building", function(event) {
    console.log("you click!");
});

// 卸载
app.off("click", ".Building");

例子 3:

var cb = function(event) {
    console.log("you click!");
    }
app.on("click",".Building", cb );

// 卸载
app.off("click", ".Building", cb );

例子 4:

app.on("click", ".Building", function(event) {
    console.log("you click!");
});

app.on("click",".Building",function(event) {
    console.log("you click 1 !");
    },"tag1");

app.on("click",".Building",function(event) {
    console.log("you click 2 !");
    },"tag2");

// 卸载
app.off("click", ".Building");

这样就把所有的 building 下的 click 事件都清除了。

假设这个物体有多个 marker,我们找到其中一个卸载掉事件,不影响其他 marker,和后创建的 marker。

help
注意事项

off 第二个参数必须传条件,如果没有条件,又需要传 tag ,需要将条件传 null。

`one` 注册的事件,也是用`off`来去除。

暂停事件

如果`off`掉一个事件,要想恢复,有时候比较难,你找不到之前的回调方法了。面对这种情况,我们提供 `pauseEvent`方法,用于暂停事件,它的控制方法和标准类似于 off。

例子 6:

app.on("click",".Building",function(event) {
    console.log("you click!");
    },"tag1");

// 暂停
app.pauseEvent("click", ".Building", "tag1");

例子 7:

marker.pauseEvent("click", null, tag);

要想恢复事件响应,使用 `resumeEvent` 方法

app.resumeEvent("click", ".Building", "tag1");

自定义事件

ThingJS 内置了很多事件,但如果自己写模块的时候,也需要触发事件,该如何操作?外部注册还是使用 on,在需要触发的地方我们使用 trigger 接口来对外触发事件。

比如你在写一个报警管理器:

 class AlamrManager {
    constructor() {
        ......
    }

    enable() {
        ......
        app.trigger("AlarmEnable")
        ......
    }

    setObjAlarm(obj, alarmLevel) {
        ......
        obj.trigger("alarm", {"level":alarmLevel} )
        ......
    }
}

外部注册如下:

app.on("AlarmEnable", function(ev){
    ......
})

app.query(".Thing").on("alarm",function(ev){
    if(ev.level == "critical"){
        ......
    }
    ......
})

事件清单

EventType ,可点击此处查看。

事件中的事件名以及事件参数部分如下图所示:

内部事件

  • Complete 通知 App 初始化完成
  • Resize 通知视口大小变化
    • {Array} Resize.size 窗口宽高([0]:宽度, [1]:高度)
  • Update 通知 App 更新
  • Progress 通知加载进度更新
    • {Number} Progress.progress 加载进度(0~1)
  • BeforeLoad 通知将要加载物体
    • {THING.Object} BeforeLoad.object 要加载的物体
  • Load 通知加载
    • {THING.Campus} Load.campus 园区
    • {THING.Selector} Load.buildings 园区建筑物
  • Unload 通知卸载
    • {String} Unload.url 场景资源路径
  • Click 通知鼠标点击
    • {Boolean} Click.picked 是否成功拾取到物体
    • {THING.BaseObject} Click.object 当前拾取物体
    • {Array} Click.pickedPosition 获取拾取点坐标
  • DBLClick 通知鼠标双击
    • {Number} DBLClick.button 鼠标按键[0: 左键, 1: 中键, 2: 右键]
    • {Boolean} DBLClick.picked 是否成功拾取到物体
    • {THING.BaseObject} DBLClick.object 当前拾取物体
    • {Array} DBLClick.pickedPosition 获取拾取点坐标
  • SingleClick 通知鼠标单击
    • {Boolean} SingleClick.picked 是否成功拾取到物体
    • {THING.BaseObject} SingleClick.object 当前拾取物体
    • {Array} SingleClick.pickedPosition 获取拾取点坐标
  • MouseUp 通知鼠标键抬起
    • {Boolean} MouseUp.picked 是否成功拾取到物体
    • {THING.BaseObject} MouseUp.object 当前拾取物体
    • {Array} MouseUp.pickedPosition 获取拾取点坐标
  • MouseDown 通知鼠标键按下
    • {Boolean} MouseDown.picked 是否成功拾取到物体
    • {THING.BaseObject} MouseDown.object 当前拾取物体
    • {Array} MouseDown.pickedPosition 获取拾取点坐标
  • MouseMove 通知鼠标移动
    • {Boolean} MouseDown.picked 是否成功拾取到物体
    • {THING.BaseObject} MouseDown.object 当前拾取物体
    • {Array} MouseDown.pickedPosition 获取拾取点坐标
  • MouseMove 通知鼠标移动
    • {Boolean} MouseMove.picked 是否成功拾取到物体
    • {THING.BaseObject} MouseMove.object 当前拾取物体
    • {Array} MouseMove.pickedPosition 获取拾取点坐标
  • MouseWheel 通知鼠标滚轮事件
    • {Number} MouseWheel.delta 判断滚轮方向, (负数: 向前滚动, 正数: 向后滚动)
  • MouseEnter 通知鼠标首次移入物体
    • {THING.BaseObject} MouseEnter.object 当前拾取物体
  • MouseOver 通知鼠标首次移入物体, 会一直传递到父物体
    • {THING.BaseObject} MouseOver.object 当前拾取物体
  • DragStart 通知物体拖拽开始
    • {THING.BaseObject} DragStart.object 当前拾取物体
    • {Array} DragStart.pickedPosition 获取拾取点坐标
  • Drag 通知物体拖拽进行中
    • {THING.BaseObject} Drag.object 当前拾取物体
    • {Array} Drag.pickedPosition 获取拾取点坐标
  • DragEnd 通知物体拖拽结束
    • {THING.BaseObject} DragEnd.object 当前拾取物体
    • {Array} DragEnd.pickedPosition 获取拾取点坐标
  • KeyDown 通知键盘按键按下
    • {Number} KeyDown.key 键值 ID
  • KeyPress 通知键盘按键一直被按下
    • {THING.KeyType} KeyPress.key 键值 ID
  • KeyUp 通知键盘按键抬起
    • {THING.KeyType} KeyUp.key 键值 ID
  • CameraChangeStart 通知摄像机位置变动开始
    • {Array} CameraChangeStart.target 摄像机观察点世界坐标信息
    • {Array} CameraChangeStart.position 摄像机世界坐标信息
  • CameraChangeEnd 通知摄像机位置变动结束
    • {Array} CameraChangeEnd.target 摄像机观察点世界坐标信息
    • {Array} CameraChangeEnd.position 摄像机世界坐标信息
  • CameraChange 通知摄像机位置变动中
    • {Array} CameraChange.target 摄像机观察点世界坐标信息
    • {Array} CameraChange.position 摄像机世界坐标信息
  • CameraViewChange 通知摄像机观察模式改动
    • {THING.CameraView} CameraViewChange.view 摄像机类型
  • Create 通知物体创建完成
    • {THING.BaseObject} Create.object 物体
  • Expand 通知物体被展开
    • {THING.BaseObject} Expand.object 物体
  • Unexpand 通知物体被合并
    • {THING.BaseObject} Unexpand.object 物体
  • PickChange 通知物体拾取对象更新
    • THING.Selector} PickChange.objects 当前拾取物体列表
    • {THING.Selector} PickChange.previousObjects 之前的拾取物体列表
  • AreaPickStart 通知框选开始
  • AreaPicking 通知框选中
    • {THING.Selector} AreaPicking.objects 拾取物体列表
  • AreaPickEnd 通知框选结束
  • Select 通知物体被选择
    • {THING.Selector} Select.objects 物体列表
  • Deselect 通知物体被取消选择
    • {THING.Selector} Deselect.objects 物体列表
  • SelectionChange 通知物体选择集合更新
    • {THING.Selector} SelectionChange.objects 物体集合
  • LevelChange 通知场景层次发生改变
    • {THING.LevelType} LevelChange.level 当前层级标识
    • {THING.BaseObject} LevelChange.current 当前层级
    • {THING.BaseObject} LevelChange.previous 上一层级
  • EnterLevel 通知进入物体层级
    • {THING.BaseObject} EnterLevel.object 当前层级
    • {THING.BaseObject} EnterLevel.current 当前层级
    • {THING.BaseObject} EnterLevel.previous 上一层级
  • LeaveLevel 通知退出物体层级
    • {THING.BaseObject} LeaveLevel.object 当前层级
    • {THING.BaseObject} LeaveLevel.current 当前层级
    • {THING.BaseObject} LeaveLevel.previous 上一层级(离开的层级)
  • LevelFlyEnd 通知摄像机飞入物体层级进入完成
    • {THING.BaseObject} EnterLevel.object 当前层级
    • {THING.BaseObject} EnterLevel.current 当前层级
    • {THING.BaseObject} EnterLevel.previous 上一层级
  • LevelFlyEnd 通知摄像机飞入物体层级进入完成
    • {THING.BaseObject} LevelFlyEnd.object 当前层级
    • {THING.BaseObject} LevelFlyEnd.current 当前层级
在线咨询
QQ交流群
微信公众号