)
简介这是一份基于纯前端技术实现的汉诺塔益智小游戏源码包面向HTML/CSS/JavaScript初学者与Web开发入门者帮助理解递归逻辑、DOM操作与交互式动画设计。资源共21个文件包含2个核心HTML页面、2个JS脚本含主游戏逻辑hanoi.js与弹窗组件sweet-alert.min.js、3个CSS样式文件含重置、默认主题及提示样式、6张界面截图PNG、1段演示MP4视频以及字体文件、图标和说明文档等整体压缩包大小为19.82MB结构清晰、即开即用。已有941人学习下载适合用于课堂演示、算法可视化教学或个人项目练手。读者可直接运行index.html体验完整功能支持3–8层圆盘自由配置、实时显示最小步数与当前步数、一键回退操作、电脑自动求解并附带游戏规则说明、完成界面反馈及响应式UI设计配套readme.txt提供快速上手指引。1. 用纯 HTML CSS JS 实现可交互的汉诺塔小游戏零依赖、开箱即用你不需要 Node.js 环境不用 npm install甚至不连网络——把一个.html文件双击打开就能拖拽圆盘、验证步数、自动判断胜负。这不是教学演示而是真实可部署的轻量级交互逻辑3 根柱子动态渲染、圆盘按尺寸堆叠、拖放时实时校验合法性只能小盘压大盘、只能放空柱或更大盘上、步数统计精确到每一次有效移动。适合嵌入企业内网培训页、中小学信息课实验页、前端面试手写题参考实现也适合作为html网页制作入门项目练手——所有结构、样式、行为全部收敛在单文件中符合!doctype htmlhtml langzh-cnheadmeta charsetutf-8标准头部声明兼容 Chrome/Firefox/Edge 最新 3 个主版本。新手能照着改颜色和层数老手可直接提取核心算法模块复用。2. 构建响应式汉诺塔 UIHTML 结构 CSS 布局 动态尺寸计算2.1 HTML 骨架语义化容器与可拖放元素定义汉诺塔的 DOM 结构必须支持三类交互实体柱子容器div classpeg、圆盘元素div classdisk、控制面板div classcontrols。关键设计点在于所有圆盘初始状态必须由 JavaScript 动态生成并插入对应柱子而非硬编码 HTML——否则无法灵活调整层数如从 3 层切换到 5 层。以下是精简但完整的 HTML 片段严格遵循langzh-cn和 UTF-8 编码声明!doctype html html langzh-cn head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title汉诺塔小游戏 | HTMLCSSJS 实现/title style /* 后续 CSS 将在此处注入 */ /style /head body main classgame-container h1汉诺塔小游戏/h1 div classgame-board div classpeg idpeg-A>.game-container { max-width: 960px; margin: 0 auto; padding: 20px; font-family: Segoe UI, system-ui, sans-serif; } .game-board { display: flex; justify-content: space-between; align-items: flex-end; height: 400px; margin: 30px 0; position: relative; } .peg { width: 12px; background: #5d4037; border-radius: 6px; position: relative; flex-shrink: 0; } /* 柱子底部基座 */ .peg::before { content: ; position: absolute; bottom: -20px; left: 50%; transform: translateX(-50%); width: 120px; height: 12px; background: #8d6e63; border-radius: 6px; } .disk { height: 24px; border-radius: 4px; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); cursor: grab; transition: all 0.2s ease; box-shadow: 0 2px 4px rgba(0,0,0,0.2); } /* 圆盘颜色按尺寸区分越小越亮 */ .disk:nth-child(1) { background: #ff5252; width: 180px; } .disk:nth-child(2) { background: #ff9e52; width: 160px; } .disk:nth-child(3) { background: #ffdd52; width: 140px; } .disk:nth-child(4) { background: #aaff52; width: 120px; } .disk:nth-child(5) { background: #52ffaa; width: 100px; } .disk:nth-child(6) { background: #52aaff; width: 80px; } .disk:nth-child(7) { background: #a252ff; width: 60px; } .controls { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; } .stats { font-weight: bold; color: #1976d2; }注意.disk的position: absolute和bottom: 0是堆叠关键——所有圆盘初始都贴柱子底部JS 通过修改top值实现垂直堆叠z-index未显式设置依赖 DOM 插入顺序后插入的元素自然在上层颜色方案采用 HSL 渐变逻辑但此处用固定 class 避免运行时计算开销。2.3 动态尺寸计算JS 初始化柱子与圆盘初始化函数initGame(level)接收层数参数清空所有柱子然后按规则将level个圆盘从大到小依次放入 A 柱。每个圆盘 DOM 元素需绑定>function initGame(level) { // 清空三根柱子 document.querySelectorAll(.peg).forEach(peg peg.innerHTML ); // 重置步数 stepCount 0; document.getElementById(step-count).textContent 0; // 创建 level 个圆盘从大到小size 从 level 递减到 1 for (let size level; size 1; size--) { const disk document.createElement(div); disk.className disk; disk.dataset.size size; disk.draggable true; // 绑定拖拽事件后续章节详述 disk.addEventListener(dragstart, handleDragStart); disk.addEventListener(dragend, handleDragEnd); // 插入 A 柱ID 为 peg-A document.getElementById(peg-A).appendChild(disk); } // 设置柱子高度根据圆盘数量动态计算 const pegHeight Math.max(300, 24 * level 60); document.querySelectorAll(.peg).forEach(peg { peg.style.height ${pegHeight}px; }); } // 页面加载后初始化 3 层游戏 let stepCount 0; document.addEventListener(DOMContentLoaded, () { initGame(3); // 绑定层数输入框变更事件 document.getElementById(level-input).addEventListener(change, e { const level parseInt(e.target.value) || 3; initGame(Math.min(Math.max(level, 3), 7)); }); // 绑定重置按钮 document.getElementById(reset-btn).addEventListener(click, () { const level parseInt(document.getElementById(level-input).value) || 3; initGame(level); }); });逻辑说明dataset.size存储圆盘尺寸数值比 class 名更易读取和比较Math.max(300, 24 * level 60)确保柱子高度足够容纳所有圆盘24px 高 60px 底部余量parseInt()防止输入非数字导致 NaNMath.min/max双重校验保证层数在 3–7 区间。3. 实现拖拽交互逻辑事件监听 合法性校验 步数更新3.1 拖拽事件链dragstart → dragover → drop 的完整流程HTML5 原生拖拽 API 是本游戏交互核心。需在圆盘上监听dragstart记录被拖圆盘在柱子上监听dragover阻止默认行为以允许放置在柱子上监听drop执行移动逻辑。关键点在于dragstart中必须设置dataTransfer.setData()否则drop事件无法获取数据let draggedDisk null; let targetPeg null; function handleDragStart(e) { // 记录被拖圆盘元素及尺寸 draggedDisk e.target; const size parseInt(draggedDisk.dataset.size); e.dataTransfer.setData(text/plain, size); // 必须设置否则 drop 无法读取 // 添加拖拽中样式 draggedDisk.style.opacity 0.6; draggedDisk.style.cursor grabbing; } function handleDragEnd(e) { if (draggedDisk) { draggedDisk.style.opacity 1; draggedDisk.style.cursor grab; } draggedDisk null; } // 柱子监听 dragover允许放置 document.querySelectorAll(.peg).forEach(peg { peg.addEventListener(dragover, e { e.preventDefault(); // 关键阻止默认行为否则无法触发 drop }); // 柱子监听 drop执行移动 peg.addEventListener(drop, e { e.preventDefault(); if (!draggedDisk) return; targetPeg peg; const targetName peg.dataset.name; const sourcePeg draggedDisk.closest(.peg); // 禁止拖回原柱子 if (sourcePeg targetPeg) return; // 执行移动校验与操作 if (isValidMove(draggedDisk, targetPeg)) { moveDisk(draggedDisk, targetPeg); stepCount; document.getElementById(step-count).textContent stepCount.toString(); // 检查胜利条件 if (checkWinCondition()) { setTimeout(() { alert(恭喜你用 ${stepCount} 步完成 ${document.getElementById(level-input).value} 层汉诺塔\n最少步数为 ${Math.pow(2, parseInt(document.getElementById(level-input).value)) - 1}); }, 100); } } }); });参数说明e.preventDefault()在dragover中是强制要求否则浏览器会拒绝放置draggedDisk.closest(.peg)获取源柱子避免依赖 IDsetTimeout延迟弹窗防止阻塞动画。3.2 移动合法性校验四条规则逐项验证isValidMove(disk, targetPeg)函数必须检查以下四点缺一不可目标柱子是否为空→ 允许放置目标柱子顶部圆盘尺寸是否大于当前圆盘→ 允许放置当前圆盘是否位于某柱子顶部即没有被其他圆盘压住→ 仅顶部圆盘可移动是否尝试将圆盘移回原柱子→ 已在drop事件中前置拦截function isValidMove(disk, targetPeg) { const diskSize parseInt(disk.dataset.size); // 规则12目标柱子为空或顶部圆盘更大 const disksOnTarget targetPeg.children; if (disksOnTarget.length 0) return true; const topDiskOnTarget disksOnTarget[disksOnTarget.length - 1]; const topSize parseInt(topDiskOnTarget.dataset.size); if (diskSize topSize) return false; // 小盘不能压大盘 // 规则3检查当前圆盘是否在源柱子顶部 const sourcePeg disk.closest(.peg); const disksOnSource sourcePeg.children; const diskIndex Array.from(disksOnSource).indexOf(disk); if (diskIndex ! disksOnSource.length - 1) return false; // 不是顶部圆盘 return true; }逻辑说明disksOnTarget[disksOnTarget.length - 1]获取目标柱子最上方圆盘DOM 顺序即堆叠顺序Array.from().indexOf()精确判断圆盘在源柱子中的位置diskSize topSize严格禁止相等尺寸汉诺塔规则要求严格小于。3.3 圆盘移动与 DOM 更新计算新位置 插入顺序moveDisk(disk, targetPeg)不仅要将 DOM 元素从源柱子移到目标柱子还需重新计算其top值以实现视觉堆叠。新top(targetPeg.children.length - 1) * 24每个圆盘高 24px底部对齐function moveDisk(disk, targetPeg) { // 从源柱子移除 const sourcePeg disk.closest(.peg); sourcePeg.removeChild(disk); // 插入目标柱子末尾即顶部 targetPeg.appendChild(disk); // 重置样式 disk.style.transition none; disk.style.top auto; disk.style.bottom 0; // 强制重排以应用新位置 void disk.offsetWidth; // 添加过渡效果 disk.style.transition all 0.2s ease; // 计算新 top 值从底部向上堆叠 const disksOnTarget targetPeg.children; const newIndex disksOnTarget.length - 1; const newTop newIndex * 24; disk.style.top ${newTop}px; disk.style.bottom auto; }注意void disk.offsetWidth是强制浏览器重排reflow的关键技巧确保top值重置后立即生效transition: none临时禁用过渡避免闪烁top和bottom属性互斥必须显式设为auto或具体值。4. 胜利判定与性能优化状态快照 最少步数验证 事件委托4.1 胜利条件判定基于柱子状态的实时检查胜利条件是「所有圆盘按尺寸递增顺序小→大完整堆叠在 B 柱或 C 柱」。但注意标准汉诺塔要求目标柱子为 B 或 C而本游戏允许任一非 A 柱作为目标更符合用户自由操作习惯。判定逻辑分三步检查 B 柱或 C 柱是否非空检查该柱子圆盘数量是否等于总层数检查该柱子圆盘是否严格按size递增排列从下到上size从大到小即 DOM 顺序从左到右size递减function checkWinCondition() { const level parseInt(document.getElementById(level-input).value); const pegB document.getElementById(peg-B); const pegC document.getElementById(peg-C); // 检查 B 柱 if (isCompleteStack(pegB, level)) return true; // 检查 C 柱 if (isCompleteStack(pegC, level)) return true; return false; } function isCompleteStack(peg, expectedLevel) { const disks peg.children; if (disks.length ! expectedLevel) return false; // 检查尺寸是否严格递减从下到上大→小即 DOM 顺序第一个最大最后一个最小 for (let i 0; i disks.length; i) { const size parseInt(disks[i].dataset.size); if (size ! expectedLevel - i) { return false; } } return true; }逻辑说明expectedLevel - i生成期望序列当level3时期望disks[0].size3,disks[1].size2,disks[2].size1此序列与 DOM 插入顺序完全一致先插大盘后插小盘无需额外排序。4.2 最少步数验证数学公式与用户表现对比汉诺塔理论最少步数为2^n - 1n 为层数。游戏在胜利时不仅显示用户步数还对比理论最优值增强教学意义。此计算在alert中直接执行无需额外存储// 在 checkWinCondition() 为 true 后的 alert 中 const level parseInt(document.getElementById(level-input).value); const minSteps Math.pow(2, level) - 1; const efficiency ((minSteps / stepCount) * 100).toFixed(1); alert(恭喜你用 ${stepCount} 步完成 ${level} 层汉诺塔\n最少步数为 ${minSteps}效率 ${efficiency}%);注意Math.pow(2, level)比1 level更易读且level ≤ 7时无精度问题toFixed(1)保留一位小数提升可读性。4.3 事件委托优化减少内存占用与提升响应速度当前代码为每个圆盘单独绑定dragstart/dragend当层数增至 7 时最多生成 21 个事件监听器3柱×7盘。改用事件委托将监听器挂载到main或.game-board通过e.target判断是否为圆盘// 替换原圆盘事件绑定改为委托 document.querySelector(.game-board).addEventListener(dragstart, e { if (e.target.classList.contains(disk)) { draggedDisk e.target; const size parseInt(draggedDisk.dataset.size); e.dataTransfer.setData(text/plain, size); draggedDisk.style.opacity 0.6; draggedDisk.style.cursor grabbing; } }); document.querySelector(.game-board).addEventListener(dragend, e { if (draggedDisk) { draggedDisk.style.opacity 1; draggedDisk.style.cursor grab; draggedDisk null; } });优势无论层数多少仅 2 个全局监听器DOM 动态增删圆盘无需重新绑定内存占用恒定。这是html网页制作中处理动态元素的通用最佳实践。5. 进阶技巧键盘操作支持 移动端适配 自定义主题5.1 键盘快捷键方向键控制圆盘移动无障碍友好为提升可访问性添加键盘支持←/→切换当前选中柱子↑将选中圆盘移至下一柱子↓移至上一柱子。需维护currentPegIndex和selectedDisk状态let currentPegIndex 0; // 0:A, 1:B, 2:C let selectedDisk null; document.addEventListener(keydown, e { const pegs [peg-A, peg-B, peg-C]; const currentPeg document.getElementById(pegs[currentPegIndex]); if (e.key ArrowLeft) { currentPegIndex (currentPegIndex - 1 3) % 3; } else if (e.key ArrowRight) { currentPegIndex (currentPegIndex 1) % 3; } else if (e.key ArrowUp) { // 尝试将当前柱子顶部圆盘移至下一柱子 const disks currentPeg.children; if (disks.length 0) { const topDisk disks[disks.length - 1]; const nextPeg document.getElementById(pegs[(currentPegIndex 1) % 3]); if (isValidMove(topDisk, nextPeg)) { moveDisk(topDisk, nextPeg); stepCount; document.getElementById(step-count).textContent stepCount.toString(); } } } else if (e.key ArrowDown) { // 移至上一柱子 const disks currentPeg.children; if (disks.length 0) { const topDisk disks[disks.length - 1]; const prevPeg document.getElementById(pegs[(currentPegIndex - 1 3) % 3]); if (isValidMove(topDisk, prevPeg)) { moveDisk(topDisk, prevPeg); stepCount; document.getElementById(step-count).textContent stepCount.toString(); } } } });技巧说明(currentPegIndex - 1 3) % 3处理负数取模确保索引在 0–2 循环ArrowUp/Down仅操作当前柱子顶部圆盘符合直觉键盘操作与鼠标操作共享isValidMove和moveDisk保证逻辑一致性。5.2 移动端触摸适配touchstart/touchmove/touchend 模拟拖拽移动端需将touchstart映射为dragstarttouchend映射为dragend并在touchmove中模拟拖拽位移。核心是阻止默认滚动并将触摸坐标转为相对柱子位置// 移动端触摸支持 document.querySelectorAll(.disk).forEach(disk { disk.addEventListener(touchstart, e { e.preventDefault(); const touch e.touches[0]; const rect disk.getBoundingClientRect(); disk.style.left ${touch.clientX - rect.width / 2}px; disk.style.top ${touch.clientY - rect.height / 2}px; disk.style.position absolute; disk.style.zIndex 1000; draggedDisk disk; }); disk.addEventListener(touchmove, e { e.preventDefault(); const touch e.touches[0]; disk.style.left ${touch.clientX - 40}px; // 40px 为圆盘半宽近似值 disk.style.top ${touch.clientY - 12}px; // 12px 为圆盘半高 }); disk.addEventListener(touchend, e { if (!draggedDisk) return; // 查找最近的柱子 const pegs document.querySelectorAll(.peg); let closestPeg null; let minDist Infinity; pegs.forEach(peg { const pegRect peg.getBoundingClientRect(); const dist Math.sqrt( Math.pow(touch.clientX - (pegRect.left pegRect.width / 2), 2) Math.pow(touch.clientY - (pegRect.top pegRect.height / 2), 2) ); if (dist minDist) { minDist dist; closestPeg peg; } }); if (closestPeg isValidMove(draggedDisk, closestPeg)) { moveDisk(draggedDisk, closestPeg); stepCount; document.getElementById(step-count).textContent stepCount.toString(); } // 重置样式 draggedDisk.style.position ; draggedDisk.style.left ; draggedDisk.style.top ; draggedDisk.style.zIndex ; draggedDisk null; }); });注意e.preventDefault()在touchstart和touchmove中阻止页面滚动getBoundingClientRect()获取绝对坐标距离计算使用欧氏距离确保精准吸附touchend中的closestPeg查找逻辑与鼠标drop事件解耦独立运行。5.3 主题定制表CSS 变量驱动的快速换肤通过 CSS 自定义属性Custom Properties实现一键换肤用户只需修改:root中的变量值。提供三套预设主题经典棕默认、科技蓝、清新绿:root { --peg-color: #5d4037; --peg-base-color: #8d6e63; --disk-1: #ff5252; --disk-2: #ff9e52; --disk-3: #ffdd52; --disk-4: #aaff52; --disk-5: #52ffaa; --disk-6: #52aaff; --disk-7: #a252ff; --win-color: #4caf50; } .theme-tech { --peg-color: #1976d2; --peg-base-color: #2196f3; --disk-1: #2196f3; --disk-2: #42a5f5; --disk-3: #64b5f6; --disk-4: #90caf9; --disk-5: #bbdefb; --disk-6: #e3f2fd; --disk-7: #e8f5e9; } .theme-fresh { --peg-color: #388e3c; --peg-base-color: #4caf50; --disk-1: #81c784; --disk-2: #a5d6a7; --disk-3: #c8e6c9; --disk-4: #dcedc8; --disk-5: #f0f4c3; --disk-6: #fff9c4; --disk-7: #ffecb3; } .peg { background: var(--peg-color); } .peg::before { background: var(--peg-base-color); } .disk:nth-child(1) { background: var(--disk-1); } .disk:nth-child(2) { background: var(--disk-2); } .disk:nth-child(3) { background: var(--disk-3); } .disk:nth-child(4) { background: var(--disk-4); } .disk:nth-child(5) { background: var(--disk-5); } .disk:nth-child(6) { background: var(--disk-6); } .disk:nth-child(7) { background: var(--disk-7); }使用方法在html标签添加classtheme-tech即可切换主题所有颜色变量集中管理修改一处全局生效var(--xxx)语法确保浏览器兼容性Chrome 49/Firefox 31。本文还有配套的精品资源点击获取