在任意网页里“召唤”一个火柴人:一次有趣的 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. 用最小物理模型驱动角色运动

下班。

相关推荐
醉城夜风~10 分钟前
CSS元素显示模式(display)
前端·css
AI大模型-小华1 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子3 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅3 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni4 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学5 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端
赵庆明老师5 小时前
Vben精讲:21-详解web-antd:tsconfig.json
前端·json·vim
wc885 小时前
微软EDGE浏览器功能学习
前端·学习·edge
破z晓5 小时前
javascript 导出excel表
开发语言·javascript·excel