
简介“疯狂挠一挠——艾玛手指痒死了”是一款主打趣味互动的小游戏这份源码包基于HTML5技术开发面向H5游戏初学者、前端练习者及对移动端触摸交互感兴趣的开发者可帮助理解游戏逻辑、动画与用户操作之间的配合。压缩包共5个文件大小约21KB内含一个页面入口文件、一份样式表、一个核心交互脚本、一张图片素材和一份说明文档结构克制且易于通读。目前已有69人学习下载。代码以轻量级脚本库实现可以重点分析点击、滑动等手势如何驱动“挠痒”动画同时观察场景绘制、动画刷新、事件响应和简单资源管理的组织方式项目中涉及的移动端适配与点击反馈设计也适合作为制作交互小页面的参考。由于代码量小、依赖少学习后可以直接修改原有素材、增加计分规则或转换主题快速转化为自己的作品。在此基础上还可以尝试加入音效、重置按钮或新增皮肤在动手改动中巩固对H5游戏开发流程的理解。1. 一个只有三个代码文件的“挠一挠”为什么值得拆解压 “H5小游戏源码 疯狂挠一挠——艾玛手指痒死了.zip” 之前我默认这类 H5 小游戏源码至少会有一堆精灵图或音频素材。没想到压缩包里只有一个 index.html、一个 style.css、一个 zepto.min.js、一张 share.png 和一份说明文档。整个游戏没有 Canvas也没有任何美术切片全程用 DOM 元素加 CSS3 动画在跑。这个设计对新手非常友好你想理解移动端触摸事件的命中判定、动画反馈和游戏状态切换这里每一步都能在浏览器里断点跟到。对写过一段时间 H5 的人来说它的价值在于演示了怎么用 Zepto 这样 10KB 级别的库做出传播类小游戏常见的“爽感”。下面的内容会从拆包开始把事件、动画、调试和组件化改造全部过一遍你拿到 zip 后可以直接对着操作。2. 拆包index.html 负责结构style.css 负责手感zepto.min.js 负责事件2.1 五个文件先分清谁是代码谁是素材用 unzip 解压后目录里出现的是index.html、style.css、zepto.min.js、share.png、源码重要性说明.txt。share.png 不是游戏素材是给微信分享当缩略图用的这个细节后面会细讲。真正能跑的代码是前三个文件unzip H5小游戏源码 疯狂挠一挠——艾玛手指痒死了.zip -d www cd www ls -lhunzip命令把压缩包解压到 www 目录-d参数指定目标目录。解压后建议先看一遍源码重要性说明.txt。压缩包里的文本文件一般会被当成废话但这份说明里往往会写原始出处、是否需要保留广告位、依赖了哪个库的版本。我看了下里面明确提到了 zepto 的引用方式这一点比代码本身更值得注意如果你把 zepto.min.js 换成 jQuery后面的触摸事件代码可能会因为 API 兼容性差异出现诡异行为。2.2 index.html 把游戏拆成三个区域源码中的 index.html 结构可以归纳成三块顶部得分栏、中间舞台、底部提示。因为没有图片素材每个痒痒点都是 DOM 元素示例结构大致如下div idgame classgame-box div classscore-bar span已挠/spanstrong classscore0/strong /div div classstage span classitch>grep -o [a-z]* zepto.min.js | sort -u输出的模块名列表能直接说明当前文件支持哪些能力。这个技巧在改别人打包过的 H5 小游戏源码时特别有用。如果后续你想给游戏加左右滑动切关卡却发现swipe事件不触发九成原因是这个文件没有打包 touch 模块而不是你的监听代码写错了。3. 手指“挠”下去事件绑定、命中判定与动画反馈3.1 为什么 click 在移动端不能直接用在 PC 上用户点击痒痒点触发 click 就能完成交互。但在 H5 小游戏里click 在移动端会经过 touchstart、touchend、然后浏览器等待约 300ms 判断是否双击最终才触发 click。这 300ms 对“挠一下”这种需要即时反馈的体验来说太长。更麻烦的是如果页面上同时存在 Zepto 的 tap 事件和原生 click快速点击会触发两次也就是常说的“点透”。源码里可控的做法是直接监听 touchstart 或 touchend。touchstart 的反馈最快适合做“手指一碰到就开始挠”touchend 适合模拟“拳头落到皮肤上再弹开”的感觉。我比较倾向 touchstart 加 CSS 延迟动画这样手指离开后视觉上还能延续挠的余韵var $stage $(#game .stage); $stage.on(touchstart, function(e) { var touch e.touches[0]; var hit findHitch(touch.clientX, touch.clientY); if (hit) { handleHit(hit); } });这里用e.touches[0]而不是e.targetTouches[0]是因为在 touchstart 阶段两者拿到的触点对象一致但touches在 touchend 时会变成空数组。如果你把这段逻辑复制到 touchend 里就必须改用changedTouches。这是移动端事件最容易翻车的参数差异。提示如果游戏里有倒计时倒计时触发end()后记得解绑 stage 的 touchstart否则用户继续乱点会更新已结束的分数。3.2 命中判定矩形相交不依赖 targetH5 小游戏源码里的痒痒点是小尺寸 DOM 元素手指的触控面积却比显示面积大。如果只判断e.target是不是.itch会出现两种情况手指按在痒痒点边缘时 target 可能是父容器点击被误判为 miss或者由于痒痒点太小用户很难精准点到。常见做法是给每个痒痒点设置一个比视觉尺寸稍大的命中区域然后做矩形相交判定function findHitch(touchX, touchY) { var match null; $stage.find(.itch).each(function() { var rect { left: this.offsetLeft, top: this.offsetTop, right: this.offsetLeft this.offsetWidth, bottom: this.offsetTop this.offsetHeight }; var hit touchX rect.left touchX rect.right touchY rect.top touchY rect.bottom; if (hit) { match this; return false; // Zepto each 支持 return false 中断循环 } }); return match; }需要注意offsetLeft和offsetTop获取的是相对 offsetParent 的位置。如果.stage设置了position: relative结果就是精确的。这也是 index.html 里 stage 必须加定位的原因。命中矩形的内外边距不需要在 JS 里调直接改 CSS 更直观比如.itch { width: 30px; height: 30px; padding: 12px; background-clip: content-box; }这样视觉圆点小可点击区域大。3.3 CSS3 动画反馈让“挠”看得见命中后如果只是分数变一下用户没有“挠到什么”的感觉。这个源码里给痒痒点加了缩放和抖动效果本质是切换一个 class动画完全交给 CSS3.itch.kicked { animation: twitch 0.3s ease-out forwards; } keyframes twitch { 0% { transform: scale(1) rotate(0deg); } 30% { transform: scale(1.2) rotate(8deg); } 60% { transform: scale(0.9) rotate(-6deg); } 100% { transform: scale(0.6) rotate(0deg); opacity: 0; } }forwards表示动画结束后保持最后一帧的状态这样痒痒点会缩成一个淡出的圆点而不是动画结束后突然跳回原来的位置。旋转角度控制在 8 度和 -6 度之间视觉上接近“皮肤被抓起来又落下”的抖动。如果你希望碰到后不是消失而是冒出挠痕可以改成transform: scale(1.1) rotate(3deg)再叠一个小红条的伪元素。这部分的代价只有 CSS 维护成本JS 不参与动画计算所以即使活动页同时跑三四个特效也不会掉帧。4. 跑起来本地服务、真机调试与 WebView 兼容性4.1 别用 file:// 直接打开 index.html双击 index.html 在 PC 上能看到页面但很多移动端特性会失效。CommonJS 模块、Web Audio、跨域图片、以及部分 WebView 的 localStorage 在 file:// 下行为都会变。安全的做法是起一个本地静态服务cd H5小游戏源码 疯狂挠一挠——艾玛手指痒死了 python3 -m http.server 8080python3 -m http.server从 Python 3.x 开始成为标准模块参数 8080 是监听端口。启动后浏览器访问http://localhost:8080。如果你机器上没有 Python也可以npx serve -l 8080它会把当前目录映射成静态资源服务器原理一样。注意端口如果被占用改用 8081 或别的都可以关键是服务根目录必须包含 index.html。4.2 真机预览手机和电脑在同一个局域网活动页最终要跑在手机上所以真机调试必须做。手机和电脑连同一个 Wi-Fi电脑执行ifconfig或ipconfig找到局域网 IP手机直接访问http://192.168.x.x:8080。如果电脑防火墙没放行 8080 端口或者手机和电脑不在同一网段真机访问会一直转圈。这时候先不要怀疑代码用curl -I http://192.168.x.x:8080在电脑上验证服务是否正常。这时你会注意到手机上的 Safari 和微信 WebView 的触摸行为基本一致但滚动条、回弹、双击缩放这些默认行为可能干扰游戏。需要在 CSS 里加几句html, body { touch-action: manipulation; -webkit-tap-highlight-color: transparent; user-select: none; }touch-action: manipulation告诉浏览器允许平移和缩放但禁止双击缩放减少触摸事件的等待时间。-webkit-tap-highlight-color: transparent去掉点击时出现的灰色半透明层。这两项对 H5 小游戏的体验影响非常大但很多源码不会写需要拿到手之后自己补。4.3 微信内嵌 H5 的检查和发布注意疯狂挠一挠这类小游戏源码最常见的落地场景是微信公众号菜单或朋友圈分享。微信 WebView 的渲染引擎对标准 CSS animation 的支持基本没问题但有三个坑要特别注意。微信内分享缩略图读取的是页面head里的imgUrl参数这个值必须是公网绝对地址不能是相对路径否则 share.png 不会生效。页面地址必须部署在 HTTPS 域名下并且域名要在公众号后台的 JS 接口安全域名里配置过才能调用分享相关的 wx.config。WebView 里position: fixed在键盘弹起时可能错位如果游戏里没有输入框就可以忽略如果后面加了排行榜输入框要提前处理。4.4 性能调优触摸事件里的隐形开销最后说一下触摸性能。在 touchmove 回调里直接加 DOM 操作会产生大量重绘尤其当痒痒点数量超过 10 个时。简单做法是用 requestAnimationFrame 节流var ticking false; $stage.on(touchmove, function(e) { if (ticking) return; ticking true; requestAnimationFrame(function() { var touch e.touches[0]; processTouch(touch); ticking false; }); });ticking标志位确保一个动画帧内只有一次处理把 touchmove 的高频触发从每毫秒一次降到每秒 60 次。注意processTouch里不要再做$()包装能直接缓存 DOM 节点就缓存因为 Zepto 的选择器底层依赖querySelectorAll在移动端低端机上比原生的getElementById慢一个数量级。5. 把“疯狂挠一挠”改成一个可配置的 H5 小游戏组件5.1 用 IIFE 封装不污染全局改别人的 H5 小游戏源码最重要的是留出可配置入口。这个源码体积小非常适合直接改造成组件。我一般会包一层立即执行函数然后把核心 Game 类暴露给 Zepto 的插件机制(function($) { use strict; var ItchGame function(el, options) { this.el $(el); this.options $.extend({}, ItchGame.defaults, options); this.score 0; this.init(); }; ItchGame.defaults { duration: 30, itchCount: 8, allowSound: false, onEnd: function() {} }; ItchGame.prototype.init function() { // 生成痒痒点、绑定 touchstart、启动倒计时 }; ItchGame.prototype.end function() { this.options.onEnd.call(this.el, this.score); }; $.fn.scratchItch function(options) { return this.each(function() { var game new ItchGame(this, options); $(this).data(scratchItch, game); }); }; }(window.Zepto || window.jQuery));$.extend在这里做了浅合并后面传入的配置会覆盖默认值。$(this).data()把游戏实例存在 DOM 节点上方便外部调用end()或重新初始化。5.2 暴露配置项覆盖活动页常见需求改成组件后普通 H5 页面里只需要一段初始化代码$(#gameBox).scratchItch({ duration: 60, itchCount: 12, onEnd: function(score) { $(#result).text(你挠了 score 下); } });配置项建议固定成一张表方便后续接需求的人对齐配置项类型默认值作用durationnumber30游戏时长秒itchCountnumber8舞台上的痒痒点数量allowSoundbooleanfalse是否播放挠动音效onEndfunction空函数游戏结束时回调allowSound默认关掉是因为移动端自动播放音频会被浏览器拦截。需要音效时在 touchstart 回调里创建一次 AudioContext 即可不要在用户触摸前初始化否则会被 WebView 的策略直接丢弃。5.3 在微信公众号内嵌场景里复用微信公众号内嵌页面通常不希望你直接调用微信 SDK除非要做分享。把游戏封装成组件后可以在onEnd回调里触发分享配置var game new ItchGame($(#gameBox)[0], { onEnd: function(score) { wx.updateAppMessageShareData({ title: 我挠了 score 下你敢挑战吗, imgUrl: https://your-cdn.com/share.png }); } });这里imgUrl必须和第二章提到的 share.png 对应起来。也就是说组件化改造并没有让原文件失效反而让 share.png 从不起眼的分享图变成了活动传播的入口。本文还有配套的精品资源点击获取