在任意网页里“召唤”一个火柴人:一次有趣的 JavaScript Hack

在任意网页里"召唤"一个火柴人:一次有趣的 JavaScript Hack

有时候,写点"没什么用但很好玩"的代码,比写业务代码更能提升对浏览器底层的理解。

这次分享的是一个小脚本:

只需要把它保存为书签(Bookmarklet),点击一下,就能在当前任意网页上生成一个可操控的火柴人。

它能跳跃、移动、下落穿透平台,还能拾取道具、回血、加速、增强跳跃甚至短暂无敌。

更重要的是------它不依赖任何框架,不污染页面结构,不影响交互。 先上完整代码(压缩后)

js 复制代码
javascript:(function(){const I="__stickman_anim__";if(window[I]){window[I].destroy();delete window[I];return}const c=document.createElement("canvas");c.style.cssText="position:fixed;left:0;top:0;pointer-events:none;z-index:999999";document.body.appendChild(c);const x=c.getContext("2d");function r(){c.width=innerWidth;c.height=innerHeight}r();addEventListener("resize",r);let g=.6,f=.85,ms=3,jp=-12,k={},t=0,a,dead=!1;function centerSpawn(){let cx=innerWidth/2,cy=innerHeight/2,el=document.elementFromPoint(cx,cy);if(el){let r=el.getBoundingClientRect();return{x:r.left+r.width/2-10,y:r.top+r.height/2-20}}return{x:cx-10,y:cy-20}}let sp=centerSpawn();const p={x:sp.x,y:sp.y,vx:0,vy:0,w:20,h:40,onGround:!1,dropping:!1,hp:100,maxHp:100,inv:!1},it=[],M=5;let et=0;function spawn(){if(it.length>=M||dead)return;it.push({x:Math.random()*(c.width-30),y:Math.random()*(c.height-200),s:15,t:Math.floor(Math.random()*4)})}const si=setInterval(spawn,4e3);function eff(n){et=300;n==0&&(p.hp=Math.min(p.maxHp,p.hp+30));n==1&&(ms=6);n==2&&(jp=-20);n==3&&(p.inv=!0)}function reset(){ms=3;jp=-12;p.inv=!1}function plats(){return[...document.querySelectorAll("body *")].map(e=>e.getBoundingClientRect()).filter(r=>r.width>60&&r.height>20)}function col(){p.onGround=!1;for(let r of plats())if(p.x+p.w>r.left&&p.x<r.right&&p.y+p.h>r.top&&p.y+p.h<r.top+15&&p.vy>=0&&!p.dropping){p.y=r.top-p.h;p.vy=0;p.onGround=!0}}function pick(){for(let i=it.length-1;i>=0;i--){let o=it[i];if(p.x<o.x+o.s&&p.x+p.w>o.x&&p.y<o.y+o.s&&p.y+p.h>o.y){eff(o.t);it.splice(i,1)}}}function destroy(){dead=!0;cancelAnimationFrame(a);clearInterval(si);removeEventListener("keydown",kd);removeEventListener("keyup",ku);removeEventListener("resize",r);c.remove();delete window[I]}function upd(){if(dead)return;if(!p.inv)p.hp-=.01;if(p.hp<=0)return destroy();k.ArrowLeft&&(p.vx=-ms);k.ArrowRight&&(p.vx=ms);p.vx*=f;p.vy+=g;p.x+=p.vx;p.y+=p.vy;col();pick();k.ArrowDown||(p.dropping=!1);p.y>innerHeight+200&&(p.y=-100,p.vy=0);if(et>0&&!--et)reset();t+=Math.abs(p.vx)*.2}function limb(X,Y,l,a2){x.beginPath();x.moveTo(X,Y);x.lineTo(X+Math.cos(a2)*l,Y+Math.sin(a2)*l);x.stroke()}function draw(){x.clearRect(0,0,c.width,c.height);x.lineWidth=2;for(let o of it){x.fillStyle=o.t==0?"lime":o.t==1?"orange":o.t==2?"cyan":"gold";x.fillRect(o.x,o.y,o.s,o.s)}x.font="12px monospace";x.fillStyle="black";x.fillText(Math.floor(p.hp)+" / "+p.maxHp,c.width-100,c.height-15);let px=p.x,py=p.y;x.beginPath();x.arc(px+10,py+8,6,0,2*Math.PI);x.stroke();x.beginPath();x.moveTo(px+10,py+14);x.lineTo(px+10,py+30);x.stroke();let as=0,ls=0;p.onGround?(as=Math.sin(t)*.8,ls=Math.sin(t)): (as=-.5,ls=.5);limb(px+10,py+18,12,Math.PI/2+as);limb(px+10,py+18,12,Math.PI/2-as);limb(px+10,py+30,14,Math.PI/2+ls);limb(px+10,py+30,14,Math.PI/2-ls)}function loop(){upd();draw();a=requestAnimationFrame(loop)}function kd(e){["ArrowUp","ArrowDown","ArrowLeft","ArrowRight"].includes(e.code)&&e.preventDefault();k[e.code]=!0;if(e.code=="ArrowUp"&&p.onGround)p.vy=jp;if(e.code=="ArrowDown"&&p.onGround){p.dropping=!0;p.vy=5}}function ku(e){k[e.code]=!1}addEventListener("keydown",kd,{passive:!1});addEventListener("keyup",ku);a=requestAnimationFrame(loop);window[I]={destroy}})();

