外观
事件
约 2837 字大约 9 分钟
2023-09-08
无论是前端系统,还是二维/三维GIS应用系统,都离不开各种事件的应用,尤其是鼠标的单击、双击事件。 Mars3D 内所有类均支持事件的绑定、解绑、触发等统一的事件机制,事件类型全部定义在mars3d.EventType。
1. 事件的相关类
事件相关的更多方法可以参阅 BaseClass类的API文档。
事件方法中使用到所有支持的事件类型,都统一定义在mars3d.EventType枚举中。在每个对应类的API中都会说明当前类所支持的事件类型,具体参阅各类的API文档。
1.1 事件订阅
通过obj.on(types, fn, context) 方法订阅事件, 参数如下:
/**
* 绑定指定类型事件监听器,
* 支持在监听中调用 event.stopPropagation(); 阻止事件冒泡
*
* @param {EventType|string|EventType[]|string[]|object} types 事件类型
* @param {Function} fn 绑定的监听器回调方法
* @param {object} [context] 侦听器的上下文(this关键字将指向的对象)。
* @return {BaseClass} 当前对象本身,可以链式调用
*
*/
on(types, fn, context)如果只需要响应一次,可以使用 once(types, fn, context),监听器被触发一次后会自动删除,参数与 on 完全一致。
1.2 事件取消订阅
通过obj.off(types, fn, context) 方法取消订阅事件, 参数如下:
/**
* 解除绑定指定类型事件监听器
*
* @param {EventType|string|EventType[]} [types] 事件类型,未传值时解绑所有事件
* @param {Function} [fn] 绑定的监听器回调方法,未传值时解绑所有指定类型对应事件,特殊说明:map.on监听的Cesium相关原生事件时必须传入该参数
* @param {object} [context] 侦听器的上下文(this关键字将指向的对象)。
* @return {BaseClass} 当前对象本身,可以链式调用
*
*/
off(types, fn, context)也可以通过 obj.fire(type, data) 手动触发一个事件,在调试或自定义对象时比较有用。
1.3 使用示例
下面,我们以 map对象为例,绑定一个事件做示例:
function map_cameraChangedHandler(event) {
console.log("相机位置完成", event)
}
// on绑定事件
map.on(mars3d.EventType.cameraChanged, map_cameraChangedHandler, this)
// off移除事件
map.off(mars3d.EventType.cameraChanged, map_cameraChangedHandler, this)
// once绑定一次性事件
map.once(mars3d.EventType.load, (event) => {
console.log("地图加载完成,只触发一次", event)
})2. 主要类的常用事件
2.1 Map类的事件
目前最重要的Map类支持3类事件类型可以绑定:
- Map内部图层变化时的事件,如
addLayer、removeLayer等 - 相机、时钟、场景等变化触发的事件,如
cameraMoveStart、preUpdate、clockTick等 - 鼠标键盘操作触发的事件,如
click、mouseMove、mouseOver、keydown等
详细事件列表,请参阅:Map支持的事件类型
2.2 图层类的事件
图层类型可以绑定多种事件,我们以最常用的GraphicLayer图层为例:
- 图层内部变化时的事件,如
add、show、addGraphic等 - 鼠标操作触发的事件,如
click、rightClick、mouseOver、mouseOut等 - 标绘和编辑触发的事件,如
drawStart、drawCreated、editStart、editStop等
详细事件列表,请参阅:GraphicLayer支持的事件类型
2.3 矢量数据类的事件
矢量数据类型可以绑定多种事件,我们以最常用的Entity矢量数据为例:
- 数据内部变化时的事件,如
add、remove、show等 - 鼠标操作触发的事件,如
click、rightClick、mouseOver、mouseOut等 - 标绘和编辑触发的事件,如
drawStart、drawCreated、editStart、editStop等
详细事件列表,请参阅:Graphic支持的事件类型
3. 鼠标事件回调对象
通过 on 绑定鼠标事件后,回调方法会收到一个统一的事件对象 event,常用属性如下:
| 属性 | 说明 |
|---|---|
event.windowPosition | Cartesian2 屏幕像素坐标,是拾取的原始输入 |
event.cartesian | Cartesian3 拾取位置的世界坐标,点不到任何对象时可能为 undefined |
event.graphic | 拾取到的 mars3d 矢量对象(Graphic),取不到时为 undefined |
event.pickedObject | 拾取到的 Cesium 原生对象,如 Cesium3DTileFeature 模型构件 |
event.stopPropagation() | 阻止事件继续向父级冒泡,详见第 5 节 |
// 在图层上监听单击,直接拿到被点中的矢量
graphicLayer.on(mars3d.EventType.click, (event) => {
console.log("单击了矢量", event.graphic)
if (event.cartesian) {
// 世界坐标转经纬度,坐标转换详见《坐标转换》章节
const point = mars3d.LngLatPoint.fromCartesian(event.cartesian)
console.log("经度、纬度、高度", point.lng, point.lat, point.alt)
}
})事件类型中除了通用的
click,还提供更语义化的clickGraphic(单击到矢量或模型数据时触发)和clickTileGraphic(单击到 WMS、ArcGIS 等瓦片服务的要素时触发),可以减少在回调里自行判断的代码。完整列表见 EventType。
4. map.mouseEvent 与主动拾取
4.1 订阅事件与主动拾取的区别
map.on(EventType.click, fn)是订阅式:用户操作后被动收到回调,绝大多数交互场景用它即可。- map.mouseEvent 是 Map 内部创建的鼠标统一管理对象,提供主动拾取能力:当你需要在自己的业务逻辑里(不是点击回调里)手动查询某个屏幕位置/地理坐标上的对象时使用。
| 方法 | 说明 |
|---|---|
| map.mouseEvent.getPicked(event) | 传入鼠标事件对象,获取拾取到的 Cesium 选中对象 |
| map.mouseEvent.pickImageryLayerFeatures(position, clampToTileset) | 主动查询 WMS/WMTS/ArcGIS 瓦片服务上的要素,返回 Promise,第二个参数用于贴模型瓦片图层 |
mouseEvent 还支持以下常用配置(构造地图时通过 mouse 参数传入,或用 setOptions 动态修改,详见 Map.mouseOptions):
| 参数 | 说明 |
|---|---|
enabledMoveTarget | 是否开启鼠标移动时的矢量拾取,关闭后不触发 mouseOver 等事件,可提升性能 |
moveDelay | 鼠标移动事件的延迟毫秒数,默认 30,移动拾取频繁时可调大 |
noPickEntity | 是否完全不拾取数据,纯展示场景可关闭拾取 |
const map = new mars3d.Map("mars3dContainer", {
mouse: {
enabledMoveTarget: true,
moveDelay: 30
}
// ...其他配置
})
// 运行中动态调整
map.mouseEvent.setOptions({ moveDelay: 100 })4.2 拾取(Picking)方法选型
拾取解决的是"屏幕上的这个像素,对应三维世界里的哪个对象/哪个坐标"。除了直接用事件回调给出的结果,也可以调用 Cesium 原生方法主动拾取:
| 需求 | API | 说明 |
|---|---|---|
| 拾取 mars3d 矢量 | event.graphic / map.mouseEvent.getPicked(event) | mars3d 体系下首选,自动关联到 Graphic |
| 拾取 3DTiles 模型构件 | event.pickedObject 判断 Cesium.Cesium3DTileFeature | 取属性推荐用 mars3d.Util.get3DTileFeatureAttr(feature),详见 3DTiles模型 4.2 节 |
| 拾取地形/模型表面坐标 | scene.pickPosition(windowPosition) | 依赖深度信息,地形或模型已加载时才有结果 |
| 射线与地球表面求交 | globe.pick(camera.getPickRay(windowPosition), scene) | 只与地形表面求交,pickPosition 取不到时的兜底方案 |
| 拾取椭球面坐标(忽略地形起伏) | camera.pickEllipsoid(windowPosition) | 得到的是 WGS84 椭球上的点,不含地形高度 |
| 同一位置穿透拾取多个对象 | scene.drillPick(windowPosition, limit) | 需要逐个计算,开销明显大于 pick,避免在 mouseMove 中高频调用 |
相关示例:map 相关事件、坐标拾取和转换、图上量算;3DTiles 构件拾取见 3DTiles模型 4.2 节。
4.3 地形深度检测对拾取的影响
开启 globe.depthTestAgainstTerrain 后,被地形遮挡的矢量在显示和拾取时都会被挡住,可以保证"看到才能点到";但如果矢量坐标本身在地面以下,它会完全点不中。矢量在地面漂移、拾取位置异常时的完整处理见常见问题:加载的矢量数据在地面漂移。
4.4 WMS / WMTS / ArcGIS 瓦片要素拾取
影像瓦片本身是图片,单击查询要素依赖服务端的 GetFeatureInfo/identify 能力,与矢量拾取机制不同:
- 目前只有
wms、wmts、arcgis类型图层默认具备要素单击;普通xyz图层需要配置enablePickFeatures、pickFeaturesUrl、getFeatureInfoFormats等参数后才支持。 - 要素单击对应
EventType.clickTileGraphic事件,也可以在任意时机手动调用map.mouseEvent.pickImageryLayerFeatures(position)查询(如卷帘对比时只查一侧、自定义节流)。 - 不建议在
mouseMove中查询:鼠标每次移动都会请求服务,单击一次查询一次更合理。 - 高亮轮廓与瓦片图斑对不上时,是服务端
GetFeatureInfo返回的几何决定的,需要调整服务(如 GeoServer 返回面几何),不是前端绘制问题。
功能示例:WMS 服务;更多细节见常见问题:瓦片图层 - WMS/xyz 单击相关(在新窗口打开)。
5. 事件冒泡与拦截
5.1 冒泡链路
矢量、图层、地图是三层结构,鼠标事件默认按 矢量(graphic) → 所属图层(layer) → 地图(map) 的顺序向上冒泡:
const graphic = new mars3d.graphic.PointEntity({ position: [116.1, 30.0, 100] })
graphicLayer.addGraphic(graphic)
graphic.on(mars3d.EventType.click, (event) => {
console.log("1. 矢量自己先收到")
})
graphicLayer.on(mars3d.EventType.click, (event) => {
console.log("2. 冒泡到图层,event.graphic 是被点中的矢量")
event.stopPropagation() // 阻止后,map 将收不到本次事件
})
map.on(mars3d.EventType.click, () => {
console.log("3. 冒泡到地图")
})5.2 控制冒泡的两种常用方式
- 回调内:调用
event.stopPropagation()阻止本次事件继续冒泡。 - 构造参数:矢量的
eventParent可以指定冒泡目标对象,设为false时不向上冒泡(默认冒泡到所属图层);图层、控件等类还支持stopPropagation配置项,详见 BaseGraphic 构造参数。
另外,矢量构造参数中的 allowDrillPick 用于控制鼠标是否可以穿透当前对象拾取到下层对象,存在上下重叠数据时按需关闭。
6. "事件不触发"排查清单
单击没有反应时,建议在回调第一行先 console.log(event),按下面顺序排查:
- 对象是否已加入地图:图层需要
map.addLayer(layer),矢量需要layer.addGraphic(graphic),未加入的对象不参与拾取。 - 监听是否挂对了对象:图层类事件(如
drawCreated、图层内矢量单击)要绑在 layer 上;绑在 map 上时依赖冒泡,若中间层调用了stopPropagation()就收不到。 - 是否被提前解绑:
map.on监听 Cesium 原生事件时,off必须传入同一个 fn(见 1.2 节说明);注意组件卸载、map.destroy()后事件会全部失效。 - 影像底图不支持要素单击:普通 xyz 瓦片是图片,默认点不出要素,只有 wms/wmts/arcgis 配置了要素查询才支持(见 4.4 节)。
- 被上层对象遮挡:上层不透明图层、DivGraphic 覆盖物、Video3D 后处理画面都会挡住下面的对象,图层顺序问题见 zIndex层级,视频遮挡说明见 视频矢量相关。
- 深度检测与坐标高度:开启
depthTestAgainstTerrain后地面以下的矢量点不中;数据在视域外或坐标写错也会"看起来没反应",见 加载的矢量不显示。 - Popup 不弹不等于事件没触发:先确认 click 回调能否进入,再排查 popup 绑定与模板配置,见 Popup弹窗(在新窗口打开)。
