小白也能秒懂:Canvas全局透明度globalAlpha实战指南(附避坑技
- 小白也能秒懂:Canvas全局透明度globalAlpha实战指南(附避坑技巧)
- 引言:别被“透”支配的恐惧
- Canvas 透明度到底跟 DOM 有啥血海深仇?
- globalAlpha 到底是啥?一句话讲清
- 最简 Demo:画俩矩形,一个实的一个“虚”的
- 实战套路一:save & restore——“状态回档”神器
- 实战套路二:globalAlpha vs rgba()——到底谁听谁的?
- 实战场景 1:渐隐动画——“淡入淡出”如此丝滑
- 实战场景 2:水印叠加——“低调炫富”
- 实战场景 3:蒙层遮罩——“除了中间全黑”
- 踩坑合集:我替你躺过的雷
- 调试小妙招:console 一眼看穿
- 开发效率小技巧
- 隐藏彩蛋:globalAlpha + globalCompositeOperation 混合双打
- 结语:globalAlpha 不是洪水猛兽,只是忘了自我介绍
小白也能秒懂:Canvas全局透明度globalAlpha实战指南(附避坑技巧)
引言:别被“透”支配的恐惧
前阵子公司做大屏,产品小姐姐甩过来一张 UI:
“就按这个来,背景要那种若隐若现的科技感。”
我瞄了一眼,心想不就是半透明吗,十行代码搞定。结果当晚我盯着浏览器怀疑人生——为啥我画的网格老是透得跟丝袜一样?改 fillStyle、改 opacity、甚至给 canvas 加 CSS 透明度,全翻车。凌晨两点,我在群里哀嚎:“这破 canvas 咋就听不懂人话?”
直到老大淡淡甩了一句:“你先看看 globalAlpha 是不是被前任写成 0.1 没恢复。”
我???
原来这玩意儿就是 canvas 界的“隐形房东”,你不打招呼它就偷偷把整层房客全染成玻璃人。今天咱们就把这位房东扒个底朝天,顺带把我踩过的坑全给你铺好,保你以后画图不再“透”彻心扉。
Canvas 透明度到底跟 DOM 有啥血海深仇?
先放一句话:在浏览器里,CSS 的 opacity 是“美颜相机”,canvas 的 globalAlpha 是“雾霾警报”。
DOM 元素只要一个 opacity:0.5,它自己和所有娃全部变半透明,层级、事件、动画都继续嗨。
canvas 里可没这好事儿。canvas 是“位图黑洞”,一旦画下去就是像素,回头想改?只能重画!所以 canvas 搞了两套透明机制:
- 全局的 globalAlpha——“环境雾霾”,画一笔算一笔,全都带雾。
- 局部的 paintStyle——“局部遮瑕”,只影响当前这条命令,比如 fillStyle=‘rgba(255,0,0,0.3)’。
如果你把两者混着用,恭喜,你将喜提“我明明设置了 0.3 为啥看起来像 0.03”的玄学体验。下面咱们挨个拆。
globalAlpha 到底是啥?一句话讲清
官方文档说:
“globalAlpha = number between 0 and 1,default 1.”
翻译成人话:
它就是一只看不见的手,每一帧绘图命令执行前,都会先被这只手撸一遍——像素颜色先乘以 alpha,再盖到画布上。
注意,是“每一帧命令”!这意味着:
- 你画 100 个圆,它撸 100 次;
- 你画文字、画图片、画路径,全都撸;
- 撸完还不告诉你,转头就忘,除非你手动改回来。
所以,它更像“状态机”而不是“属性”。状态机最怕啥?忘!了!恢!复!
最简 Demo:画俩矩形,一个实的一个“虚”的
<canvas id="c" width="300" height="200"></canvas>
<script>
const ctx = c.getContext('2d');
// 先画个实心红块
ctx.fillStyle = 'red';
ctx.fillRect(20, 20, 80, 80);
// 把 globalAlpha 调成 0.4,再画个蓝块
ctx.globalAlpha = 0.4;
ctx.fillStyle = 'blue';
ctx.fillRect(120, 20, 80, 80);
// 忘了恢复?下面再画文字也透
ctx.fillStyle = '#000';
ctx.font = '20px sans-serif';
ctx.fillText('我咋也透了?', 20, 140);
</script>
运行结果:文字自带 0.4 透明,因为 globalAlpha 还是 0.4。
把代码最后一行之前加一句 ctx.globalAlpha = 1; 再看,文字瞬间精神。
这就是最经典的“忘了关门”现场,群里每天有人中招。
实战套路一:save & restore——“状态回档”神器
上面那种“手动关门”太反人类,canvas 早就给了快捷键:ctx.save() 和 ctx.restore()。
save 会把当前所有绘图状态(alpha、fillStyle、strokeStyle、线宽、阴影、裁剪区等)压栈;restore 弹出。
改写一下:
// 画蓝块之前存档
ctx.save();
ctx.globalAlpha = 0.4;
ctx.fillStyle = 'blue';
ctx.fillRect(120, 20, 80, 80);
ctx.restore(); // 一出栈,alpha 自动回 1
// 画文字时就是完全不透
ctx.fillText('这次我不透了', 20, 140);
友情提示:save/restore 可以嵌套,但别疯狂套娃,性能掉得比发量还快。一般建议:
“一个功能块一次 save/restore”,比如画水印、画遮罩、画粒子层,各玩各的。
实战套路二:globalAlpha vs rgba()——到底谁听谁的?
很多人懵:“我都 rgba(0,0,0,0.5) 了,还要 globalAlpha 干嘛?”
来,做个实验:
ctx.globalAlpha = 0.5;
ctx.fillStyle = 'rgba(0,0,0,0.5)';
ctx.fillRect(0, 0, 100, 100);
最终 alpha 是多少?
答案是:0.5 * 0.5 = 0.25。
canvas 在填充前会先把颜色拆成 R/G/B/A,再乘以 globalAlpha。
所以:
- 只想单图形透明 → 用 rgba 就够;
- 想批量给一堆元素统一降透明 → 用 globalAlpha 省代码;
- 两者一起用 → 你就得乘法口诀背牢。
实战场景 1:渐隐动画——“淡入淡出”如此丝滑
需求:一个 200×200 的图,从 0 透明度拉到 1,持续 1 秒,循环往复。
思路:
- 用 requestAnimationFrame 驱动;
- 每帧先清空画布;
- 根据进度算 globalAlpha;
- 画图片;
- 循环。
const img = new Image();
img.src = 'kunkun.png'; // 任意本地图
img.onload = () => animate();
let start = 0;
function animate(ts) {
if (!start) start = ts;
const progress = (ts - start) % 1000 / 1000; // 0~1
const phase = Math.sin(progress * Math.PI); // 先升后降
ctx.clearRect(0, 0, c.width, c.height);
ctx.save();
ctx.globalAlpha = phase;
ctx.drawImage(img, 50, 50, 200, 200);
ctx.restore();
requestAnimationFrame(animate);
}
注意:
- 一定要
clearRect,否则上一帧残影让你怀疑人生。 - 图片加载完再动画,不然第一帧空图直接社死。
实战场景 2:水印叠加——“低调炫富”
需求:右下角 30% 透明文字水印,不随内容刷新而闪烁。
套路:
- 单独开一个 offscreenCanvas(内存画布),一次性画好水印;
- 主画布每帧直接
drawImage(offscreen, 0, 0)。
// 1. 先整内存画布
const water = document.createElement('canvas');
water.width = 200;
water.height = 80;
const wctx = water.getContext('2d');
wctx.globalAlpha = 0.3;
wctx.fillStyle = '#fff';
wctx.font = '16px sans-serif';
wctx.fillText('@前端冠希', 20, 50);
// 2. 主画布循环里直接贴图
function drawMain() {
ctx.clearRect(0, 0, c.width, c.height);
/* 画你的各种图表 */
// 最后贴水印,坐标自己算
ctx.drawImage(water, c.width - 220, c.height - 100);
}
这样做的好处:
- 水印只生成一次,不占主线程;
- 主画布随便刷新,水印稳如老狗;
- 想改透明度?改 offscreen 里的 globalAlpha 就行,主画布无感。
实战场景 3:蒙层遮罩——“除了中间全黑”
需求:点击按钮弹出弹窗,背景 canvas 变暗,但中间留一个圆形洞口高亮。
思路:
- 先正常画你的内容;
- save 后设置 globalAlpha = 0.6;
- 用复合路径:外框矩形 + 中间圆,调用
fill('evenodd'); - restore。
function drawMask(cx, cy, r) {
ctx.save();
ctx.globalAlpha = 0.6;
ctx.fillStyle = '#000';
// 开始路径
ctx.beginPath();
// 先外框,顺时针
ctx.rect(0, 0, c.width, c.height);
// 再圆洞,逆时针
ctx.arc(cx, cy, r, 0, Math.PI * 2, true);
ctx.fill('evenodd');
ctx.restore();
}
注意:
- 圆洞必须逆时针,否则 evenodd 不生效;
- 如果懒得算方向,直接用
globalCompositeOperation = 'destination-out'也行,但那就是另一个话题了。
踩坑合集:我替你躺过的雷
- 设置了 globalAlpha 没反应?
八成是你先画的背景,后设的 alpha。记住:状态机只对“之后的命令”生效。 - 文字发虚?
透明度过低 + 字体小,抗锯齿直接糊成一片。要么提高 alpha,要么放大字体,无解。 - 用了 save 还没恢复?
检查是不是提前 return 了,比如图片没加载完就 return,restore 没执行,栈乱了,后面全崩。 - 性能突然掉帧?
在循环里疯狂 save/restore,每帧 200 次,GPU 直呼内行。换成手动 alpha 或者分层 canvas。 - 导出 base64 背景全黑?
toDataURL 不会把 CSS 背景带上去,如果你把 alpha 设成 0 再去 toDataURL,那结果就是一片透明黑。先画个白底再导出。
调试小妙招:console 一眼看穿
- 彩虹条调试法
画一条彩虹渐变矩形,每格 10% 透明度,肉眼对比你的图形落在哪一格,就能反推当前 alpha。 - 像素拾色器
ctx.getImageData(x, y, 1, 1).data拿到 [R,G,B,A],如果 A 值比你预期的小,那一定是 globalAlpha 在搞鬼。 - 逐帧录像
Chrome DevTools → Rendering → FPS meter → 勾选“Capture screenshots”,回放时一帧一帧看,透明突变瞬间无所遁形。
开发效率小技巧
- 临时关闭所有透明:在循环入口
ctx.globalAlpha = 1;一刀斩,快速排除透明问题。 - 批量画同透明元素:先设一次 globalAlpha,for 循环画 1000 个粒子,再 restore,比每条命令都 rgba 快 30%。
- 用 OffscreenCanvas:在 WebWorker 里预渲染带透明度的图层,主线程只负责合成,UI 线程不卡。
- 颜色混合偷懒:想搞“半透明红色 + 半透明蓝色 = 紫色”?记得
ctx.globalCompositeOperation = 'lighter',再分别画,就不用自己算 RGB。
隐藏彩蛋:globalAlpha + globalCompositeOperation 混合双打
举个骚操作:
需求——“鼠标擦除”蒙版,擦过的地方变透明,看到下层视频。
思路:
- 上层 canvas 盖在 video 上;
- 全局 alpha 0.8,先画黑幕;
- 合成模式改成
destination-out; - 鼠标移动时画粗线,线经过的地方直接“挖空”。
ctx.save();
ctx.globalAlpha = 0.8;
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, c.width, c.height);
ctx.globalCompositeOperation = 'destination-out';
ctx.globalAlpha = 1; // 挖洞不需要透明
ctx.lineWidth = 40;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(lastX, lastY);
ctx.lineTo(e.offsetX, e.offsetY);
ctx.stroke();
ctx.restore();
视频瞬间露出,鼠标变成橡皮擦,小姐姐惊呼“这效果 TikTok 见过!”
结语:globalAlpha 不是洪水猛兽,只是忘了自我介绍
当年我要是有人把这篇甩脸上,也不至于凌晨两点对着 MDN 爆哭。今天哥把锅都替你背了,代码直接复制就能跑。
下次再遇到“为啥我画的图透得跟丝袜一样”,别慌,先打一行 console.log(ctx.globalAlpha),然后按部就班:
save → 设 alpha → 画 → restore。
把这四步背成肌肉记忆,你就从“透明小倒霉”升级成“alpha 大魔王”。
好了,板砖扔完,我去修 BUG 了,祝你画图不再“透”心凉!

2199



被折叠的 条评论
为什么被折叠?



