源码分析之Leaflet中Marker

概述

Marker类用于创建一个标记点对象,可以用于在地图上添加标记点。Marker类继承自Layer类,提供了一些方法用于创建标记点对象。

源码分析

源码实现

Marker类实现如下:

js 复制代码
export var Marker = Layer.extend({
  options: {
    icon: new IconDefault(), // 默认图标实例
    interactive: true, // 是否可交互(点击/悬停)
    keyboard: true, // 是否支持键盘导航
    title: "", // 图标的title属性
    alt: "Marker", // 图标的alt属性
    zIndexOffset: 0, // 控制图层堆叠顺序
    opacity: 1, // 图标透明度
    riseOnHover: false, // 是否在悬停时提升图层
    riseOffset: 250, // 上浮的z-index偏移量
    pane: "markerPane", // 图标挂载的DOM窗格
    shadowPane: "shadowPane", // 阴影挂载的DOM窗格
    bubblingMouseEvents: false, // 阻止鼠标事件冒泡
    autoPanOnFocus: true, // 聚焦时自动平移地图
    draggable: false, // 是否可拖动图标
    autoPan: false, // 拖动时是否自动平移地图
    autoPanPadding: [50, 50],// 自动平移的填充距离
    autoPanSpeed: 10, // 自动平移的速度
  },
  initialize: function (latlng, options) {
    Util.setOptions(this, options);
    this._latlng = latLng(latlng); // 标准化经纬度
  },
  onAdd: function (map) {
    this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;

    if (this._zoomAnimated) {
      map.on("zoomanim", this._animateZoom, this); // 监听缩放动画
    }

    this._initIcon(); // 创建图标和阴影
    this.update(); // 更新图标位置
  },
  // 清理资源
  onRemove: function (map) {
    if (this.dragging && this.dragging.enabled()) {
      this.dragging.draggable = true;
      this.dragging.removeHooks(); // 清理图标拖动事件
    }

    delete this.dragging;

    if (this._zoomAnimated) {
      map.off("zoomanim", this._animateZoom, this); // 取消事件监听
    }

    this._removeIcon(); // 移除图标 DOM
    this._removeShadow(); // 移除阴影 DOM
  },
  getEvents: function () {
    // 定义事件
    return {
      zoom: this.update,
      viewreset: this.update,
    };
  },
  getLatLng: function () {
    return this._latlng; // 获取图标经纬度
  },
  setLatLng: function (latlng) {
    var oldLatLng = this._latlng;
    this._latlng = latLng(latlng); // 更新坐标
    this.update(); // 刷新位置
    return this.fire("move", { oldLatLng: oldLatLng, latLng: this._latlng }); // 触发移动事件
  },
  setZIndexOffset: function (offset) {
    this.options.zIndexOffset = offset; //更新zIndexOffset属性
    return this.update(); // 更新图标位置
  },
  getIcon: function () {
    return this.options.icon; // 获取图标实例
  },
  setIcon: function (icon) { // 设置图标实例
    if (this.options.icon) { // 移除旧的图标
      this._removeIcon();
    }
    this.options.icon = icon; // 更新图标

    if (this._map) {
      this._initIcon(); // 初始化图标
      this.update(); // 更新位置
    }

    if (this._popup) {
      this.bindPopup(this._popup, this._popup.options);
    }
    return this;
  },
  getElement: function () {
    return this._icon; // 获取图标的DOM元素
  },
  update: function () {
    if (this._icon && this._map) {
      var pos = this._map.latLngToLayerPoint(this._latlng).round(); // 将经纬度转换为屏幕坐标
      this._setPos(pos); // 设置图标的位置
    }
  },
  _initIcon: function () { // 初始化图标,创建图标以及阴影图标
    var options = this.options,
      classToAdd = "leaflet-zoom-" + (this._zoomAnimated ? "animated" : "hide");

    var icon = options.icon.createIcon(this._icon),
      addIcon = false;

    if (icon !== this._icon) {
      if (this._icon) {
        this._remove();
      }

      addIcon = true;

      if (options.title) {
        icon.title = options.title;
      }

      if (icon.tagName == "IMG") {
        icon.alt = options.alt || "";
      }
    }

    DomUtil.addClass(icon, classToAdd);

    if (options.keyboard) {
      icon.tabIndex = "0";
      icon.setAttribute("role", "button");
    }

    this._icon = icon;
    if (options.riseOnHover) {
      this.on({
        mouseover: this._bringToFront,
        mouseout: this._resetZIndex,
      });
    }

    if (this.options.autoPanOnFocus) {
      DomEvent.on(icon, "focus", this._panOnFocus, this);
    }

    var newShadow = options.icon.createShadow(this._shadow),
      addShadow = false;

    if (newShadow !== this._shadow) {
      this._removeShadow();
      addShadow = true;
    }

    if (newShadow) {
      DomUtil.addClass(newShadow, classToAdd);
      newShadow.alt = "";
    }
    this._shadow = newShadow;

    if (options.opacity < 1) {
      this._updateOpacity();
    }

    if (addIcon) {
      this.getPane().appendChild(this._icon);
    }
    this._initInteraction();
    if (newShadow && addShadow) {
      this.getPane(options.shadowPane).appendChild(this._shadow);
    }
  },
  _removeIcon: function () {
    // 移除图标
    if (this.options.riseOnHover) { //若果 riseOnHover 为 true,则移除鼠标悬停事件
      this.off({
        mouseover: this._bringToFront,
        mouseout: this._resetZIndex,
      });
    }

    if (this.options.autoPanOnFocus) { // 若果 autoPanOnFocus 为 true,则移除焦点事件
      DomEvent.off(this._icon, "focus", this._panOnFocus, this);
    }

    DomUtil.remove(this._icon); // 移除图标 DOM
    this.removeInteractiveTarget(this._icon); // 移除交互目标

    this._icon = null; // 清空图标
  },
  _removeShadow: function () { // 移除阴影图标
    if (this._shadow) {
      DomUtil.remove(this._shadow);
    }
    this._shadow = null;
  },
  _setPos: function (pos) {
    // 内部方法:更新图标位置,通过DomUtil.setPosition更新位置
    if (this._icon) {
      DomUtil.setPosition(this._icon, pos);
    }

    if (this._shadow) {
      DomUtil.setPosition(this._shadow, pos);
    }

    this._zIndex = pos.y + this.options.zIndexOffset;

    this._resetZIndex();
  },
  _updateZIndex: function (offset) {
    // 内部方法:更新图标层级
    if (this._icon) {
      this._icon.style.zIndex = this._zIndex + offset;
    }
  },
  _animateZoom: function () {
    // 内部方法:处理缩放动画
    var pos = this._map
      ._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center)
      .round();
      // 在缩放动画中平滑更新位置
    this._setPos(pos);
  },
  _initInteraction: function () {
    // 初始化图标交互行为,判断图标是否支持拖动交互
    if (!this.options.interactive) {
      return;
    }

    DomUtil.addClass(this._icon, "leaflet-interactive");

    if (MarkerDrag) {
      var draggable = this.options.draggable;
      if (this.dragging) {
        draggable = this.dragging.enabled();
        this.dragging.disable();
      }

      this.dragging = new MarkerDrag(this);

      if (draggable) {
        this.dragging.enable();
      }
    }
  },
  setOpacity: function (opacity) {
    // 修改图标透明度
    this.options.opacity = opacity;
    if (this._map) {
      this._updateOpacity();
    }

    return this;
  },
  _updateOpacity: function () {
    // 内部方法:修改图标透明度
    var opacity = this.options.opacity;
    if (this._icon) {
      DomUtil.setOpacity(this._icon, opacity);
    }
    if (this._shadow) {
      DomUtil.setOpacity(this._shadow, opacity);
    }
  },
  _bringToFront: function () {
    this._updateZIndex(this.options.riseOffset); // 上浮图标层级
  },
  _resetZIndex: function () {
    this._updateZIndex(0); // 重置图标层级
  },
  _panOnFocus: function () {
    // 自动平移
    var map = this._map;
    if (!map) {
      return;
    }

    var iconOpts = this.options.icon.options;
    var size = iconOpts.iconSize ? point(iconOpts.iconSize) : point(0, 0);
    var anchor = iconOpts.iconAnchor ? point(iconOpts.iconAnchor) : point(0, 0);

    map.panInside(this._latlng, {
      paddingTopLeft: anchor,
      paddingBottomRight: size.subtract(anchor),
    }); // 平移地图确保图标可见
  },
  _getPopupAnchor: function () {
    return this.options.icon.options.popupAnchor;
  },
  _getTooltipAnchor: function () {
    return this.options.icon.options.tooltipAnchor;
  },
});

