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

Canvas上Tooltip定位错误问题求助

Canvas网格Tooltip定位错误的原因与修复方案

问题根源

  1. Canvas缩放未同步到Tooltip计算
    Canvas的绘图尺寸(1000x1000)和页面显示尺寸(100%宽高)不一致,计算Tooltip位置时直接使用了Canvas内部的单元格坐标(基于1000x1000),未转换为页面实际显示的像素坐标,导致位置偏移。

  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 12:14:51