ByteNoteByteNote
从网页到微信小程序:手写一个成语拼字游戏
字

字节笔记本

2026年10月7日 · 约 7 分钟读完

从网页到微信小程序:手写一个成语拼字游戏

API中转
¥120

拼字游戏:小而全的交互练习题

想练前端交互,拼字游戏比 Todo List 有趣得多:它有状态(哪些字已放置、哪些是提示)、有交互(拖拽)、有判定(对错反馈),逻辑却足够小,一个下午就能从零写到可玩。麻雀虽小,五脏俱全——状态管理、事件处理、视图更新、跨端适配这些日常开发的母题,在十几个汉字的棋盘上全部会出现一遍。本文以一个成语拼字游戏为例,完整走一遍「最小可玩版本、加干扰字、加提示机制、移植微信小程序」的四轮迭代,全程用原生 JavaScript 展开,不依赖任何框架,好把注意力放在机制本身。下面重点记录每一步真正容易踩的坑。

最小可玩版本:拖放三事件

HTML5 原生拖放 API 的核心只有三个环节。

源元素设置 draggable="true",监听 dragstart,把要搬运的数据写进 dataTransfer——拼字游戏里就是被拖的那个字。目标容器监听 dragover,并在回调里调用 event.preventDefault():这是最容易被忽略的一步,浏览器默认禁止放置,只有 dragover 里显式放行,drop 事件才会触发。这个设计并非多余:dragover 会高频触发,正好用来做「此处可放置」的实时视觉反馈。写完 drop 发现「拖上去没反应」,九成是漏了这一行。最后在 drop 回调里用 dataTransfer.getData 取回数据,校验目标槽位为空后写入。

判定逻辑放在每次放置之后:把四个槽位的文字 join 成串与答案比对,相等则宣告成功。要注意「尚未填满」和「填满了但错」必须分开判断——只在四个槽全满且不等于答案时才报失败,否则玩家刚放第一个字就会看到「很遗憾」的误报。

dataTransfer 的存在让拖放天然解耦:源元素只负责交货,目标元素只负责验收,同一个 drop 处理函数可以接住任何来源的数据,比手动监听 mousedown、mousemove 再算坐标干净得多。顺带一提,这套 API 在桌面浏览器表现良好,但在移动端浏览器支持很差,触摸拖拽需要另走 touch 事件或 Pointer Events 的路子。如果目标用户以手机为主,这个「最小版本」在一开始就该选别的交互方案。

HTML5 拖放事件流:dragstart、dragover、drop 与完成判定

加游戏性:干扰字与真正的洗牌

只拼一个成语太简单,两个改动让它像游戏。

其一,建一个成语题库(画蛇添足、守株待兔、掩耳盗铃、滥竽充数……),每局随机抽题;再准备一串干扰字,比如东南西北春夏秋冬天地人和,与答案的四个字合并后统一打散展示。玩家面对十几个字而不是四个字,才需要真正的成语储备。候选字一多,布局也要跟着改:把容器的 flex 换成允许换行的 flex-wrap,十几个字块就能在窄屏上自动折行。

其二,打散这步看似一行搞定:arr.sort(() => Math.random() - 0.5)。它能跑,但不是均匀洗牌——随机比较器违反了排序所依赖的传递性,不同排列出现的概率并不相等,部分顺序会显著偏高。拼字游戏里用无伤大雅,但对公平性敏感的场景应改用 Fisher–Yates:从后往前遍历,每个位置与含当前在内的前缀区间随机交换,保证所有排列等概率。这个算法最常见的写错点是把交换范围写成了全数组——那样某些排列反而永远取不到。知道 sort 洗牌错在哪、Fisher–Yates 对在哪,比背结论更重要。

提示机制:预填充两个槽

难度调整最优雅的手段不是增减干扰字,而是预填充:开局随机选两个位置,把正确的字放进去并标记为已锁定。这相当于给玩家两条线索,既能借线索推断,又保留动手乐趣;对刚接触成语的低龄玩家,也天然形成了一档温和的难度。实现上有三个细节。

随机取两个不重复下标,池子小用 while 循环加去重判断即可;更通用的写法是把下标数组洗牌后取前两个,天然不重复。可拖动字池必须随之改为「剩余字加干扰字」:如果仍把完整成语放进池子,已预填的字会再次出现在候选区,玩家会拿到干扰性极强的重复字。已锁定槽还要在 dragover 和 drop 两处都拦截,同时用不同底色和禁用光标把状态画出来——前端游戏里,状态永远要「数据与视觉」成对出现,只改数据不改样式,玩家会以为程序坏了。

预填几个字还是个现成的难度旋钮:困难模式不预填,中等预填一个,简单预填两个甚至三个,同一个游戏体就能服务不同水平的玩家。

移植小程序:交互范式必须换

微信小程序的 WXML、WXSS、JS 与 Web 三件套一一对应,页面结构(app.json 注册页面)、样式、逻辑各归其位,游戏思路可以整体平移,但有一个硬差异:小程序没有 HTML5 拖放事件。官方的 movable-view 组件能让元素动起来,但要做「拖到指定槽位精准对位」需要大量坐标判定与边界处理,投入产出比不高。最常见的替代是点击式:点选一个字,再点目标槽,两步完成原来的拖放。

这次移植本质上是三组映射。DOM 操作换数据绑定:Web 版直接改槽位的 textContent,小程序版必须维护 slots 数组并通过 setData 提交,视图才会更新——直接改数据对象不会触发渲染,这是小程序与 Web 开发思维差异最大的一处。事件目标换 dataset:HTML 里能从 event.target 顺手拿到上下文,小程序用 data-index、data-char 把参数挂在节点上,处理函数里从 currentTarget.dataset 读取。拖放语义换两次 tap:dragstart 记住选中字,drop 换成槽位上的 bindtap,原来写在 dragover 里的「可否放置」判断移到 tap 处理函数开头。

Web 到微信小程序的交互映射与开局流程

代价是操作从一步变两步,手感不如真拖拽;好处是逻辑全部收敛进数据层,判定函数读的是同一份状态,反而不容易出 bug。选中字还可以加一个高亮态,让「两步点击」有接近拖拽的视觉连续性。

收尾:迭代路径比代码更值钱

回看整个迭代——最小可玩、加干扰、加提示、跨端移植——覆盖了前端交互开发的典型决策链:先用平台原生能力跑通闭环,再按体验反馈调整规则,最后在平台约束下重新设计交互,而不是硬搬实现。这类小游戏也很适合作为 AI 结对编程的试验田:需求一句话能说清,验收标准客观(拼对、拼错、能重开),每轮改动都是局部增量,「提需求、看结果、再修正」的对话循环在这里运转最顺滑。想继续扩展,自然的方向是计时计分、成语释义展示(拼对了顺手学一个典故)、按预填字数分难度档位,以及把题库抽成可配置的 JSON 数据——骨架已经在了,剩下的都是内容运营。把这套骨架留下来,换题库、换皮肤、换平台,各是一个下午的事。

相关文章

分享: