如何将含CSS背景图的canvas c2内容完整导出到canvas c3?
问题
我有一张id为picc的图片,先使用drawImage()方法将其绘制到canvas c1中;接着通过getImageData()获取c1的图像数据,修改部分像素的透明度后,用putImageData()将处理后的数据放到带有CSS背景图的canvas c2中。请问如何让另一个canvas c3完整包含c2的输出内容(包括其CSS背景图)?
当前代码如下(注:渲染canvas时可能存在跨域问题):
现有代码
HTML
<div> <img src='https://previews.123rf.com/images/rabbybsl/rabbybsl2008/rabbybsl200800018/153141202-holi-color-background-on-white-background.jpg' id="picc" width="360" height="640"/> </div> <div> <canvas id="c1"></canvas> <canvas id="c2"></canvas> </div>
CSS
#c2 { background-image: url("https://cdn.britannica.com/84/4284-050-16C7E8C2/Photograph-Jupiter-range-Voyager-1-cloud-bands-February-1-1979.jpg"); background-repeat: no-repeat; background-size: cover; }
JavaScript
document.addEventListener("DOMContentLoaded", () => { img = document.getElementById("picc"); c1 = document.getElementById("c1"); ctx1 = c1.getContext("2d"); c2 = document.getElementById("c2"); ctx2 = c2.getContext("2d"); c1.width = c2.width = img.width; c1.height = c2.height = img.height; ctx1.drawImage(img, 0, 0, img.width, img.height); let frame = ctx1.getImageData(0, 0, c1.width, c1.height); let l = frame.data.length / 4; for (let i = 0; i < l; i++) { let r = frame.data[i * 4 + 0]; let g = frame.data[i * 4 + 1]; let b = frame.data[i * 4 + 2]; if (g >= 242 && r >= 244 && b <= 243) frame.data[i * 4 + 3] = 0 } ctx2.putImageData(frame, 0, 0); });
解决方案
canvas的drawImage()只能捕获目标canvas自身的像素数据,读不到它的CSS背景样式,所以要让c3完整包含c2的内容(背景+canvas绘制层),有两种实用方法:
方法一:先给c3画背景,再叠上c2的内容
这个方案不用改你现有的c2代码,只要新增c3的处理逻辑:
- 新建一个
Image对象,加载c2的CSS背景图 - 等背景图加载完成后,按照c2的
background-size: cover规则绘制到c3上 - 再把c2的canvas内容叠加到c3上
修改后的完整代码
HTML(新增c3)
<div> <img src='https://previews.123rf.com/images/rabbybsl/rabbybsl2008/rabbybsl200800018/153141202-holi-color-background-on-white-background.jpg' id="picc" width="360" height="640"/> </div> <div> <canvas id="c1"></canvas> <canvas id="c2"></canvas> <canvas id="c3"></canvas> <!-- 新增c3 --> </div>
JavaScript(新增c3处理逻辑)
document.addEventListener("DOMContentLoaded", () => { img = document.getElementById("picc"); c1 = document.getElementById("c1"); ctx1 = c1.getContext("2d"); c2 = document.getElementById("c2"); ctx2 = c2.getContext("2d"); c3 = document.getElementById("c3"); ctx3 = c3.getContext("2d"); c1.width = c2.width = c3.width = img.width; c1.height = c2.height = c3.height = img.height; // 原有c1、c2的处理逻辑 ctx1.drawImage(img, 0, 0, img.width, img.height); let frame = ctx1.getImageData(0, 0, c1.width, c1.height); let l = frame.data.length / 4; for (let i = 0; i < l; i++) { let r = frame.data[i * 4 + 0]; let g = frame.data[i * 4 + 1]; let b = frame.data[i * 4 + 2]; if (g >= 242 && r >= 244 && b <= 243) frame.data[i * 4 + 3] = 0 } ctx2.putImageData(frame, 0, 0); // 处理c3:先绘制背景图,再叠加c2的内容 const bgImg = new Image(); // 跨域处理:如果图片来自外部域名,需服务器支持CORS bgImg.crossOrigin = "anonymous"; bgImg.src = getComputedStyle(c2).backgroundImage.replace(/url\(['"]?(.*?)['"]?\)/, '$1'); bgImg.onload = () => { // 按照cover模式绘制背景图 const scale = Math.max(c3.width / bgImg.width, c3.height / bgImg.height); const x = (c3.width - bgImg.width * scale) / 2; const y = (c3.height - bgImg.height * scale) / 2; ctx3.drawImage(bgImg, x, y, bgImg.width * scale, bgImg.height * scale); // 叠加c2的canvas内容 ctx3.drawImage(c2, 0, 0); }; });
方法二:把背景图直接画到c2的canvas内部
直接调整c2的实现,不用CSS背景,改成把背景图画到canvas内部,这样c3直接调用drawImage(c2)就能拿到所有内容:
- 先加载背景图,绘制到c2上
- 再把处理后的透明图像叠上去(注意
putImageData()会覆盖像素,所以得先画背景)
修改后的完整代码
CSS(移除c2的背景样式)
/* 移除#c2的背景样式 */
JavaScript(修改c2的绘制逻辑)
document.addEventListener("DOMContentLoaded", () => { img = document.getElementById("picc"); c1 = document.getElementById("c1"); ctx1 = c1.getContext("2d"); c2 = document.getElementById("c2"); ctx2 = c2.getContext("2d"); c3 = document.getElementById("c3"); ctx3 = c3.getContext("2d"); c1.width = c2.width = c3.width = img.width; c1.height = c2.height = c3.height = img.height; // 原有c1的处理逻辑 ctx1.drawImage(img, 0, 0, img.width, img.height); let frame = ctx1.getImageData(0, 0, c1.width, c1.height); let l = frame.data.length / 4; for (let i = 0; i < l; i++) { let r = frame.data[i * 4 + 0]; let g = frame.data[i * 4 + 1]; let b = frame.data[i * 4 + 2]; if (g >= 242 && r >= 244 && b <= 243) frame.data[i * 4 + 3] = 0 } // 修改c2:先绘制背景图,再叠加处理后的图像 const bgImg = new Image(); bgImg.crossOrigin = "anonymous"; bgImg.src = "https://cdn.britannica.com/84/4284-050-16C7E8C2/Photograph-Jupiter-range-Voyager-1-cloud-bands-February-1-1979.jpg"; bgImg.onload = () => { // 按照cover模式绘制背景 const scale = Math.max(c2.width / bgImg.width, c2.height / bgImg.height); const x = (c2.width - bgImg.width * scale) / 2; const y = (c2.height - bgImg.height * scale) / 2; ctx2.drawImage(bgImg, x, y, bgImg.width * scale, bgImg.height * scale); // 叠加处理后的图像 ctx2.putImageData(frame, 0, 0); // c3直接绘制c2即可 ctx3.drawImage(c2, 0, 0); }; });
注意事项
- 跨域问题:如果使用的图片来自外部域名,需要确保图片服务器配置了CORS允许跨域访问,否则
getImageData()和drawImage()会抛出安全错误。可以通过设置img.crossOrigin = "anonymous"尝试解决,但最终取决于服务器是否支持。
内容的提问来源于stack exchange,提问作者ChessAndCode
相关产品推荐
相关产品推荐

