事件
SDK 提供了完整的事件系统,可以监听地图的各种交互和状态变化。
事件监听
添加事件监听
javascript
// 监听事件
sdk.on("click", (e) => {
console.log("点击事件:", e);
});
// 监听特定图层的事件
sdk.on("click", "airport", (e) => {
console.log("点击了机场图层:", e.features[0]);
});移除事件监听
javascript
// 定义事件处理函数
const handler = (e) => {
console.log("点击事件:", e);
};
// 添加监听
sdk.on("click", handler);
// 移除监听
sdk.off("click", handler);
// 移除所有监听
sdk.off("click");一次性事件监听
javascript
// 只监听一次
sdk.once("loadComplete", () => {
console.log("地图加载完成(只执行一次)");
});地图生命周期事件
load(样式加载完成)
javascript
sdk.on("load", () => {
console.log("地图样式已加载");
// 此时可以安全地操作地图样式
});loadComplete(初始化完成)
javascript
sdk.on("loadComplete", (event) => {
console.log("地图初始化完成");
console.log("NOTAM管理器:", event.notam);
console.log("ADSB管理器:", event.adsb);
// 此时所有功能都已就绪
setupFeatures();
});reinit(重新初始化)
javascript
sdk.on("reinit", (event) => {
console.log("地图重新初始化完成");
console.log("新期数:", event.period);
});error(错误事件)
javascript
sdk.on("error", (error) => {
console.error("地图错误:", error);
});交互事件
鼠标事件
click(点击)
基础点击事件监听:
javascript
sdk.on("click", (e) => {
console.log("点击位置:", e.lngLat);
console.log("点击的要素:", e.features);
console.log("点击的像素位置:", e.point);
});
// 监听特定图层点击
sdk.on("click", "airport", (e) => {
const feature = e.features[0];
console.log("点击了机场:", feature.properties);
});clickLayer(点击图层)
clickLayer 方法专门用于监听特定图层的点击事件,支持多种图层选择方式,并自动过滤只返回指定图层的要素。
语法:
javascript
sdk.clickLayer(layers, callback)参数:
layers(LayerRef): 图层标识,支持以下类型:string: 单个图层ID,如"airport"Array<string>: 多个图层ID,如["airport", "airline"]RegExp: 正则表达式匹配图层ID,如/^airport.*/Function: 函数过滤图层,如(layer) => layer.id.startsWith("airport")
callback(Function): 回调函数,接收事件对象e,包含:e.lngLat: 点击的地理坐标{ lng, lat }e.point: 点击的像素坐标{ x, y }e.features: 点击到的要素数组(仅包含指定图层的要素)e.originalEvent: 原始鼠标事件对象
返回值: 返回一个卸载函数,调用后可以移除事件监听。
示例:
javascript
// 监听单个图层
const offHandler = sdk.clickLayer("airport", (e) => {
console.log("点击了机场图层");
console.log("要素:", e.features[0]);
console.log("位置:", e.lngLat);
});
// 监听多个图层
sdk.clickLayer(["airport", "airline", "airline_label"], (e) => {
const feature = e.features[0];
console.log("点击的图层:", feature.layer.id);
console.log("要素属性:", feature.properties);
});
// 使用正则表达式匹配图层
sdk.clickLayer(/^airport.*/, (e) => {
console.log("点击了以 airport 开头的图层");
});
// 使用函数过滤图层
sdk.clickLayer((layer) => {
return layer.type === "symbol" && layer.id.includes("label");
}, (e) => {
console.log("点击了符号标签图层");
});
// 移除事件监听
offHandler();与 sdk.on("click", layer, callback) 的区别:
clickLayer会自动过滤并只返回指定图层的要素clickLayer支持更灵活的图层选择方式(数组、正则、函数)clickLayer返回卸载函数,便于管理事件生命周期
clickPopup(点击弹窗)
clickPopup 方法用于在点击图层要素时自动显示弹窗,支持自定义 HTML 内容和异步数据加载。
语法:
javascript
sdk.clickPopup(layers, htmlFunc, popupOptions)参数:
layers(LayerRef): 图层标识,同clickLayer的layers参数htmlFunc(HtmlFunction): HTML 生成函数,接收两个参数:feature: 被点击的要素对象popup: Popup 实例(可用于动态更新内容)- 返回
string或Promise<string>(支持异步加载)
popupOptions(PopupOptions, 可选): Popup 配置选项,如:anchor: 弹窗锚点位置 ("center","top","bottom","left","right","top-left","top-right","bottom-left","bottom-right")closeButton: 是否显示关闭按钮(默认true)closeOnClick: 点击地图是否关闭(默认true)maxWidth: 最大宽度- 更多选项参考 MapLibre Popup 文档
返回值: 返回一个卸载函数,调用后可以移除事件监听和弹窗。
示例:
javascript
// 基础用法:显示静态 HTML
sdk.clickPopup("airport", (feature) => {
const props = feature.properties;
return `
<div style="padding: 10px;">
<h3>${props.name || "机场"}</h3>
<p>ICAO: ${props.icao || "N/A"}</p>
<p>IATA: ${props.iata || "N/A"}</p>
</div>
`;
});
// 自定义弹窗位置和样式
sdk.clickPopup("airport", (feature) => {
return `<div>${feature.properties.name}</div>`;
}, {
anchor: "left",
closeButton: true,
maxWidth: "300px"
});
// 异步加载数据
sdk.clickPopup("airport", async (feature) => {
const airportId = feature.properties.id;
// 显示加载状态(SDK 会自动处理)
const response = await fetch(`/api/airport/${airportId}`);
const data = await response.json();
return `
<div style="padding: 10px;">
<h3>${data.name}</h3>
<p>状态: ${data.status}</p>
<p>跑道数: ${data.runways.length}</p>
</div>
`;
});
// 使用 popup 实例动态更新内容
sdk.clickPopup("airport", async (feature, popup) => {
// 初始内容
popup.setHTML('<div>加载中...</div>');
// 异步加载
const data = await loadAirportData(feature.properties.id);
// 更新内容
return `
<div>
<h3>${data.name}</h3>
<button onclick="alert('详情')">查看详情</button>
</div>
`;
});
// 监听多个图层
sdk.clickPopup(["airport", "airline"], (feature) => {
const layerId = feature.layer.id;
if (layerId === "airport") {
return `<div>机场: ${feature.properties.name}</div>`;
} else {
return `<div>航线: ${feature.properties.name}</div>`;
}
});
// 移除弹窗监听
const offPopup = sdk.clickPopup("airport", (f) => `<div>${f.properties.name}</div>`);
// 稍后移除
offPopup();错误处理: 如果 htmlFunc 抛出错误或返回 Promise 被拒绝,弹窗会自动显示错误信息。
最佳实践:
javascript
// 1. 在 loadComplete 后再设置弹窗
sdk.on("loadComplete", () => {
sdk.clickPopup("airport", async (feature) => {
// 安全地访问地图和要素
return generatePopupHTML(feature);
});
});
// 2. 使用防抖处理频繁点击
let popupTimeout;
sdk.clickPopup("airport", (feature) => {
clearTimeout(popupTimeout);
return new Promise((resolve) => {
popupTimeout = setTimeout(async () => {
const html = await loadData(feature);
resolve(html);
}, 300);
});
});
// 3. 统一管理弹窗生命周期
const popupHandlers = [];
sdk.on("loadComplete", () => {
// 添加多个弹窗
popupHandlers.push(
sdk.clickPopup("airport", (f) => generateAirportPopup(f)),
sdk.clickPopup("airline", (f) => generateAirlinePopup(f))
);
});
// 页面卸载时清理
window.addEventListener("beforeunload", () => {
popupHandlers.forEach(off => off());
});dblclick(双击)
javascript
sdk.on("dblclick", (e) => {
// 双击放大
sdk.zoomIn();
});contextmenu(右键菜单)
javascript
sdk.on("contextmenu", (e) => {
e.preventDefault();
showContextMenu(e.lngLat);
});mouseenter(鼠标进入)
javascript
sdk.on("mouseenter", "airport", (e) => {
sdk.getCanvas().style.cursor = "pointer";
highlightFeature(e.features[0]);
});mouseleave(鼠标离开)
javascript
sdk.on("mouseleave", "airport", () => {
sdk.getCanvas().style.cursor = "";
clearHighlight();
});mousemove(鼠标移动)
javascript
sdk.on("mousemove", (e) => {
updateMousePosition(e.lngLat);
});触摸事件(移动端)
touchstart(触摸开始)
javascript
sdk.on("touchstart", (e) => {
console.log("触摸开始");
});touchmove(触摸移动)
javascript
sdk.on("touchmove", (e) => {
console.log("触摸移动");
});touchend(触摸结束)
javascript
sdk.on("touchend", (e) => {
console.log("触摸结束");
});地图状态事件
move(移动)
javascript
sdk.on("move", () => {
const center = sdk.getCenter();
console.log("地图中心:", center);
});moveend(移动结束)
javascript
sdk.on("moveend", () => {
const bounds = sdk.getBounds();
console.log("地图边界:", bounds);
// 加载当前视野的数据
loadDataInBounds(bounds);
});movestart(移动开始)
javascript
sdk.on("movestart", () => {
console.log("开始移动地图");
});zoom(缩放)
javascript
sdk.on("zoom", () => {
const zoom = sdk.getZoom();
console.log("缩放级别:", zoom);
// 根据缩放级别调整图层
if (zoom > 12) {
sdk.showLayers(["detail-layer"]);
} else {
sdk.hideLayers(["detail-layer"]);
}
});zoomend(缩放结束)
javascript
sdk.on("zoomend", () => {
const zoom = sdk.getZoom();
console.log("缩放结束,当前级别:", zoom);
});zoomstart(缩放开始)
javascript
sdk.on("zoomstart", () => {
console.log("开始缩放");
});rotate(旋转)
javascript
sdk.on("rotate", () => {
const bearing = sdk.getBearing();
console.log("旋转角度:", bearing);
});rotatestart(旋转开始)
javascript
sdk.on("rotatestart", () => {
console.log("开始旋转");
});rotateend(旋转结束)
javascript
sdk.on("rotateend", () => {
console.log("旋转结束");
});pitch(倾斜)
javascript
sdk.on("pitch", () => {
const pitch = sdk.getPitch();
console.log("倾斜角度:", pitch);
});拖拽事件
dragstart(拖拽开始)
javascript
sdk.on("dragstart", () => {
console.log("开始拖拽");
});drag(拖拽中)
javascript
sdk.on("drag", () => {
console.log("拖拽中");
});dragend(拖拽结束)
javascript
sdk.on("dragend", () => {
console.log("拖拽结束");
});数据事件
data(数据加载)
javascript
sdk.on("data", (e) => {
console.log("数据加载:", e.dataType);
console.log("源ID:", e.sourceId);
console.log("是否源已加载:", e.isSourceLoaded);
});sourcedata(源数据加载)
javascript
sdk.on("sourcedata", (e) => {
if (e.sourceId === "custom-source" && e.isSourceLoaded) {
console.log("源数据已加载");
}
});styledata(样式数据加载)
javascript
sdk.on("styledata", (e) => {
console.log("样式数据加载:", e.dataType);
});渲染事件
render(渲染)
javascript
sdk.on("render", () => {
// 每次地图渲染时触发
// 注意:此事件触发频率很高,避免执行复杂操作
});idle(空闲)
javascript
sdk.on("idle", () => {
// 地图处于空闲状态(无动画、无加载)
console.log("地图空闲");
});自定义事件
触发自定义事件
javascript
// 触发自定义事件
sdk.map.fire("custom-event", {
data: "自定义数据",
});
// 监听自定义事件
sdk.on("custom-event", (e) => {
console.log("自定义事件:", e.data);
});事件对象
MapMouseEvent
鼠标事件对象包含以下属性:
javascript
sdk.on("click", (e) => {
console.log("位置:", e.lngLat); // { lng, lat }
console.log("像素位置:", e.point); // { x, y }
console.log("原始事件:", e.originalEvent);
console.log("目标:", e.target);
console.log("类型:", e.type);
});MapTouchEvent
触摸事件对象:
javascript
sdk.on("touchstart", (e) => {
console.log("触摸点:", e.point);
console.log("触摸位置:", e.lngLat);
console.log("原始事件:", e.originalEvent);
});事件处理最佳实践
1. 事件清理
javascript
// 保存事件处理函数引用
const handlers = {
click: (e) => console.log("click", e),
zoom: () => console.log("zoom"),
};
// 添加监听
Object.entries(handlers).forEach(([event, handler]) => {
sdk.on(event, handler);
});
// 页面卸载时清理
window.addEventListener("beforeunload", () => {
Object.entries(handlers).forEach(([event, handler]) => {
sdk.off(event, handler);
});
});2. 防抖处理
javascript
// 使用防抖处理频繁触发的事件
let zoomTimeout;
sdk.on("zoom", () => {
clearTimeout(zoomTimeout);
zoomTimeout = setTimeout(() => {
// 执行操作
updateUI();
}, 300);
});3. 事件委托
javascript
// 使用事件委托处理多个图层
sdk.on("click", (e) => {
const features = e.features;
if (features.length > 0) {
const feature = features[0];
const layerId = feature.layer.id;
switch (layerId) {
case "airport":
handleAirportClick(feature);
break;
case "airline":
handleAirlineClick(feature);
break;
default:
handleDefaultClick(feature);
}
}
});完整示例
javascript
let sdk;
async function initMap() {
sdk = new navMap.MapSDK({
container: "map",
center: [116.39, 39.9],
zoom: 10,
});
setupEventHandlers();
}
function setupEventHandlers() {
// 1. 生命周期事件
sdk.on("load", () => {
console.log("地图样式已加载");
});
sdk.on("loadComplete", (event) => {
console.log("地图初始化完成");
setupInteractions();
});
// 2. 交互事件
sdk.on("click", (e) => {
console.log("点击位置:", e.lngLat);
const features = sdk.queryRenderedFeatures(e.point);
if (features.length > 0) {
showFeatureInfo(features[0]);
}
});
// 3. 状态事件
sdk.on("zoom", () => {
const zoom = sdk.getZoom();
updateZoomIndicator(zoom);
// 根据缩放级别调整图层
if (zoom > 12) {
sdk.showLayers(["detail-layer"]);
} else {
sdk.hideLayers(["detail-layer"]);
}
});
sdk.on("moveend", () => {
const bounds = sdk.getBounds();
loadDataInBounds(bounds);
});
// 4. 错误处理
sdk.on("error", (error) => {
console.error("地图错误:", error);
showErrorMessage(error);
});
}
function setupInteractions() {
// 图层特定事件
sdk.on("mouseenter", "airport", (e) => {
sdk.getCanvas().style.cursor = "pointer";
highlightFeature(e.features[0]);
});
sdk.on("mouseleave", "airport", () => {
sdk.getCanvas().style.cursor = "";
clearHighlight();
});
}
initMap();注意事项
- 事件时机: 确保在地图
loadComplete后再添加交互事件 - 性能考虑: 避免在频繁触发的事件中执行复杂操作
- 内存管理: 及时移除不需要的事件监听,避免内存泄漏
- 事件顺序: 了解事件的触发顺序有助于正确处理逻辑
- 移动端适配: 移动端优先使用触摸事件而非鼠标事件
