零外部依赖 —— 海贼王h小游戏为什么只有本地脚本
打开任意一个游戏页,网络面板里只有同域的四类文件:HTML、一份 CSS、一份公用脚本、一份这款游戏的脚本。没有 CDN、没有外部字体、没有框架、没有外链图片。这篇写的是这条边界带来的具体后果。
边界是什么
具体到文件:一份 site.css 管全站版式,一份 ui.js 管本地存储、种子随机、筛选与两件工具,一份 game-*.js 只在对应的游戏页加载。插画是内联 SVG 或同域的 .svg 文件,字体用系统字体栈,一个远程请求都没有。
这条边界不是洁癖。它换来三件可以验证的事:页面首屏不等任何第三方域名解析;站点在浏览器缓存里是自洽的,断网之后再打开同一页,游戏逻辑照旧跑得完一局;没有任何第三方脚本能顺带读到你在这个站上的行为。
代价也很直接:所有东西都得自己写。下面两节是其中真正花时间的部分。
放弃框架之后要自己补什么
第一是状态与视图的同步。没有响应式框架,六款游戏统一用一个土办法:所有可见状态只从一个 S 对象出发,任何改动之后调一次 show() 把读数栏整体写一遍,再调一次 draw() 或 render() 重画。宁可多写几次 DOM,也不维护增量更新——小游戏的状态量小,整体重写反而更不容易出现「分数变了但读数栏没变」这类错位。
第二是文本化状态。画布对读屏软件是不透明的,所以每款游戏都有一个 role="status" 的段落,把刚刚发生的事写成一句人话:命中多少度、还剩几次机会、这一步应该走哪里。这句话同时是功能测试的断言对象——测试读的是用户能看见的那句话,不是「事件是否已绑定」。
第三是工具函数。种子随机、最佳成绩读写、本地存储失败提示这三件事六款游戏都要用,所以提到公用脚本里一份;其余逻辑各写各的,不抽象出一个「游戏基类」。六款玩法差别太大,强行抽象出来的基类会比重复代码更难改。
字体与插画怎么办
字体栈写成「PingFang SC、Hiragino Sans GB、Microsoft YaHei、system-ui」这样的序列,标题另走一条衬线序列(Songti SC 一类),读数与公式用等宽序列。好处是零字节、零闪烁;代价是不同系统上的字形不完全一样,所以版式不能依赖某一款字体的精确字宽,行高与间距都留了余量。
插画全部手写 SVG。图里的标签字号按 390px 屏反推:图的 viewBox 宽 480,放进 390px 的内容区大约缩到 0.8 倍,所以标签至少写 15px,缩放后还有 12px 以上。这不是审美选择,是之前吃过教训——按桌面尺寸配的 13px 图注在手机上只有 10px 出头,等于白画。
怎么确认这条边界没被破坏
靠人看不住,所以用一条机械判定:取这一页交付的 HTML 源码(而不是运行后的 DOM),解析一遍,检查所有 src 与 href 里有没有 http 开头的资源引用。源码里没有,就说明页面本身没有引入外部资源。
为什么必须扫源码而不是扫活 DOM:页面上线后会带统计脚本,统计脚本运行期会往文档里插入它自己的节点,扫活 DOM 会把那些注入算成本页外链,本地与线上的判定就对不上了。分清「本站交付的 markup」和「运行期的文档」,这条判定才稳定。