toDataURL 截出来是黑图,Cesium 也一样
点击“导出图片”,拿到一张纯黑或全透明的 PNG;同一段代码在别的页面又是正常的。
现象
页面上地球显示正常,调用截图后得到 1920×1080 的黑色图片。 部分浏览器不报错,部分会给出提示:
Uncaught DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement':
Tainted canvases may not be exported.
如果是黑色而不是报错,说明跨域问题不存在,问题在绘制缓冲已被清空。
原因
WebGL 的绘制缓冲默认不保留。浏览器在合成到屏幕后就会清空缓冲,
此时再调用 toDataURL,读到的是一张空画布。
Cesium 的 Viewer 构造时没有打开 preserveDrawingBuffer,
所以任何异步延迟之后再截图都会拿到黑图。
另一个独立问题是影像跨域:瓦片服务器没返回
Access-Control-Allow-Origin 时,画布被标记为污染,导出会抛出上面的 DOMException。
排查过程
- 在点击事件里立即调用
toDataURL,看是否仍为黑图,以区分时序问题与跨域问题。 - 看报错文本:出现
Tainted canvases就是跨域,没有则是缓冲问题。 - 检查是否有
setTimeout、await或requestAnimationFrame把截图推迟到下一帧之后。
解决方案
构造时打开缓冲保留。代价是每帧多一次拷贝,帧率会略降,建议只在需要截图的页面开启:
var viewer = new Cesium.Viewer('cesiumContainer', {
contextOptions: {
webgl: {
preserveDrawingBuffer: true,
alpha: false
}
}
});
不想改构造参数,就在渲染完成的同一帧里取图:
function capture() {
return new Promise(function (resolve) {
var remove = viewer.scene.postRender.addEventListener(function () {
remove();
var canvas = viewer.scene.canvas;
resolve(canvas.toDataURL('image/png'));
});
viewer.scene.requestRender();
});
}
capture().then(function (dataUrl) {
var a = document.createElement('a');
a.href = dataUrl;
a.download = 'scene.png';
a.click();
});
影像跨域时,给瓦片服务补上响应头,并把影像提供者的跨域开关打开:
var provider = new Cesium.UrlTemplateImageryProvider({
url: 'https://tile.example.cn/{z}/{x}/{y}.png',
credit: 'Example',
tilingScheme: new Cesium.WebMercatorTilingScheme()
});
// 服务端需返回:Access-Control-Allow-Origin: *
// Nginx 示例
// location /tiles/ { add_header Access-Control-Allow-Origin *; }
验证
截图得到的 PNG 用图片查看器打开,应能看到与页面一致的底图和模型;
canvas.toDataURL().length 明显大于纯黑图的长度;
连续截三张,尺寸和内容都一致。
性能取舍:preserveDrawingBuffer: true 在移动端会增加显存占用。
只在导出页面开启,其余页面保持默认;或者用 postRender 方案,
一次截图只多渲染一帧。
经纬志(2026)。《toDataURL 截出来是黑图,Cesium 也一样》。https://www.jingweizhi.com/cookbook/cesium-screenshot-black.html(访问日期 2026-11-03)。