公开文章
我用 Canvas 做了一张可以无限放大的画
刷到无限缩放画以后,我用 Canvas 2D 和 SVG 风插画复刻了一个可交互版本,顺便拆一下它背后的递归构图和缩放矩阵。
大家好,我是唐人。
前几天刷到一个视频:一张画可以一直放大,从画室钻进画板,从海岛钻进海底,再钻进另一个世界。
第一反应当然是:这得画多久?
但盯着看了一会儿,我发现它最有意思的地方,不是“分辨率有多高”,而是它的结构。
它不是一张真正无限大的图。
它更像一组套娃:每一层画面里,都提前藏好了下一层入口。
于是我就想,用代码能不能复刻一个简化版?
这篇文章就先做一个 Canvas 2D + SVG 风插画 的版本。它不追求照片级细节,只先把那个“不断往画里钻”的感觉跑起来。
Demo 在这里:
无限放大,不是无限分辨率
如果真要做一张可以无限放大的图片,靠普通位图肯定不现实。
图片再大,也总有像素用完的时候。你放到一定程度,画面就会糊。
所以这类视频的关键不是“有一张超级大图”,而是“每一层都藏着下一层”。
比如第一层是画室,画室里有一块画板;画板里画着海岛。镜头靠近画板时,画板里的海岛慢慢填满整个屏幕,于是观众就觉得自己进入了下一层世界。
再往后,海岛里有背包,背包里藏着海底;海底有洞口,洞口里藏着城市;城市里有屏幕,屏幕里藏着星空。
这就是递归构图。
不是一张图无限大,而是每一层都给下一层留了门。

代码里真正重要的三个东西
我这版 Demo 里,核心不是复杂图形,而是三个变量:
const sceneIndex = Math.floor(depth) % scenes.length;
const nextIndex = (sceneIndex + 1) % scenes.length;
const t = depth - Math.floor(depth);
sceneIndex 表示当前在哪一层。
nextIndex 表示当前层的入口里,应该预览哪一层。
t 是当前层的缩放进度,从 0 到 1。当它接近 1 时,镜头已经几乎钻进入口,下一层就会接管整个画面。
真正的缩放也不复杂。每一层都有一个入口矩形:
portal: { x: 402, y: 332, w: 196, h: 130 }
然后镜头从整张画面,慢慢插值到这个入口区域:
function currentView(scene, t) {
const p = scene.portal;
const eased = smooth(t);
return {
x: lerp(0, p.x, eased),
y: lerp(0, p.y, eased),
w: lerp(WORLD, p.w, eased),
h: lerp(WORLD, p.h, eased),
};
}
这段代码的意思很直白:
一开始看整张世界。
随着 t 变大,镜头一点点缩到入口。
入口最终铺满屏幕,下一层就出现了。
入口要画得像“真的通向下一层”
只做缩放还不够。
如果画板里是空白的,镜头再怎么缩也没有惊喜。
所以每一层的入口里,我会先把下一层缩小画进去:
function withPortal(ctx, portal, drawNext) {
ctx.save();
roundedRect(ctx, portal.x, portal.y, portal.w, portal.h, 18);
ctx.clip();
ctx.translate(portal.x, portal.y);
ctx.scale(portal.w / WORLD, portal.h / WORLD);
drawNext();
ctx.restore();
}
这里做了三件事:
先裁剪出入口区域。
再把坐标系移动到入口左上角。
最后把下一层完整世界缩小,塞进这个入口里。
这就是“画中画”的关键。
你看到的是一个小画框,但它里面其实已经提前画好了下一层世界。
为什么我先选 Canvas,而不是 SVG
SVG 很适合做矢量风,放大后线条也清晰。
但这次我先选 Canvas,是因为它更适合讲“镜头”和“世界”的关系。
Canvas 里我可以自己控制坐标系、缩放比例、裁剪区域和动画帧。你会很明显地看到:无限缩放不是魔法,而是每一帧都在算一个新的 viewport。
当然,后面完全可以升级。
如果要做得更像插画工具,可以把每一层改成 SVG group;如果要做更炫的效果,可以用 WebGL 或 Three.js;如果要做真正大图,可以引入瓦片加载,像地图一样按需渲染。
但第一版没必要一下子做太重。
先把核心机制跑通,反而更容易讲清楚。
写在最后
这个 Demo 只是一个小玩具,但我觉得它很适合做技术文章。
因为它有一个很好的入口:读者先被画面吸引,然后你再把它拆成几个工程问题。
画面为什么能一直放大?
入口怎么和下一层对齐?
Canvas 怎么把世界坐标变成屏幕坐标?
为什么它看起来像无限,其实只是递归和循环?
这些问题都不复杂,但组合起来就很有意思。
我后面可能继续把它做成一个小工具:可以自己编辑每一层场景,设置入口位置,然后自动生成一段无限缩放动画。
如果你也刷到过那种“一直放大”的画,你觉得下一版应该做成 SVG 编辑器,还是直接上 WebGL?
评论区聊聊。
关注公众号
不错过后续文章和配套资源
扫码关注「唐人Console」,获取文章更新、配套资源和领取入口。 需要解锁时,文章页会生成专属口令。
扫码关注