源码分析之Leaflet中Canvas渲染器

概述

Canvas渲染器是Leaflet的一个内置的高效矢量图形渲染器,用于在HTML5的Canvas元素上绘制矢量图形。它继承自Renderer类,提供了基本的渲染功能,如路径绘制、样式设置和事件处理。

源码分析

源码实现

Canvas渲染器源码实现如下:

export var Canvas = Renderer.extend({
  options: {
    tolerance: 0, //控制路径简化精度,用于优化复杂路径的渲染性能,0表示不简化
  },
  getEvents: function () {
    var events = Renderer.prototype.getEvents.call(this);
    events.viewprereset = this._onViewPreReset; // 监听视图预重置事件
    return events;
  },
  _onViewPreReset: function () {
    this._postponeUpdatePaths = true;
  },
  onAdd: function () {
    Renderer.prototype.onAdd.call(this); // 调用父类方法绑定容器
    this._draw(); // 初始绘制
  },
  _initContainer: function () {
    // 创建 Canvas 元素
    var container = (this._container = document.createElement("canvas"));
    
    // 绑定鼠标事件
    DomEvent.on(container, "mousemove", this._onMouseMove, this);
    DomEvent.on(
      container,
      "click dblclick mousedown mouseup contextmenu",
      this._onClick,
      this
    );
    DomEvent.on(container, "mouseout", this._handleMouseOut, this);
    container["_leaflet_disable_events"] = true; // 禁用事件冒泡,避免事件穿透到地图容器
    // 获取 Canvas 2D 上下文
    this._ctx = container.getContext("2d");
  },
  _destroyContainer: function () {
    Util.cancelAnimFrame(this._redrawRequest); // 取消待处理的重绘请求
    delete this._ctx; // 清理上下文
    DomUtil.remove(this._container); // 移除DOM元素
    DomEvent.off(this._container); // 解绑事件
    delete this._container; // 清理DOM元素引用
  },
  _updatePaths: function () {
    if (this._postponeUpdatePaths) {
      return;
    }

    var layer;
    this._redrawBounds = null;
    // 遍历图层,更新图层的像素坐标
    for (var id in this._layers) {
      layer = this._layers[id];
      layer._update();
    }
    this._redraw(); //执行重绘
  },
  _update: function () {
    if (this._map._animatingZoom && this._bounds) {
      return;
    }

    Renderer.prototype._update.call(this);

    var b = this._bounds,
      container = this._container,
      size = b.getSize(),
      m = Browser.retina ? 2 : 1;

    DomUtil.setPosition(container, b.min);
    // Retina 优化
    container.width = m * size.x;
    container.height = m * size.y;
    container.style.width = size.x + "px";
    container.style.height = size.y + "px";

    if (Browser.retina) {
      // Retina 下缩放画布
      this._ctx.scale(2, 2);
    }
    // 调整坐标系偏移
    this._ctx.translate(-b.min.x, -b.min.y);
    // 触发update事件
    this.fire("update");
  },
  // 视图重置后恢复路径更新
  _reset: function () {
    Renderer.prototype._reset.call(this);

    if (this._postponeUpdatePaths) {
      this._postponeUpdatePaths = false;
      this._updatePaths();
    }
  },
  _initPath: function (layer) {
    this._updateDashArray(layer);
    this._layers[Util.stamp(layer)] = layer;
    // 双向链表结构
    var order = (layer._order = {
      layer: layer,
      prev: this._drawLast,
      next: null,
    });
    if (this._drawLast) {
      this._drawLast.next = order;
    }
    this._drawLast = order;
    this._drawFirst = this._drawFirst || this._drawLast;
  },
  _addPath: function (layer) {
    this._requestRedraw(layer);// 添加图层后触发重绘
  },
  _removePath: function (layer) {
    var order = layer._order;
    var next = order.next;
    var prev = order.prev;

    // 从链表中移除节点,更新头尾指针
    if (next) {
      next.prev = prev;
    } else {
      this._drawLast = prev;
    }
    if (prev) {
      prev.next = next;
    } else {
      this._drawFirst = next;
    }

    delete layer._order;

    delete this._layers[Util.stamp(layer)]; // 从图层集合中移除

    this._requestRedraw(layer);
  },
  _updatePath: function (layer) {
    this._extendRedrawBounds(layer);
    layer._project();
    layer._update();
    this._requestRedraw(layer);
  },
  _updateStyle: function (layer) {
    this._updateDashArray(layer);
    this._requestRedraw(layer);
  },
  _updateDashArray: function (layer) {
    if (typeof layer.options.dashArray === "string") {
      var parts = layer.options.dashArray.split(/[, ]+/),
        dashArray = [],
        dashValue,
        i;
      for (i = 0; i < parts.length; i++) {
        dashValue = Number(parts[i]);
        if (isNaN(dashValue)) {
          return;
        }
        dashArray.push(dashValue);
      }
      layer.options._dashArray = dashArray;
    } else {
      layer.options._dashArray = layer.options.dashArray;
    }
  },
  _requestRedraw: function (layer) {
    if (!this._map) {
      return;
    }

    this._extendRedrawBounds(layer);
    this._redrawRequest =
      this._redrawRequest || Util.requestAnimFrame(this._redraw, this); // 合并重绘请求
  },
  _extendRedrawBounds: function (layer) {
    // 计算需重绘的区域
    if (layer._pxBounds) {
      var padding = (layer.options.weight || 0) + 1;
      this._redrawBounds = this._redrawBounds || new Bounds();
      this._redrawBounds.extend(
        layer._pxBounds.min.subtract([padding, padding])
      );
      this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
    }
  },
  _redraw: function () {
    this._redrawRequest = null;
    if (this._redrawBounds) {
      this._redrawBounds.min._floor();
      this._redrawBounds.max._ceil();
    }
    this._clear(); //清空画布或局部区域
    this._draw(); // 重新绘制所有可见图层
    this._redrawBounds = null;
  },
  // 清空画布内容
  _clear: function () {
    var bounds = this._redrawBounds;
    if (bounds) {
      // 局部清除
      var size = bounds.getSize();
      this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
    } else {
      this._ctx.save();
      this._ctx.setTransform(1, 0, 0, 1, 0, 0);
      // 全屏清除
      this._ctx.clearRect(0, 0, this._container.width, this._container.height);
      this._ctx.restore();
    }
  },
  _draw: function () {
    var layer,
      bounds = this._redrawBounds;
    this._ctx.save();
    if (bounds) {
      var size = bounds.getSize();
      this._ctx.beginPath();
      this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
      this._ctx.clip(); // 局部重绘时设置裁剪区域
    }

    this._drawing = true;
    // 遍历链表
    for (var order = this._drawFirst; order; order = order.next) {
      layer = order.layer;
      // 按顺序绘制图层
      if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
        layer._updatePath();
      }
    }

    this._drawing = false;

    this._ctx.restore();
  },
  // 绘制多边形或线段的路径
  _updatePoly: function (layer, closed) {
    if (!this._drawing) {
      return;
    }

    var i,
      j,
      len2,
      p,
      parts = layer._parts,
      len = parts.length,
      ctx = this._ctx;

    if (!len) {
      return;
    }

    ctx.beginPath();

    for (i = 0; i < len; i++) {
      for (j = 0, len2 = parts[i].length; j < len2; j++) {
        p = parts[i][j];
        ctx[j ? "lineTo" : "moveTo"](p.x, p.y);
      }
      //是否闭合
      if (closed) {
        ctx.closePath();
      }
    }

    this._fillStroke(ctx, layer); // 填充和描边
  },
  // 绘制圆形或椭圆
  _updateCircle: function (layer) {
    if (!this._drawing || layer._empty()) {
      return;
    }

    var p = layer._point,
      ctx = this._ctx,
      r = Math.max(Math.round(layer._radius), 1),
      s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;

    if (s !== 1) {
      ctx.save();
      ctx.scale(1, s);
    }

    ctx.beginPath();
    ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false); // 绘制圆形路径
    // 处理椭圆缩放
    if (s !== 1) {
      ctx.restore();
    }

    this._fillStroke(ctx, layer);
  },
  _fillStroke: function (ctx, layer) {
    var options = layer.options;

    if (options.fill) {
      ctx.globalAlpha = options.fillOpacity;
      ctx.fillStyle = options.fillColor || options.color;
      ctx.fill(options.fillRule || "evenodd");
    }

    if (options.stroke && options.weight !== 0) {
      // 虚线支持
      if (ctx.setLineDash) {
        ctx.setLineDash((layer.options && layer.options._dashArray) || []);
      }
      ctx.globalAlpha = options.opacity;
      ctx.lineWidth = options.weight;
      ctx.strokeStyle = options.color;
      ctx.lineCap = options.lineCap;
      ctx.lineJoin = options.lineJoin;
      ctx.stroke();
    }
  },
  _onClick: function (e) {
    var point = this._map.mouseEventToLayerPoint(e),
      layer,
      clickedLayer;

    for (var order = this._drawFirst; order; order = order.next) {
      layer = order.layer;
      //检测点击目标图层
      if (layer.options.interactive && layer._containsPoint(point)) {
        if (
          !(e.type === "click" || e.type === "preclick") ||
          !this._map._draggableMoved(layer)
        ) {
          clickedLayer = layer;
        }
      }
    }
    this._fireEvent(clickedLayer ? [clickedLayer] : false, e); //触发事件
  },
  _onMouseMove: function (e) {
    if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) {
      return;
    }

    var point = this._map.mouseEventToLayerPoint(e);
    this._handleMouseHover(e, point);
  },
  _handleMouseOut: function (e) {
    var layer = this._hoveredLayer;
    if (layer) {
      DomUtil.removeClass(this._container, "leaflet-interactive");
      this._fireEvent([layer], e, "mouseout");
      this._hoveredLayer = null;
      this._mouseHoverThrottled = false;
    }
  },
  _handleMouseHover: function (e, point) {
    if (this._mouseHoverThrottled) {
      return;
    }

    var layer, candidateHoveredLayer;

    for (var order = this._drawFirst; order; order = order.next) {
      layer = order.layer;
      if (layer.options.interactive && layer._containsPoint(point)) {
        candidateHoveredLayer = layer;
      }
    }

    if (candidateHoveredLayer !== this._hoveredLayer) {
      this._handleMouseOut(e);

      if (candidateHoveredLayer) {
        DomUtil.addClass(this._container, "leaflet-interactive"); // change cursor
        this._fireEvent([candidateHoveredLayer], e, "mouseover");
        this._hoveredLayer = candidateHoveredLayer;
      }
    }

    this._fireEvent(this._hoveredLayer ? [this._hoveredLayer] : false, e);

    this._mouseHoverThrottled = true;
    setTimeout(
      Util.bind(function () {
        this._mouseHoverThrottled = false;
      }, this),
      32
    );
  },
  _fireEvent: function (layers, e, type) {
    this._map._fireDOMEvent(e, type || e.type, layers);
  },
  _bringToFront: function (layer) {
    var order = layer._order;

    if (!order) {
      return;
    }

    var next = order.next;
    var prev = order.prev;

    if (next) {
      next.prev = prev;
    } else {
      return;
    }
    if (prev) {
      prev.next = next;
    } else if (next) {
      this._drawFirst = next;
    }

    order.prev = this._drawLast;
    this._drawLast.next = order;

    order.next = null;
    this._drawLast = order;

    this._requestRedraw(layer);
  },
  _bringToBack: function (layer) {
    var order = layer._order;

    if (!order) {
      return;
    }

    var next = order.next;
    var prev = order.prev;

    if (prev) {
      prev.next = next;
    } else {
      return;
    }
    if (next) {
      next.prev = prev;
    } else if (prev) {
      this._drawLast = prev;
    }

    order.prev = null;

    order.next = this._drawFirst;
    this._drawFirst.prev = order;
    this._drawFirst = order;

    this._requestRedraw(layer);
  },
});

