海边一角 · 2.5D:用 Canvas 画一片可以放东西的海
#网页#Canvas#JavaScript
海边一角 是我最近做的一个小网页。使用deepseek harness独立完成创建。画面是一片海:浪在动,太阳在海面上碎成一片光,沙滩安静地铺在右下角。你可以往海里放点东西,然后刷新页面,它们还在。
怎么玩
| 操作 | 效果 |
|---|---|
| 点底部素材(海星 / 小鱼 / 贝壳 / 海草) | 进入放置模式,海面上出现半透明预览 |
| 点海面 | 在该位置放下装饰,只能放在海水区域 |
| 拖动装饰 | 移动位置,越靠下越大、越靠前 |
| 双击装饰,或选中后按 Delete / Backspace | 删除 |
Ctrl/Cmd + Z |
撤销,放置、移动、删除、清空都能退回去 |
Esc |
取消选中、退出放置模式 |
| 右上角按钮 | 撤销 / 清空 / 保存当前画面为 PNG |
小鱼放下后会自己游;所有布置自动保存在浏览器的 localStorage 里,下次打开原样恢复。清空之后也不会再长出默认装饰——你看到的就是你留下的。
画面是怎么动起来的
整个项目只有 index.html、styles.css 和一个 js/ 目录,没有框架、没有构建、没有第三方依赖,画面全部由 Canvas 2D 绘制。
用 2.5D 代替 3D。 天空、海、沙滩和装饰是几层平面,每个装饰记一个 ny(在画面纵向上的归一化位置),由它决定缩放、透明度和绘制顺序,再叠一点指针带来的视差。于是越靠下的东西越大、越靠前,有了纵深,但不涉及 WebGL、相机和光照。这个体量下,比引入 three.js 轻得多,移动端也稳。
海浪是 22 条正弦波线。 由远及近排开,振幅和波长随深度增加,浪谷压深、浪尖提亮;近岸再叠一层随时间起伏的泡沫。太阳下方则是一组按透视扩散的横向光点,用 sin 相位闪烁,形成碎光。
性能上做了几件小事。 设备像素比固定上限 2,避免高分屏上无谓地多画几倍像素;沙滩纹理离屏预生成,不用每帧重画;全场只有一条 requestAnimationFrame 循环,页面隐藏时直接跳过渲染。
交互状态和绘制是分开的。 scene.js 只管装饰列表、命中检测、撤销栈和坐标换算,environment.js 与 creatures.js 只管画,main.js 负责指针事件、工具栏和存档。想加一种新装饰,只要写一个绘制函数、在两个表里注册、再在底部工具栏加一个按钮。
几个有意为之的取舍
- 不做构建。 所有资源都用
./相对路径,整个目录丢进任意子目录都能直接跑。这次就是原样放进个人站点的public/web/seaside-2d/,构建时随站点一起复制到dist/。 - 数据只留在本地。 没有服务端,布置存在 localStorage,换浏览器或清缓存就没了。对这样一个玩具来说,这是最合适的边界。
- 照顾不同的使用方式。 系统开启「减弱动态效果」时,动画降到 30% 速度;工具栏按钮有
aria-pressed和键盘焦点样式;脚本没加载时页面也会给出一句说明。 - 保存的只是画布。 导出的 PNG 里只有海边场景,不含标题和工具栏,方便直接拿来当壁纸或分享。