前端 Canvas/WebGL 入门

juming
juming 初级会员超兽战士
发布于 2026-10-07 23:30 ·1 浏览 ·0 回复

Canvas 2D 和 WebGL 都是浏览器原生的图形绘制接口,选择标准只有一条:**画 2D 图形、元素数量在几千以内,用 Canvas 2D;要画 3D、或者单帧图元数量上万、需要 GPU 并行计算,用 WebGL**。两者的 JS 调用方式完全不同,Canvas 2D 是「命令式画图」,WebGL 是「配好管线上传数据」,新手最常见的错误是用 WebGL 的思维写 Canvas 2D,或者反过来。

Canvas 2D 和 WebGL 到底有什么区别?

结论:Canvas 2D 是 CPU 逐条执行绘图命令的立即模式接口,WebGL 是把顶点数据交给 GPU 的渲染管线接口。具体差异有四点:

  • 坐标系统:Canvas 2D 原点在左上角,x 向右、y 向下,单位是 CSS 像素;WebGL 的裁剪空间是 x/y/z 都在 -1 到 1 之间,原点在画布中心。
  • 编程模型:Canvas 2D 调用 fillRect、arc、drawImage 这类方法,画完即生效;WebGL 必须先编译着色器(shader,跑在 GPU 上的小程序)、链接成 program,再通过 attribute(逐顶点数据)、uniform(全局常量)、varying(顶点到片元之间插值的变量)三个通道传数据。
  • 语言:Canvas 2D 只用写 JS;WebGL 必须写 GLSL(一种类 C 的着色器语言)。
  • 生态:Canvas 2D 做图表、H5 小游戏、图片裁剪、签名的场景,用原生 API 就够;WebGL 实际项目基本都套库,2D 用 Pixi.js,3D 用 Three.js。

最小的 Canvas 2D 代码怎么写?

结论:三行就能画出东西。<canvas> 元素的默认尺寸是 300×150 像素,不设置宽高就直接按这个尺寸渲染。

<canvas id="c" width="400" height="300"></canvas>
<script>
  const ctx = document.getElementById('c').getContext('2d');
  ctx.fillStyle = '#09f';
  ctx.fillRect(10, 10, 100, 80);
</script>

注意 width/height 是 canvas 的位图尺寸,和 CSS 里的 style.width 是两回事。只改 CSS 尺寸会让画面被拉伸模糊。

动画必须用 requestAnimationFrame 而不是 setInterval:前者跟随显示器刷新率(60Hz 屏幕约 16.7ms 一帧,120Hz 约 8.3ms),并且页面切到后台会自动暂停。Canvas 2D 是立即模式,每帧都要先 ctx.clearRect(0, 0, w, h) 清空再重画,否则图形会叠加。

为什么我的 Canvas 在高清屏上很模糊?

结论:因为没有按 devicePixelRatio(设备像素比,Retina 屏通常是 2 或 3)放大位图尺寸。CSS 写 400px 宽,实际需要 800 或 1200 个物理像素来填。

标准做法:

const dpr = window.devicePixelRatio || 1;
const w = 400, h = 300;
canvas.width = w * dpr;
canvas.height = h * dpr;
canvas.style.width = w + 'px';
canvas.style.height = h + 'px';
ctx.scale(dpr, dpr);   // 之后按 CSS 像素坐标画即可

这样设置后,你继续用 fillRect(10, 10, 100, 80) 这种逻辑坐标写代码,渲染自动变清晰。同样的坑在 WebGL 里也存在,gl.viewport(0, 0, canvas.width, canvas.height) 要传物理像素值。

WebGL 的第一个三角形要写多少代码?