export function canvas(options) {
  return Browser.canvas ? new Canvas(options) : null;
}

源码解析

生命周期管理
  • getEvents():在地图重置前标记_postponeUpdatePaths,延迟路径更新

  • onAdd():将Canvas容器添加到地图后立即渲染

  • _initContainer():通过DOM事件实现Canvas图层交互

  • _destroyContainer():销毁时释放资源,防止内存泄漏

渲染更新机制
  • _update():通过scaletranslate适配高清屏与地图偏移

  • _requestRedraw()_extendRedrawBounds:性能优化,使用requestAnimFrame批量处理重绘,局部更新减少渲染开销

路径管理(双向链表)
  • _initPath:通过链表结构管理图层渲染顺序,确保bringToFront/bringToBack高效执行

  • addPath(layer)_removePath(layer):动态维护链表结构,支持图层增删

绘制核心逻辑
  • _draw():通过裁剪区域(clip)限制绘制范围,提升性能

  • _updatePoly(layer,closed)_updateCircle(layer):根据几何图形的类型,调用不同的Canvas API绘制路径

  • _fillStroke(ctx, layer):填充和描边逻辑,支持虚线

交互处理
  • _onClick(e):通过遍历图层,检测点击目标图层

  • _onMouseMove(e)_handleMouseHover(e, point):通过_containsPoint判断鼠标位置,触发mouseovermouseout事件

总结

Canvas渲染器的核心设计:

1. 性能优化​​:局部重绘、链表管理、requestAnimFrame 批量更新。
2. 交互实现​​:通过坐标转换与路径检测模拟 DOM 事件。
3. 渲染隔离​​:双向链表维护绘制顺序,Retina 适配。
4. 扩展性​​:统一接口支持多种几何类型(线、多边形、圆)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jinuss

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值