前言svg 是一种矢量图形,在 web 上应用很广泛,但是很多时候由于应用的场景,常常需要将 svg 转为 png 格式,下载到本地等 。随着浏览器对 html 5 的支持度越来越高,我们可以把 svg 转为 png 的工作交给浏览器来完成 。
一般方式
- 创建 imageimage,src = https://www.isolves.com/it/cxkf/yy/html5/2019-09-04/xxx.svg;
- 创建 canvas,dragImage 将图片贴到 canvas 上;
- 利用 toDataUrl 函数,将 canvas 的表示为 url;
- new image, src = https://www.isolves.com/it/cxkf/yy/html5/2019-09-04/url, download = download.png;
问题 1 :浏览器对 canvas 限制
Canvas 的 W3C 的标准上没有提及 canvas 的最大高/宽度和面积,但是每个厂商的浏览器出于浏览器性能的考虑,在不同的平台上设置了最大的高/宽度或者是渲染面积,超过了这个阈值渲染的结果会是空白 。测试了几种浏览器的 canvas 性能如下:
- chrome (版本 46.0.2490.80 (64-bit))
- 最大面积:268, 435, 456 px^2 = 16, 384 px * 16, 384 px
- 最大宽/高:32, 767 px
- firefox (版本 42.0)
- 最大面积:32, 767 px * 16, 384 px
- 最大宽/高:32, 767px
- safari (版本 9.0.1 (11601.2.7.2))
- 最大面积: 268, 435, 456 px^2 = 16, 384 px * 16, 384 px
- ie 10(版本 10.0.9200.17414)
- 最大宽/高: 8, 192px * 8, 192px
而且从另一方面来说,导出 png 也是一项很消耗内存的操作,粗略估算一下,导出 16, 384 px * 16, 384 px 的 svg 会消耗 16384 * 16384 * 4 / 1024 / 1024 = 1024 M 的内存 。所以,在接近这些极限值的时候,浏览器也会反应变慢,能否导出成功也跟系统的可用内存大小等等都有关系 。
对于这个问题,有如下两种解决方法:
- 将数据发送给后端,在后端完成转换;
- 前端将 svg 切分成多个图片导出;
svg 切分成多个图片导出
思路:浏览器虽然对 canvas 有尺寸和面积的限制,但是对于 image 元素并没有明确的限制,也就是第一步生成的 image 其实显示是正常的,我们要做的只是在第二步 dragImage 的时候分多次将 image 元素切分并贴到 canvas 上然后下载下来 。同时,应注意到 image 的载入是一个异步的过程 。
关键代码:
// 构造 svg Url,此处省略将 svg 经字符过滤后转为 url 的过程 。var svgUrl = DomURL.createObjectURL(blob);var svgWidth = document.querySelector('#kity_svg').getAttribute('width');var svgHeight = document.querySelector('#kity_svg').getAttribute('height');// 分片的宽度和高度,可根据浏览器做适配var w0 = 8192;var h0 = 8192;// 每行和每列能容纳的分片数var M = Math.ceil(svgWidth / w0);var N = Math.ceil(svgHeight / h0);var idx = 0;loadImage(svgUrl).then(function(img) { while(idx < M * N) {// 要分割的面片在 image 上的坐标和尺寸var targetX = idx % M * w0,targetY = idx / M * h0,targetW = (idx + 1) % M ? w0 : (svgWidth - (M - 1) * w0),targetH = idx >= (N - 1) * M ? (svgHeight - (N - 1) * h0) : h0;var canvas = document.createElement('canvas'),ctx = canvas.getContext('2d');canvas.width = targetW;canvas.height = targetH;ctx.drawImage(img, targetX, targetY, targetW, targetH, 0, 0, targetW, targetH);console.log('now it is ' + idx);// 准备在前端下载var a = document.createElement('a');a.download = 'naotu-' + idx + '.png';a.href = https://www.isolves.com/it/cxkf/yy/html5/2019-09-04/canvas.toDataURL('image/png');var clickEvent = new MouseEvent('click', {'view': window,'bubbles': true,'cancelable': false});a.dispatchEvent(clickEvent);idx++; }}, function(err) { console.log(err);});// 加载 imagefunction loadImage(url) { return new Promise(function(resolve, reject) {var image = new Image();image.src = url;image.crossOrigin = 'Anonymous';image.onload = function() {resolve(this);};image.onerror = function(err) {reject(err);}; });}说明:
- 由于在前端下载有浏览器兼容性、用户体验等问题,在实际中,可能需要将生成后的数据发送到后端,并作为一个压缩包下载 。
- 分片的尺寸这里使用的是 8192 * 9192,在实际中,为了增强兼容性和体验,可以根据浏览器和平台做适配,例如在 IOS 下的 safari 的最大面积是 4096 *4096 。
在导出的时候,还会碰到另一个问题:如果 svg 里面包含图片,你会发现通过以上方法导出的 png 里面,原来的图片是不显示的 。一般认为是 svg 里面包含的图片跨域了,但是如果你把这个图片换成本域的图片,还是会出现这种情况 。
推荐阅读
- 从底层彻底搞懂String,StringBuilder,StringBuffer的实现
- 如何设计实现一个轻量的开放API网关
- 恶意软件是如何使DNS隐蔽信道通信技术实现通信的?
- iPhone竟然不会长截图?两种方法教你实现
- 基于分布式系统的7种唯一ID实现方案
- 马蜂窝API 资源隔离系统设计与实现
- 一张图带你理解和实现RabbitMQ的延迟队列功能
- 实现一个 Golang Module Proxy
- Java 分布式系统如何实现session共享?
- 前端:html+css+javascript 手把手教大家编写贪吃蛇小游戏