结论:原生的第一个三角形大概 40 行,其中一半是样板代码。核心流程是固定的五步:

  1. const gl = canvas.getContext('webgl2') || canvas.getContext('webgl')——先要 WebGL 2(等价于 OpenGL ES 3.0,Chrome 56+、Firefox 51+、Safari 15+ 支持),拿不到再退回 WebGL 1(等价于 OpenGL ES 2.0)。
  2. 写两个着色器源码字符串。WebGL 1 的顶点着色器用 attribute vec2 a_pos; 接收顶点,片元着色器用 gl_FragColor 输出颜色;WebGL 2 要加 #version 300 es,改用 in/out,gl_FragColor 已移除,需要自己声明 out vec4 fragColor;。
  3. gl.createShader → gl.shaderSource → gl.compileShader,两个着色器各来一遍,务必检查 gl.getShaderParameter(shader, gl.COMPILE_STATUS),编译失败时 gl.getShaderInfoLog 才有错误行号。
  4. 链接成 program:gl.createProgram → gl.attachShader ×2 → gl.linkProgram → gl.useProgram。
  5. 传数据并绘制:把三角形三个顶点 [-0.5,-0.5, 0.5,-0.5, 0,0.5] 放进 Float32Array,绑定 ARRAY_BUFFER,gl.vertexAttribPointer 告诉 GPU 怎么读,最后 gl.drawArrays(gl.TRIANGLES, 0, 3)。

每帧要 gl.clear(gl.COLOR_BUFFER_BIT),画布尺寸变化时要重新调用 gl.viewport。

该学原生 WebGL 还是直接用库?

结论:目标是交付项目就直接用库,目标是理解渲染原理才手写原生。两者不冲突,建议先手写一遍三角形,再切到库里。

  • 2D 场景:Pixi.js 用 WebGL 加速,同屏渲染上万个精灵(sprite)依然能跑满 60fps,API 比原生 Canvas 2D 还简单。
  • 3D 场景:Three.js 覆盖相机、光照、材质、模型加载,原生写同样的效果代码量是它的 10 倍以上。
  • 图表场景:ECharts、Chart.js 默认走 Canvas 2D,数据量大时开 renderer: 'canvas' 之外的 SVG 反而更慢,别乱切。
  • 新方向:WebGPU 已在 Chrome 113(2023 年)默认开启,是 WebGL 的继任标准,但 Safari 和 Firefox 的完整支持仍在推进中,2024 年后新项目可以关注,暂时不作为主力。

性能优化必须知道的三个数字

结论:Canvas 2D 单帧 drawImage 调用超过约 1000 次、WebGL 单帧 draw call 超过约 100-300 次,就要考虑优化。

  • 减少状态切换:Canvas 2D 里 fillStyle、strokeStyle 的赋值有开销,把同色图形合并批量绘制。
  • 离屏缓存:不变的内容(背景、静态文字)用 document.createElement('canvas') 或 OffscreenCanvas 预渲染一次,之后每帧只 drawImage 一次。
  • WebGL 侧:不要在渲染循环里 new Float32Array 或创建 program,gl.bufferData 分配好的 buffer 复用;图元多时用 drawElements 配索引缓冲,能省掉重复顶点。
  • 逐帧检测:用 Chrome DevTools 的 Performance 面板录制 3-5 秒,看是 JS 执行超时还是 GPU 绘制超时,前者优化逻辑,后者减少绘制量。

总结一下:Canvas 2D 是画 2D 的首选,关键是处理 devicePixelRatio 和用 requestAnimationFrame 驱动;WebGL 的核心是着色器 + program + attribute 数据这条固定管线,原生写一遍再交给 Three.js 或 Pixi.js;性能上,Canvas 2D 控制单帧绘制次数,WebGL 控制 draw call 和每帧对象分配。选型的唯一判断依据是维度(2D/3D)和图元数量,不是哪个「更高级」。

版权声明:本文来自 GJ站长论坛《前端 Canvas/WebGL 入门》
原文链接:https://www.gj0.com/thread-617.html
转载请注明出处并保留本声明;内容仅代表作者观点,与本站立场无关。若本文涉嫌侵权,请联系本站处理。

全部回复 0

还没有回复,来抢沙发~