使用自定义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();
关键说明
- 全局Canvas替代瓦片Canvas:不再为每个瓦片创建独立Canvas,改用单个Canvas覆盖整个地图,避免位置偏移问题。
- 投影坐标转换:通过
fromPointToLatLng和fromLatLngToDivPixel方法,将瓦片内的像素位置转换为地图上的经纬度,再转换为Canvas的像素坐标,确保绘制位置精准。 - 事件绑定到正确层级:将Canvas添加到
overlayMouseTarget层,该层专门用于接收鼠标交互事件,不会被Google Maps的其他图层拦截。 - 重绘逻辑:监听地图的缩放、平移事件,触发Canvas的重绘,保证内容始终匹配地图视图。
内容的提问来源于stack exchange,提问作者Brian
相关产品推荐
相关产品推荐

