You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用自定义Google Maps类型时Canvas无法捕获鼠标事件的解决问询

问题描述

使用Google Maps自定义地图类型为每个瓦片创建Canvas,在瓦片加载后根据瓦片及像素位置绘制内容,但Canvas的鼠标移动事件被Google Maps的图层拦截,无法触发。尝试通过自定义Overlay将Canvas添加到overlayMouseTarget层,但导致Canvas被附加到地图中心,位置偏移,绘制内容错位。

当前无法触发事件的代码示例:

async function drawTest() {
    await google.maps.importLibrary("maps");

    var map = new google.maps.Map(document.getElementById("map"), {
      zoom: 7,
      minZoom: 3,
      center: {lat: 38, lng: -5}
    });

    function CoordMapType(tileSize) {
      this.tileSize = tileSize;
    }

    CoordMapType.prototype.getTile = function(tile, zoom, ownerDocument) {
      const canvas = ownerDocument.createElement('canvas');
      canvas.id = "canvas_" + tile.x + "," + tile.y;
      canvas.width = 256;
      canvas.height = 256;
      canvas.addEventListener("mousemove", (e) => {
        console.log(e);
      });

      return canvas;
    };
    
    map.overlayMapTypes.insertAt(0, new CoordMapType(new google.maps.Size(256, 256)));

    let positions = [
      {tileX: 61, tileY: 48, pX: 100, pY: 200},
      {tileX: 62, tileY: 49, pX: 231, pY: 180},
    ]

    google.maps.event.addListener(map, 'tilesloaded', function() {
      for (let i = 0; i < positions.length; i++) {
        let canvas = document.getElementById("canvas_" + positions[i].tileX + "," + positions[i].tileY);
        let ctx = canvas.getContext("2d");
        ctx.fillStyle = "red";
        ctx.fillRect(positions[i].pX, positions[i].pY, 10, 10);
      }
    });
}
drawTest();

尝试添加Overlay但位置错位的代码片段:

var CanvasOverlay = function() {};
CanvasOverlay.prototype = new google.maps.OverlayView();
CanvasOverlay.prototype.onAdd = function() {   
  console.log(tile)         
  let panes = this.getPanes();
  panes.overlayMouseTarget.appendChild(canvas);
};
var canvasOverlay = new CanvasOverlay();
canvasOverlay.setMap(map);
解决方案

核心思路是使用单个全局Canvas的自定义Overlay,替代每个瓦片单独创建Canvas的方式。这样既可以将Canvas放在overlayMouseTarget层捕获鼠标事件,又能通过地图投影精确计算每个目标点的绘制位置。

完整实现代码

async function drawTest() {
    await google.maps.importLibrary("maps");

    var map = new google.maps.Map(document.getElementById("map"), {
        zoom: 7,
        minZoom: 3,
        center: { lat: 38, lng: -5 }
    });

    // 需要绘制的点位数据
    const positions = [
        { tileX: 61, tileY: 48, pX: 100, pY: 200 },
        { tileX: 62, tileY: 49, pX: 231, pY: 180 },
    ];

    // 自定义Overlay类
    class CanvasOverlay extends google.maps.OverlayView {
        constructor(map, positions) {
            super();
            this.map = map;
            this.positions = positions;
            this.canvas = null;
            this.ctx = null;
            this.setMap(map);
        }

        onAdd() {
            // 创建全局Canvas
            this.canvas = document.createElement('canvas');
            this.canvas.style.position = 'absolute';
            this.ctx = this.canvas.getContext('2d');

            // 绑定鼠标事件
            this.canvas.addEventListener('mousemove', (e) => {
                console.log('鼠标移动事件:', e);
                // 可在此处添加坐标转换、交互逻辑
            });

            // 将Canvas添加到可接收鼠标事件的层
            const panes = this.getPanes();
            panes.overlayMouseTarget.appendChild(this.canvas);
        }

        draw() {
            const projection = this.getProjection();
            const mapSize = this.map.getSize();

            // 设置Canvas尺寸匹配地图容器
            this.canvas.width = mapSize.width;
            this.canvas.height = mapSize.height;

            // 清空画布
            this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);

            // 遍历所有点位,计算投影坐标并绘制
            this.positions.forEach(pos => {
                // 将瓦片坐标转换为经纬度
                const worldCoordinate = new google.maps.Point(
                    pos.tileX * 256 + pos.pX,
                    pos.tileY * 256 + pos.pY
                );
                const zoom = this.map.getZoom();
                const scale = Math.pow(2, zoom);
                const latLng = projection.fromPointToLatLng(
                    new google.maps.Point(worldCoordinate.x / scale, worldCoordinate.y / scale)
                );

                // 将经纬度转换为Canvas上的像素坐标
                const pixelPos = projection.fromLatLngToDivPixel(latLng);

                // 绘制红色矩形
                this.ctx.fillStyle = 'red';
                this.ctx.fillRect(pixelPos.x, pixelPos.y, 10, 10);
            });
        }

        onRemove() {
            this.canvas.remove();
            this.canvas = null;
        }
    }

    // 初始化Overlay
    const overlay = new CanvasOverlay(map, positions);

    // 监听地图变化,触发重绘
    google.maps.event.addListener(map, 'zoom_changed', () => {
        overlay.draw();
    });
    google.maps.event.addListener(map, 'center_changed', () => {
        overlay.draw();
    });
}

drawTest();

关键说明

  1. 全局Canvas替代瓦片Canvas:不再为每个瓦片创建独立Canvas,改用单个Canvas覆盖整个地图,避免位置偏移问题。
  2. 投影坐标转换:通过fromPointToLatLng和fromLatLngToDivPixel方法,将瓦片内的像素位置转换为地图上的经纬度,再转换为Canvas的像素坐标,确保绘制位置精准。
  3. 事件绑定到正确层级:将Canvas添加到overlayMouseTarget层,该层专门用于接收鼠标交互事件,不会被Google Maps的其他图层拦截。
  4. 重绘逻辑:监听地图的缩放、平移事件,触发Canvas的重绘,保证内容始终匹配地图视图。

内容的提问来源于stack exchange,提问作者Brian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 00:54:51