本文详解如何结合原生 javascript 实现平滑拖拽 + 碰撞检测 + 智能页面跳转,支持多 cd 图片分别触发不同目标页面,无需依赖第三方库,适合初学者快速上手。
本文详解如何结合原生 javascript 实现平滑拖拽 + 碰撞检测 + 智能页面跳转,支持多 cd 图片分别触发不同目标页面,无需依赖第三方库,适合初学者快速上手。
在网页交互开发中,「拖拽进入区域后跳转页面」是一个常见但易被误解的需求。许多新手会尝试混合 HTML5 原生 Drag & Drop(dragstart/drop)与手动坐标拖拽(mousedown/mousemove),结果导致事件冲突、定位不准或逻辑断裂。本文提供一套统一、稳定、可扩展的解决方案:使用纯手动拖拽(更可控),配合精确的矩形碰撞检测(getBoundingClientRect),并在松手时自动判断是否“落入目标区域”,再根据被拖拽元素的标识跳转对应页面。
✅ 核心思路:分离职责,精准判定
- 拖拽控制:通过 mousedown → mousemove → mouseup 实现自由、平滑的绝对定位拖拽;
- 碰撞检测:不依赖视觉重叠模拟,而是用 getBoundingClientRect() 获取真实 DOM 矩形边界,执行标准 AABB(Axis-Aligned Bounding Box)算法判断是否相交;
- 智能路由:为每个可拖拽图片添加语义化属性(如 data-page="JourneyThroughLove"),松手时读取该属性构造跳转路径,避免硬编码和耦合。
? 完整可运行代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>CD 插入游戏机跳转演示</title>
<style>
#console-area {
width: 800px;
height: 520px;
margin: 30px auto;
border: 2px dashed #666;
position: relative;
overflow: hidden;
background-color: #f9f9f9;
}
#console-area img {
width: 100%;
height: auto;
display: block;
}
.cd-draggable {
position: absolute;
width: 160px;
height: 160px;
cursor: move;
user-select: none;
z-index: 10;
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
transition: transform 0.1s;
}
.cd-draggable:hover {
transform: scale(1.05);
}
</style>
</head>
<body>
<div align="center">
<h2>? 拖动光盘插入主机,启动对应游戏</h2>
<p>(松开鼠标即触发检测与跳转)</p>
<div id="console-area">
<img src="https://electrazzz.neocities.org/Images/Graphics/2D/CD/PS2_Original.png" alt="PS2 主机">
</div>
<br>
<!-- 多个可拖拽 CD,各自绑定独立目标页 -->
<img src="https://electrazzz.neocities.org/Images/Graphics/2D/CD/LoveTime_Disc.png"
class="cd-draggable"
data-page="JourneyThroughLove"
alt="LoveTime 光盘">
<img src="https://electrazzz.neocities.org/Images/Graphics/2D/CD/AnotherGame_Disc.png"
class="cd-draggable"
data-page="AnotherGame"
alt="AnotherGame 光盘">
<img src="https://electrazzz.neocities.org/Images/Graphics/2D/CD/ClassicDemo_Disc.png"
class="cd-draggable"
data-page="ClassicDemo"
alt="ClassicDemo 光盘">
</div>
<script>
// 全局拖拽状态
let drag = false;
let offsetX, offsetY;
let coordX, coordY;
let draggedElement = null;
// 碰撞检测函数:判断两个 DOM 元素是否矩形重叠
function isOverlapping(el1, el2) {
const r1 = el1.getBoundingClientRect();
const r2 = el2.getBoundingClientRect();
return !(r1.right < r2.left || r1.left > r2.right ||
r1.bottom < r2.top || r1.top > r2.bottom);
}
// 启动拖拽
function startDrag(e) {
const targ = e.target || e.srcElement;
if (!targ.classList.contains('cd-draggable')) return;
draggedElement = targ;
drag = true;
// 计算鼠标相对元素左上角偏移
const rect = targ.getBoundingClientRect();
offsetX = e.clientX - rect.left;
offsetY = e.clientY - rect.top;
// 初始化位置(若未设置)
if (!targ.style.left) targ.style.left = '0px';
if (!targ.style.top) targ.style.top = '0px';
coordX = parseInt(targ.style.left);
coordY = parseInt(targ.style.top);
document.onmousemove = dragDiv;
e.preventDefault(); // 防止文字选中等干扰
}
// 执行拖拽
function dragDiv(e) {
if (!drag || !draggedElement) return;
draggedElement.style.left = (coordX + e.clientX - offsetX) + 'px';
draggedElement.style.top = (coordY + e.clientY - offsetY) + 'px';
}
// 松手:检测碰撞并跳转
function stopDrag() {
if (!drag || !draggedElement) return;
const consoleArea = document.getElementById('console-area');
if (isOverlapping(draggedElement, consoleArea)) {
const targetPage = draggedElement.dataset.page || 'index';
window.location.href = `${targetPage}.html`; // 使用 _self 替代 window.open 更符合现代体验
}
drag = false;
draggedElement = null;
document.onmousemove = null;
}
// 绑定全局事件(注意:仅需一次)
document.addEventListener('mousedown', startDrag);
document.addEventListener('mouseup', stopDrag);
// ✅ 关键优化:禁用图片默认拖拽行为,避免与自定义拖拽冲突
document.querySelectorAll('.cd-draggable').forEach(img => {
img.ondragstart = () => false;
});
</script>
</body>
</html>⚠️ 注意事项与最佳实践
- 避免混用两种拖拽机制:HTML5 draggable="true" 会触发浏览器原生拖放,与手动 mousemove 冲突。务必显式设置 ondragstart = () => false 禁用它。
- getBoundingClientRect() 是关键:它返回视口坐标(含滚动偏移),比 offsetTop/Left 更可靠,尤其在复杂布局或滚动页面中。
- 跳转方式建议用 window.location.href:相比 window.open(..., "_self"),它更语义清晰、SEO 友好,且无弹窗拦截风险。
- 扩展性设计:所有 CD 图片共享 .cd-draggable 类,通过 data-page 属性解耦内容与逻辑,新增光盘只需添加 <img ... data-page="NewGame"> 即可。
- 移动端兼容? 当前方案基于鼠标事件,如需支持触摸设备,请额外监听 touchstart/touchmove/touchend 并映射 clientX/Y 到 touches[0].clientX/Y。
✅ 总结
你不再需要在“原生 Drag & Drop”和“手动拖拽”之间艰难抉择——本方案以手动拖拽为基底,用数学级精准的碰撞检测替代模糊的 drop 区域,再通过语义化 data-* 属性实现多目标动态路由。代码结构清晰、零依赖、易调试,是入门 Web 交互开发的理想范例。下一步,你可以轻松加入音效反馈、动画过渡或本地存储最近游戏记录,让这个“虚拟游戏机”真正活起来。









