demo_canvas 设计

设计目标

demo_canvas 用尽可能少的浏览器代码在真实页面上展示 Canvas 后端:一个画布、一个选择框、一个动画循环。它复用了终端演示的场景,因此两种输出可以直接比较。

数学背景

由动画帧得到时间

requestAnimationFrame 传入自页面启动以来以毫秒计的时间戳 τ\tau。圆环场景旋转角度 θ=0.0006 τ\theta = 0.0006\,\tau,因此绕 yy 转一整圈需要 2π/0.0006≈10.52\pi / 0.0006 \approx 10.5 秒,与显示器的刷新率无关。推拉场景是按终端演示的帧定义的,因此页面按每秒 30 帧把时间换算为帧号,

k=⌊τ1000/30⌋,k = \left\lfloor \frac{\tau}{1000/30} \right\rfloor ,

并渲染第 kk 帧。在 60 Hz 显示器上,每个推拉帧显示两次;运动速度与终端中相同。

构图

画布为 640 × 480(4:3),传感器为全画幅(3:2)。使用投影比例 s=Hf/hs = H f / h 时,垂直视场角填满画布高度,而水平视场比镜头的水平视场角更窄,推导见 view 设计。画布像素是正方形的,因此与终端不同,不做宽高比校正。

设计决策

每帧从头渲染

每次回调都会重建场景、绘制列表和整个画布。不做任何缓存,这使程序保持为时间戳和所选场景的纯函数,而在 640 × 480 下这样做没有值得一提的代价。

通过 Ref 进行选择

change 处理器只把所选场景写入一个 Ref;动画循环在下一帧读取它。渲染只发生在一个地方,切换场景不需要与循环协调。

正确性与不变量

  • 画布位图为 640 × 480,与投影的视口一致。
  • 时刻 τ\tau 的画面只取决于 τ\tau 和当前选择。

被否决的方案

  • 每个场景一个页面 会重复初始化代码。
  • 固定的帧计数器 代替时间戳会使速度依赖于刷新率。

边界

demo_canvas 不导出 API、不在浏览器之外运行、不提供播放控制(见 demo_gsap),也不随窗口尺寸调整。