GIS 地图打印时空白问题
前言
之前基于 vue-print-np
改造开发了新版的 Vue 打印插件: vue-print-next
,虽然只在一篇文章里提了一句,但也陆续收获了几十个 star。尽管 npm 每周下载量仅有 20-30 次,但能有人使用,我还是感到非常开心。
前段时间有一位用户提了一个 issue,表示在打印地图(如 GIS,类似百度地图、高德地图)时,打印结果是空白的。定位这个问题花费了不少时间,最终找到了原因并解决。本文将复现问题,并详细分析其原因与解决方法。
问题复现
以下是一个用于复现问题的简单 Demo:
1 |
|
上述代码中的 canvas
在页面显示时有一个绿色背景方块,但打印结果却是灰白的。这只是一个简单例子,实际上无论 canvas
绘制什么内容,打印结果都只会显示为灰白背景。
如下是打印后的效果:
问题分析
要分析这个问题,首先需要了解目前常用的局部打印方案及其对 canvas
的处理方式。
局部打印原理
局部打印通常通过以下步骤实现:
- 获取需要打印的 DOM 元素。
- 将该元素克隆到一个新的
iframe
中。 - 调用浏览器的打印功能打印该
iframe
。
对于 canvas
元素,为了确保打印效果,打印库一般会调用 canvas.toDataURL
方法将 canvas
内容转换为 base64
格式的图片,然后将其替换为 img
标签,从而正常显示。
由此可以推断,地图打印为空白的原因很可能是 toDataURL
输出的内容有问题。
尝试直接输出
在控制台中直接调用 toDataURL
输出地图的内容:
1 |
|
通过工具解码后,结果是一个空白图片。这表明 toDataURL
方法并未正确转换 canvas
内容。
为了进一步排查,我们尝试使用其他绘图工具(如 ECharts)生成 canvas
并导出 base64
,结果是正常的。这说明问题与 WebGL 绘图方式有关。
问题根源
通过查阅资料发现,WebGL 模式下,preserveDrawingBuffer
参数默认值为 false
。这是出于性能和内存优化的考虑,在每帧渲染完成后会自动清除画布缓存。对于需要保留画布数据的场景(如打印、截图),这种行为会导致 toDataURL
方法无法获取画布内容,从而输出空白。
解决方法
有两种常见的解决方法:打印前重绘一次,或者开启 preserveDrawingBuffer
配置
方法一:打印前重绘
最简单的一种方式,如果你使用的库支持,或者本身就是你自己写的 webgl 方法。
那么可以在打印之前调用一次渲染方法,这样就可以成功打印了。
1 |
|
在调用打印功能前调用一次绘制方法 draw()
,确保 canvas
内容在打印时是完整的。
方法二:开启配置
在初始化 WebGL 上下文时,将 preserveDrawingBuffer
参数设置为 true
,以确保每次绘制后画布内容不会被清除。
可以通过重写 getContext
方法实现:
1 |
|
在此方法中,我们拦截了 canvas.getContext
方法的调用,将 preserveDrawingBuffer
设置为 true
后再返回上下文。这样在打印时无需额外重绘即可获取正确的内容。
总结
WebGL 中 preserveDrawingBuffer
默认值为 false
是导致打印空白的根本原因。对于需要打印或截图的场景,可以根据需求选择以下方案:
- 打印前重绘:适合对性能要求高、不希望永久开启
preserveDrawingBuffer
的场景。 - 启用
preserveDrawingBuffer
:适合打印或截图需求频繁、对性能要求不高的场景。
如果使用的库(webgl 地图)支持重新渲染方法,还是推荐仅在打印前重绘,避免因为开启preserveDrawingBuffer
导致造成性能问题。