跳到主要内容

海边一角 · 2.5D:用 Canvas 画一片可以放东西的海

#网页#Canvas#JavaScript

海边一角 是我最近做的一个小网页。使用deepseek harness独立完成创建。画面是一片海:浪在动,太阳在海面上碎成一片光,沙滩安静地铺在右下角。你可以往海里放点东西,然后刷新页面,它们还在。

海边一角的画面:蓝天、太阳、海面波纹、帆船与沙滩,海面上放着海星、小鱼、贝壳和海草
海面、沙滩和放上去的小东西,都是 Canvas 现画的。

怎么玩

操作 效果
点底部素材(海星 / 小鱼 / 贝壳 / 海草) 进入放置模式,海面上出现半透明预览
点海面 在该位置放下装饰,只能放在海水区域
拖动装饰 移动位置,越靠下越大、越靠前
双击装饰,或选中后按 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 里只有海边场景,不含标题和工具栏,方便直接拿来当壁纸或分享。

去哪里看

网页入口在 网页 板块,也可以直接打开 海边一角。点一下海面,放只海星进去,再刷新看看它还在不在。

← 返回文章列表