JavaScript Canvas瓦片鼠标悬停跟随式弹窗实现技术问询
嘿,这个需求我刚好折腾过,给你理清楚实现逻辑,附上完整可运行的代码,直接抄就能用~
实现Canvas瓦片悬浮跟随弹窗的完整方案
核心思路拆解
要实现这个效果,核心要解决两个问题:精准判断鼠标悬停在哪个瓦片上,以及让弹窗跟随鼠标实时更新位置和内容。
1. 先搞定「鼠标-瓦片」的坐标映射
Canvas的鼠标坐标是基于视口的,我们需要把它转换成瓦片网格的行列号。比如每个瓦片是32px,那用鼠标在Canvas内的坐标除以瓦片尺寸取整,就能得到对应的行列。
2. 打造跟随式弹窗
用一个固定定位的div做弹窗,默认隐藏。当检测到鼠标在有效瓦片上时,显示弹窗并同步更新内容和位置;鼠标离开瓦片或Canvas时隐藏弹窗。
完整可运行代码
HTML部分
<!-- Canvas容器 --> <canvas id="tileCanvas" width="320" height="320"></canvas> <!-- 悬浮弹窗 --> <div id="tileTooltip" class="tooltip"></div>
CSS部分
.tooltip { position: fixed; background: #ffffff; border: 1px solid #cccccc; padding: 8px 12px; border-radius: 4px; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); display: none; pointer-events: none; /* 关键:防止弹窗干扰鼠标事件 */ z-index: 1000; font-size: 14px; line-height: 1.4; }
JavaScript部分
const canvas = document.getElementById('tileCanvas'); const ctx = canvas.getContext('2d'); const tooltip = document.getElementById('tileTooltip'); // 基础配置 const tileSize = 32; // 单个瓦片的尺寸(像素) const gridSize = 10; // 瓦片网格大小(10×10) let canvasRect = canvas.getBoundingClientRect(); // Canvas的视口位置 // 模拟瓦片数据:每个瓦片存储独立信息(你可以替换成自己的业务数据) const tileData = Array.from({ length: gridSize }, (_, row) => Array.from({ length: gridSize }, (_, col) => ({ row: row + 1, col: col + 1, terrain: Math.random() > 0.5 ? '平原' : '山地', resources: Math.floor(Math.random() * 100), description: `这是第${row+1}行第${col+1}列的瓦片` }) )); // 绘制瓦片(这里用纯色模拟,实际换成new Image()加载的图片即可) function drawTiles() { ctx.clearRect(0, 0, canvas.width, canvas.height); tileData.forEach((row, rowIdx) => { row.forEach((tile, colIdx) => { // 绘制瓦片底色(模拟不同地形) ctx.fillStyle = tile.terrain === '平原' ? '#f0f9e8' : '#d9d2c5'; ctx.fillRect(colIdx * tileSize, rowIdx * tileSize, tileSize, tileSize); // 绘制瓦片编号(可选,方便调试) ctx.fillStyle = '#333333'; ctx.font = '12px Arial'; ctx.textAlign = 'center'; ctx.fillText(`${tile.row},${tile.col}`, colIdx*tileSize + tileSize/2, rowIdx*tileSize + tileSize/2 + 4); }); }); } // 根据鼠标位置获取对应的瓦片 function getHoveredTile(clientX, clientY) { // 转换为Canvas内部坐标(修正Canvas的视口偏移) const x = clientX - canvasRect.left; const y = clientY - canvasRect.top; // 计算瓦片行列 const col = Math.floor(x / tileSize); const row = Math.floor(y / tileSize); // 检查是否在有效网格范围内 if (row >= 0 && row < gridSize && col >= 0 && col < gridSize) { return tileData[row][col]; } return null; } // 更新弹窗的内容和位置 function updateTooltip(tile, mouseX, mouseY) { if (!tile) { tooltip.style.display = 'none'; return; } // 组装弹窗内容(可根据你的需求自定义) tooltip.innerHTML = ` <strong>瓦片 ${tile.row}-${tile.col}</strong><br> 地形:${tile.terrain}<br> 资源量:${tile.resources}<br> ${tile.description} `; // 显示弹窗,位置偏移鼠标避免遮挡 tooltip.style.display = 'block'; tooltip.style.left = `${mouseX + 12}px`; tooltip.style.top = `${mouseY + 12}px`; } // 绑定鼠标事件 canvas.addEventListener('mousemove', (e) => { // 实时更新Canvas的视口位置(防止页面滚动后坐标偏移) canvasRect = canvas.getBoundingClientRect(); const hoveredTile = getHoveredTile(e.clientX, e.clientY); updateTooltip(hoveredTile, e.clientX, e.clientY); }); // 鼠标离开Canvas时隐藏弹窗 canvas.addEventListener('mouseleave', () => { tooltip.style.display = 'none'; }); // 页面加载完成后绘制瓦片 window.addEventListener('load', drawTiles);
额外优化建议
- 瓦片图片加载:如果是用
new Image()加载外部瓦片,记得在图片onload事件触发后再调用drawTiles,避免绘制空白。 - 弹窗边界处理:如果鼠标靠近屏幕边缘,弹窗可能会超出视口,可以加个判断,当弹窗右侧/底部超出视口时,调整位置到鼠标左侧/上方。
- 性能优化:如果瓦片数量很大,可以考虑只在鼠标移动时计算一次瓦片,避免频繁重复计算。
内容的提问来源于stack exchange,提问作者Dakado
相关产品推荐
相关产品推荐

