Canvas上Tooltip定位错误问题求助
Canvas网格Tooltip定位错误的原因与修复方案
问题根源
Canvas缩放未同步到Tooltip计算
Canvas的绘图尺寸(1000x1000)和页面显示尺寸(100%宽高)不一致,计算Tooltip位置时直接使用了Canvas内部的单元格坐标(基于1000x1000),未转换为页面实际显示的像素坐标,导致位置偏移。Tooltip定位参考系错误
.tooltipMap是绝对定位,但未设置相对定位的父容器,默认以整个文档为定位基准,而非目标Canvas,进一步放大了坐标偏差。
修复后的完整代码
<style> /* Canvas设置相对定位,作为Tooltip的定位参考 */ canvas { position: relative; top: 0; left: 0; width: 100%; height: 100%; } .tooltipMap { position: absolute; display: none; background-color: white; border: 1px solid black; padding: 5px; box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1); border-radius: 5px; font-size: 14px; z-index: 10; } .tooltipMap button { padding: 5px 10px; background-color: #007bff; color: white; border: none; border-radius: 3px; cursor: pointer; } .tooltipMap button:hover { background-color: #0056b3; } </style> <div id="tooltipMap" class="tooltipMap"> <p>Comprar esta celda</p> <button id="buyButton">Comprar</button> </div> <canvas id="gameMap" width="1000" height="1000"></canvas> <script> document.addEventListener('DOMContentLoaded', function() { const canvas = document.getElementById('gameMap'); const ctx = canvas.getContext('2d'); const tooltipMap = document.getElementById('tooltipMap'); const buyButton = document.getElementById('buyButton'); const img = new Image(); img.onload = function() { ctx.drawImage(img, 0, 0); const gridSize = 50; function drawGrid() { ctx.strokeStyle = '#D3D3D3'; ctx.setLineDash([5, 5]); ctx.lineWidth = 1; for (let x = 0; x <= 1000; x += gridSize) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, 1000); ctx.stroke(); } for (let y = 0; y <= 1000; y += gridSize) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(1000, y); ctx.stroke(); } } function drawCellNumbers() { ctx.setLineDash([]); ctx.fillStyle = 'black'; ctx.strokeStyle = 'white'; ctx.lineWidth = 3; ctx.font = 'bold 18px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; let cellNumber = 1; for (let y = 0; y < 1000; y += gridSize) { for (let x = 0; x < 1000; x += gridSize) { ctx.strokeText(cellNumber, x + gridSize / 2, y + gridSize / 2); ctx.fillText(cellNumber, x + gridSize / 2, y + gridSize / 2); cellNumber++; } } } drawGrid(); drawCellNumbers(); let highlightedCell = null; function getCellCoords(x, y) { return { col: Math.floor(x / gridSize) * gridSize, row: Math.floor(y / gridSize) * gridSize }; } function highlightCell(col, row) { ctx.strokeStyle = 'black'; ctx.setLineDash([]); ctx.lineWidth = 3; ctx.strokeRect(col, row, gridSize, gridSize); } function redraw() { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0); drawGrid(); drawCellNumbers(); } canvas.addEventListener('mousemove', function(event) { const rect = canvas.getBoundingClientRect(); const scaleX = canvas.width / rect.width; const scaleY = canvas.height / rect.height; const mouseX = (event.clientX - rect.left) * scaleX; const mouseY = (event.clientY - rect.top) * scaleY; const cellCoords = getCellCoords(mouseX, mouseY); if (highlightedCell && (highlightedCell.col !== cellCoords.col || highlightedCell.row !== cellCoords.row)) { redraw(); } highlightedCell = cellCoords; highlightCell(cellCoords.col, cellCoords.row); }); canvas.addEventListener('click', function(event) { const rect = canvas.getBoundingClientRect(); // 修正缩放比例:页面显示尺寸 / Canvas绘图尺寸,用于转换内部坐标到页面像素 const scaleX = rect.width / canvas.width; const scaleY = rect.height / canvas.height; const mouseX = (event.clientX - rect.left) / scaleX; const mouseY = (event.clientY - rect.top) / scaleY; const cellCoords = getCellCoords(mouseX, mouseY); // 将Canvas内部坐标转换为页面实际显示坐标 const tooltipMapX = cellCoords.col * scaleX; const tooltipMapY = (cellCoords.row + gridSize) * scaleY; tooltipMap.style.left = `${tooltipMapX}px`; tooltipMap.style.top = `${tooltipMapY}px`; tooltipMap.style.display = 'block'; buyButton.onclick = function() { const cellNumber = Math.floor(mouseY / gridSize) * (1000 / gridSize) + Math.floor(mouseX / gridSize) + 1; alert(`Has comprado la celda número ${cellNumber}`); tooltipMap.style.display = 'none'; }; }); canvas.addEventListener('mouseleave', function() { tooltipMap.style.display = 'none'; }); }; img.src = 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/fd/000080_Navy_Blue_Square.svg/2048px-000080_Navy_Blue_Square.svg.png'; }); </script>
关键修复点
- 调整缩放比例计算逻辑:将
scaleX/Y改为页面显示尺寸 / Canvas绘图尺寸,实现内部坐标到页面实际像素的转换。 - 利用Canvas的相对定位:Canvas已设置
position: relative,Tooltip的绝对定位会自动以Canvas为基准,无需额外叠加offsetLeft/Top。 - 修正Tooltip坐标计算:用内部单元格坐标乘以缩放比例,得到页面上的精准位置。
内容的提问来源于stack exchange,提问作者Andrés Peláez
相关产品推荐
相关产品推荐