// 工厂函数,接受经纬度以及其它选项
export function marker(latlng, options) {
  return new Marker(latlng, options);
}

源码详解

Marker类继承自Layer类,复用图层管理、事件等基础功能。

核心方法
  • initialize:初始化方法,接受latlng(坐标)和options,设置默认选项和初始化_latlng属性。
  • onAdd:添加到地图时的回调方法,初始化图标和阴影,设置事件监听。
  • onRemove:从地图移除时的回调方法,清理资源。
  • getEvents:返回事件映射,用于绑定事件。
  • getLatLng:获取标记点的经纬度。
  • setLatLng:设置标记点的经纬度,并触发移动事件。
  • setZIndexOffset:设置标记点的zIndex偏移量。
  • getIcon:获取标记点的图标实例。
  • setIcon:设置标记点的图标实例。
  • getElement:获取标记点的DOM元素。
  • update:更新标记点的位置。
  • _initIcon:初始化图标,创建图标和阴影。
  • _removeIcon:移除图标。
  • _removeShadow:移除阴影。
  • _setPos:设置标记点的位置。
  • _updateZIndex:更新标记点的zIndex。
  • _animateZoom:缩放动画回调方法。
  • _initInteraction:初始化交互功能。
  • setOpacity:设置标记点的透明度。
  • _updateOpacity:更新标记点的透明度。
  • _bringToFront:将标记点提升到最前面。
  • _resetZIndex:重置标记点的zIndex。
  • _panOnFocus:焦点事件回调方法,自动平移地图。
  • _getPopupAnchor:获取弹出框的锚点。
  • _getTooltipAnchor:获取提示框的锚点。

