«

Canvas 可以替代 DOM 了吗?

时间:2026-8-16 23:04     作者:独元殇     分类: 前端技术




好问题!我是个搞 SaaS 的,我也一直在研究两者的优劣区别。

2026 AI 时代了。到底命令 AI 去使用 Canvas 还是 DOM 去写我们的应用?

今天聊聊吧(顺便再加几个技巧),我是一个已经被时代抛弃的 前端程序员 。大家以后选择到底是 DOM 还是 canvas ,这个是很重要的事儿,两者差太多了。选对的,不要走火入魔了。不得不说,我也感慨,浏览器就这么奇怪,内置强大的字体解析器和强大的文字排版引擎,但就不给你用,进了canvas还得自己弄字体解析器和文字排版。

首先 Canvas 的代表,就是 Google Docs ,还有 Google sheet 。图中的这个面板,就是一个 canvas 元素。多窗口复杂联动界面系统只能用 canvas ....

img

其实互联网上很多网站,尤其是 面板 ,比如 mori、Hivekit 等,以及大名鼎鼎的 excalidraw 这个在线无限画板,都是 canvas 。为啥不用 DOM 呢?

canvas 其实很成熟了。它很底层了,你可以自由控制上面的每个像素点。甚至可以使用 webgl 来搞出 https://ow.ccgxk.com/demo/house 像这样的三维世界。

很原始很底层。

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.fillRect(10, 10, 100, 100);
ctx.strokeRect(10, 10, 100, 100);
ctx.clearRect(20, 20, 50, 50);
ctx.beginPath();
ctx.moveTo(10, 10);
ctx.lineTo(100, 100);
ctx.arc(100, 100, 50, 0, Math.PI * 2);
ctx.stroke();

像上面一样,点线面可以自由控制。

对于前端程序员,其实更习惯 JS 去控制那种已经成体系、有一定逻辑和结构的 HTML 和 CSS ,也就是 DOM ,对于 canvas 就有点推倒重来了。。。但为什么他们搞 doc ,搞画板,还是使用 canvas 呢?

最主要的就是速度(这个没的说)!对于浏览器,他们每分每秒都在计算着 DOM 结构,肯定费时间。canvas 足够底层,甚至能直接动用 GPU 更大的性能来参与。所以速度必然可以更快。

Dom 背后是怎么渲染的,flutter 背后是怎么渲染的,他们底层同样调用的是类似 canvas 的东西,然后上层提供了这些功能。那我们同样可以在 canvas 上实现这一套东西

其实,主要还是为了 一致性 。(还有防作弊能力,canvas 不能直接改)

搞前端的都知道 CSS 是很让人疯的东西,最近 5 年还好,以前的时候要考虑多个系统、多个浏览器平台,多个设备等等,相当的考验耐心.... 但是 canvas 相对来讲就好多了。

根据我个人的经验,canvas 的各平台一致性还是不错了,除了字体外,其余的基本一致。不用再去考虑什么响应式、渐变、过渡效果等等。

而且, canvas 的个人控制权还是很高的。在HTML 里【滚动】这个功能,实现起来还是分复杂的,但是对于 canvas 就简单多了。

另外也有一个好处,大家都知道现在 WebAssembly 还是比较火的,你可以把以前的那种二进制文件编译成 WASM 然后在浏览器上运行,它们都支持直接使用 canvas 来展示输出。

canvas 可以替代 DOM 了吗?什么时候用,什么时候不用?

很显然。不能。

如果canvas能代替html/css,那么html/css从一开始就不会出现。

DOM 不是吃素的,人家已经把大部分的逻辑封装好了,我们在大部分的情况下,肯定是使用 DOM 好很多。你看 vscode 打开上万行的代码上下滚动其实也不会卡.... 性能可以的!

你就不用考虑最基本的排版或者什么。而且表单系统也给你弄好了。像什么 input 输入框、按钮都是现成的。并且有极其极其成熟的文档来保证全球一起工作,一致性。尤其是偏向于文字的,文字排版是最复杂的软件之一一般人不是说做就做的,只考虑monospace ascii文字都已经很复杂了,更何况要支持所有类型的文字!另外 SEO 也得考虑,canvas 是没有 SEO 能力的。

但是,如果你要做游戏,有复杂的 图层、定位 需求时,那 canvas 好。或者概念比较新、或者 3D 或者等等,反正 DOM 实现不了的,就使用 canvas 。

