首页/ 学科题库/ webgl/ WebGL 上下文创建与尺寸适配实战
webgl 实战教程

WebGL 上下文创建与尺寸适配实战

2026-09-13 约 7 分钟 juanwangdev

WebGL 上下文创建与尺寸适配实战

一句话结论:WebGL 画布出问题的两大源头是上下文创建方式与渲染尺寸换算,把 DPR 与视口处理正确,画面模糊和坐标偏移都会消失。

Canvas 上下文创建与尺寸适配

是什么

Canvas 上下文创建与尺寸适配,是指从 canvas 元素取出 WebGL 绘图上下文,并把渲染缓冲的像素尺寸、CSS 显示尺寸、视口范围三者对齐的过程。三者不一致时,画面会模糊、被拉伸或点击坐标偏移。

解决什么问题

它解决三类高频问题:高分屏上画面发虚、窗口缩放后内容变形、鼠标点击位置与绘制位置对不上。本质原因是 canvas 有两套尺寸,渲染缓冲用 width 与 height 属性,页面显示用 CSS 宽高,二者必须按设备像素比换算。

为什么这样做

浏览器 CSS 像素与物理像素不等价。在 DPR 为 2 的手机上,1 个 CSS 像素对应 2 个物理像素,如果缓冲只按 CSS 尺寸创建,浏览器就会把小图拉伸到两倍,边缘必然模糊。反之,如果只放大缓冲却不更新 gl.viewport,绘制内容只会占据画布的四分之一。

怎么用

JavaScript
const canvas = document.getElementById('scene');
const gl = canvas.getContext('webgl', {
  alpha: false,
  antialias: true,
  premultipliedAlpha: false,
  powerPreference: 'high-performance',
});

function resize() {
  const dpr = Math.min(window.devicePixelRatio || 1, 2); // 上限 2 兼顾性能
  const cssW = canvas.clientWidth;
  const cssH = canvas.clientHeight;
  const bufW = Math.round(cssW * dpr);
  const bufH = Math.round(cssH * dpr);

  if (canvas.width !== bufW || canvas.height !== bufH) {
    canvas.width = bufW;
    canvas.height = bufH;
    gl.viewport(0, 0, bufW, bufH); // 视口必须与缓冲尺寸一致
  }
}

window.addEventListener('resize', resize);
resize();

// 逐帧渲染时先清屏
gl.clearColor(0.06, 0.09, 0.16, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

常见坑

  • 只改 CSS 尺寸不改 canvas.width → 原因:误把 CSS 宽高当渲染尺寸 → 处理:两者分别设置,缓冲尺寸乘 DPR
  • 改了缓冲尺寸却忘记 gl.viewport → 原因:视口不会随画布自动更新 → 处理:尺寸变化后立即重设视口
  • DPR 直接取原值导致性能骤降 → 原因:三倍屏像素量是两倍屏的两倍多 → 处理:DPR 上限压到 2,或按帧率动态降采样
  • 高频 resize 事件里反复重建缓冲 → 原因:未做防抖 → 处理:用 requestAnimationFrame 或防抖合并尺寸更新

WebGL 典型应用场景

是什么

WebGL 的典型应用场景,是指需要 GPU 并行绘制能力的网页业务形态,主要分为三维可视化、海量图元渲染与交互式图形应用三类,共同点是数据量或画面复杂度超出 CPU 逐像素绘制的承受范围。

解决什么问题

它解决的是“网页能不能流畅承载复杂图形”的问题。例如十万级数据点的散点图、可自由旋转的产品模型、包含光照与阴影的在线场景,用 DOM 或 Canvas 2D 都会掉到十几帧,而 WebGL 可以稳定在 60 帧。

为什么这样做

这些场景的共同特征是顶点或像素数量巨大、每帧变化规律一致、适合向量化计算。GPU 的并行结构恰好匹配这类负载:相同指令同时作用在成千上万个顶点上,而 CPU 侧只需提交一次数据与状态,CPU 与 GPU 的分工因此最优。

怎么用

场景典型形态选型要点
数据可视化大规模散点、流向图、地形热力数据点用 GPU 缓冲或实例化,避免逐点建对象
3D 展示与配置产品模型、装修与车漆切换用 glTF 资产,减少材质分支
在线 3D 应用小游戏、模型编辑器关注上下文数量与显存上限,及时释放资源
数字孪生大屏园区、产线、机房可视化用 LOD 与视锥剔除控制绘制量
图像与视频处理滤镜、实时特效用离屏帧缓冲做后处理链
JavaScript
// 判断当前场景是否值得上 WebGL:顶点或像素量级决定
const isHeavyScene = vertexCount > 50000 || perFramePixels > 2000000;

常见坑

  • 把普通图表强行 WebGL 化 → 原因:忽略初始化与显存成本 → 处理:二维简单图表留在 Canvas 2D
  • 三维场景不做资源释放 → 原因:页面切换后缓冲与纹理仍占显存 → 处理:销毁视图时调用 deleteBuffer、deleteTexture
  • 移动端沿用桌面画质 → 原因:移动 GPU 填充率有限 → 处理:按设备能力分级,限制阴影与后处理

要点总结

  • canvas 的 width/height 是渲染缓冲像素,CSS 宽高是显示尺寸,必须按 DPR 换算
  • 尺寸变化后要同步更新 gl.viewport,否则画面只占一角
  • DPR 建议上限设为 2,避免高分屏上的填充率浪费
  • WebGL 适合三维展示、海量图元与数字孪生,二维简单图表不用
  • 视口组件卸载时必须释放缓冲与纹理,防止显存泄漏

常见问题

WebGL 画布为什么在手机上很模糊?

因为只按 CSS 像素设置了 canvas 宽高,没有按设备像素比放大渲染尺寸。把 drawingBuffer 宽高设置为 CSS 尺寸乘以 devicePixelRatio,并同步调用 gl.viewport 即可清晰。

canvas 的 width 属性和 CSS 宽度有什么区别?

width 属性决定渲染缓冲的像素数量,CSS 宽度只决定元素在页面上的显示大小,两者不一致时画面会被拉伸或压缩,必须分别设置并按 DPR 换算。

WebGL 每帧都要清屏吗?

需要。默认情况下绘制结果不会自动清除,逐帧渲染动画时不调用 clear 会留下上一帧的残影,除非画面覆盖了整个画布。

哪些业务场景适合直接选用 WebGL?

三维展示与产品配置、海量数据的可视化、在线 3D 游戏以及数字孪生大屏都适合。纯二维图表、表单辅助动画用 Canvas 2D 或 SVG 成本更低。

想在手机上练这篇的配套题目?
使用微信 卷王开发者小程序/App,首页顶部扫一扫识别下方二维码,即可解锁本讲对应真题与错题本。
了解产品 App
← 上一篇 WebGL 支持检测与开发调试环境搭建 下一篇 → WebGL 渲染管线流程与顶点着色器最小实现