总结

  1. 核心功能​:将经纬度坐标转换为屏幕位置,渲染图标,支持交互(点击、拖动)。
  2. 扩展性 :通过 Icon 类自定义图标,MarkerDrag 实现拖动。
  3. ​性能优化:缩放动画平滑过渡,按需更新 DOM。
  4. 无障碍 :支持键盘导航、alt/title 属性
相关推荐
发呆小天才yy4 小时前
uniapp 微信小程序使用图表
前端·微信小程序·uni-app·echarts
@PHARAOH5 小时前
HOW - 在 Mac 上的 Chrome 浏览器中调试 Windows 场景下的前端页面
前端·chrome·macos
月月大王7 小时前
easyexcel导出动态写入标题和数据
java·服务器·前端
JC_You_Know8 小时前
多语言网站的 UX 陷阱与国际化实践陷阱清单
前端·ux
Python智慧行囊8 小时前
前端三大件---CSS
前端·css
成都渲染101云渲染66669 小时前
blender云渲染指南2025版
前端·javascript·网络·blender·maya
聆听+自律9 小时前
css实现渐变色圆角边框,背景色自定义
前端·javascript·css
牛马程序小猿猴10 小时前
17.thinkphp的分页功能
前端·数据库
huohuopro10 小时前
Vue3快速入门/Vue3基础速通
前端·javascript·vue.js·前端框架