还有,防作弊能力!!! 一些特殊的系统,用canvas直接做前端,效果拉满,防作弊能力还强!!!(个人感觉如果把数据都渲染到canvas上,是可以做到一定加密作用,防止别人来爬取你的dom结构上的数据

前面还有说:「之所以有canvas这东西,纯粹就是官方摆烂了,懒得不停地设计新标签去满足市场需求:“屁事真多,给你个画布,要什么自己去画!”」哈哈哈~

使用 canvas 需要注意什么?

其实你看不看这个文章,你都知道,canvas 什么时候改用。

但是那没意义。我们要知道,canvas 用的时候要注意什么。hivekit 的博客是说过的。

原地址: https://hivekit.io/blog/why-you-might-want-to-build-your-webapp-in-canvas-instead-of-html/

里面提出了几点注意事项。

第一:管理渲染时机 !

第一次写 canvas 的人,没有这个意识。这些都是写小游戏的人比较习惯。就是,有一个 render 。你要有帧率的意识,这个 requestAnimationFrame 会让电脑以比较舒服的帧率不断重复,比如 100 帧每秒。

let scheduled = false;

function scheduleRender() {
  if (scheduled) return;   // 这一帧已经约过了,就别重复约
  scheduled = true;
  requestAnimationFrame(render);  // 循环
}

function render() {
  scheduled = false;
  ctx.clearRect(0, 0, 500, 500); // 全擦掉
  drawBackground();              // 重画背景
  drawRows();                    // 重画行
  drawTasks();                   // 重画任务
}

你看,这个 scheduleRender 就是一个总闸,scheduled 是个开关。统一管理。只要发生变化,才重画一次。节约性能,没必要每时每刻都重画。

第二:搞两个 canvas 元素,叠加在一起

这个厉害了!

你直接想肯定想不到,这是实践出的技巧。什么意思呢?就类似于下面这种:

<canvas id="main" style="position:absolute"></canvas>
<canvas id="ui" style="position:absolute"></canvas>
<script>
const a=main.getContext('2d'), b=ui.getContext('2d');
a.fillText('复杂地图:基本不动',100,100);
ui.onmousemove=e=>{
  b.clearRect(0,0,400,200);
  b.strokeRect(e.offsetX-20,e.offsetY-20,40,40);
};
</script>

这是行业里,一直再用的一种技巧,就是两个 canvas 叠在一起。

一个专门存放静态的内容,另一个存放动态,但是很少的内容。

一个相对来说,很久才刷新一次,另一个则频繁刷新。

毕竟,如果你只提供一个 canvas ,那你项目复杂了,每次刷新一次都要费很多资源,尤其是帧率....

第三:分辨率,按照设备缩放

不同设备,是有不同的分辨率的,所以为了更清晰的效果,得把 分辨率 管理好。,

比如一般的屏幕,是 1 厘米只能画 1 个点,而 Retina 屏幕好,能塞进去 2 × 2 , 4 个像素点。

所以,不要写死那个 width 和 height ,比如这样,可以实现按照设备分辨率(window.devicePixelRatio)放大缩小:

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const dpr = window.devicePixelRatio || 1;

canvas.style.width = '500px';
canvas.style.height = '300px';

canvas.width = 500 * dpr;   // dpr 调整分辨率
canvas.height = 300 * dpr;
ctx.scale(dpr, dpr); 
ctx.fillRect(50, 50, 100, 100); 

很好用。

第四,设立一个专门的函数管理 坐标 。比如:

function toScreen(x, y) {
  return {
    x: (x - cameraX) * zoom,
    y: (y - cameraY) * zoom
  };
}

这个主要是针对于那种要经常拖拽的面板,否则使用固定的坐标,很快就乱了。这个就相当于世界坐标吧,方便拖地图、怎么缩放、屏幕多大这种。

第五:就是,元素,都要在程序里记录一下,边界。。 就是边界框 ,方便后续 鼠标 点击的计算,判断有没有点到元素。

类似于这个意思:

const boxes = [
  {id:'房子A', x1:100, y1:100, x2:200, y2:180, z:1},
  {id:'按钮B', x1:150, y1:120, x2:250, y2:200, z:2}
];

canvas.onclick = e => {
  const x=e.offsetX, y=e.offsetY;
  const hit=boxes.filter(b=>x>=b.x1&&x<=b.x2&&y>=b.y1&&y<=b.y2);
  hit.sort((a,b)=>b.z-a.z);
  console.log('你点中了:', hit[0]?.id);
};

今天就这样吧。



标签: 原创 JS 分享 教程