我们来拆解一下它背后的设计思路。


一、Bookmarklet:最轻量的"外挂"形式

整个脚本是一个立即执行函数:

js 复制代码
javascript:(function(){ ... })();

它的运行机制是:

  • javascript: 开头
  • 作为浏览器书签保存
  • 点击时在当前页面上下文执行

为了支持"再次点击销毁",脚本挂载了一个全局标记:

js 复制代码
const I = "__stickman_anim__";
if (window[I]) {
  window[I].destroy();
  delete window[I];
  return;
}

这段设计非常关键,它让这个脚本具备:

  • 可重复执行
  • 可优雅卸载
  • 不会重复创建实例

这是写任何"注入型脚本"时都应该养成的习惯。


二、Canvas 覆盖层:不干扰页面交互的核心技巧

脚本通过创建一个全屏 canvas 作为渲染层:

js 复制代码
const c = document.createElement("canvas");
c.style.cssText = `
  position:fixed;
  left:0;
  top:0;
  pointer-events:none;
  z-index:999999
`;

这里有两个关键点:

1.pointer-events: none

这保证:

  • 鼠标点击仍然穿透 canvas
  • 不影响网页原有交互

这是实现"外挂层"体验的关键。

2.超高 z-index

确保无论什么网站,都能显示在最上层。


三、物理系统:一个极简 2D 引擎

火柴人的运动核心参数:

js 复制代码
let g = 0.6;     // 重力
let f = 0.85;    // 摩擦
let ms = 3;      // 移动速度
let jp = -12;    // 跳跃初速度

每一帧的更新逻辑:

js 复制代码
p.vx *= f;
p.vy += g;
p.x += p.vx;
p.y += p.vy;

这就是一个最基础的:

  • 重力模拟
  • 摩擦减速
  • 速度积分

虽然简单,但已经足够流畅。


四、网页即平台:

js 复制代码
function plats(){
  return [...document.querySelectorAll("body *")]
    .map(e => e.getBoundingClientRect())
    .filter(r => r.width > 60 && r.height > 20)
}

把页面中所有尺寸足够大的 DOM 元素当成"平台"。

然后做简单的底部碰撞检测:

js 复制代码
if (
  p.x + p.w > r.left &&
  p.x < r.right &&
  p.y + p.h > r.top &&
  p.y + p.h < r.top + 15 &&
  p.vy >= 0 &&
  !p.dropping
)

这意味着:

  • 页面中的 div
  • 卡片
  • 图片
  • 区块

全部变成可以踩的平台。

整个网页变成关卡。


五、道具系统:状态增强机制

每 4 秒生成一个道具:

js 复制代码
setInterval(spawn, 4000);

道具类型:

类型 效果
0 回血
1 加速
2 超级跳跃
3 无敌

效果持续时间:

js 复制代码
et = 300; // 帧计时

然后自动恢复默认参数。

这是一个非常典型的 Buff 状态机。


六、动画:用数学让火柴人活起来

腿部和手臂摆动基于:

js 复制代码
Math.sin(t)

在地面时:

js 复制代码
as = Math.sin(t) * 0.8;
ls = Math.sin(t);

在空中时:

js 复制代码
as = -0.5;
ls = 0.5;

通过正弦函数驱动摆动,几乎零成本实现动态感。

但其实逐帧图片动画效果可能更好。


七、生命周期管理:

destroy 方法做了完整清理:

  • cancelAnimationFrame
  • clearInterval
  • removeEventListener
  • remove canvas
  • 删除 window 挂载

八、为什么这个小玩意很有价值?

它锻炼了:

  • DOM API 熟练度
  • 物理运动理解
  • 碰撞检测思路
  • 游戏循环结构
  • 浏览器渲染机制
  • 事件管理与清理

九、如果要继续进阶

可以考虑增加一些丰富的功能:

  • 攻击系统
  • 敌人 AI
  • 音效
  • 粒子爆炸效果
  • 真正的碰撞分离算法
  • 使用 QuadTree 优化平台检测
  • 使用 requestIdleCallback 优化扫描频率

十、总结

这段代码本质上做了三件事:

  1. 创建一个不影响页面的渲染层
  2. 把 DOM 元素当作游戏世界
  3. 用最小物理模型驱动角色运动

下班。

相关推荐
whatever who cares2 小时前
Java Web 架构全组件详解
java·前端·架构
DevDengChao2 小时前
[Aliyun] [FC] 如何使用 website-fc-serve 插件部署静态网站
前端·后端
前端拿破轮2 小时前
利用Github Page + Hexo 搭建专属的个人网站(一)
前端·人工智能·后端
q1cheng2 小时前
基于Spring Boot + Vue项目online_learn的用户登录认证全流程分析
前端
大时光2 小时前
gsap 配置解读 --2
前端
万岳科技程序员小金2 小时前
AI数字人小程序源码开发全流程实战:前端交互+后端算法部署指南
前端·人工智能·软件开发·ai数字人小程序·ai数字人系统源码·ai数字人软件开发·ai数字人平台搭建
白开水丶2 小时前
vue3源码学习(五)ref 、toRef、toRefs、proxyRefs 源码学习
前端·vue.js·学习
广州华水科技2 小时前
单北斗GNSS在变形监测中的应用与优势分析
前端
用泥种荷花2 小时前
【LangChain.js学习】大模型分类
前端