Skip to content

事件

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): 图层标识,同 clickLayerlayers 参数
  • htmlFunc (HtmlFunction): HTML 生成函数,接收两个参数:
    • feature: 被点击的要素对象
    • popup: Popup 实例(可用于动态更新内容)
    • 返回 stringPromise<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();

注意事项

  1. 事件时机: 确保在地图 loadComplete 后再添加交互事件
  2. 性能考虑: 避免在频繁触发的事件中执行复杂操作
  3. 内存管理: 及时移除不需要的事件监听,避免内存泄漏
  4. 事件顺序: 了解事件的触发顺序有助于正确处理逻辑
  5. 移动端适配: 移动端优先使用触摸事件而非鼠标事件