http://fex.baidu.com/blog/2015/11/convert-svg-to-png-at-frontend/
前言
svg 是一種矢量圖形,在 web 上應用很廣泛,但是很多時候由於應用的場景,常常需要將 svg 轉為 png 格式,下載到本地等。隨着瀏覽器對 HTML 5 的支持度越來越高,我們可以把 svg 轉為 png 的工作交給瀏覽器來完成。
一般方式
- 創建 imageimage,src = xxx.svg;
- 創建 canvas,dragImage 將圖片貼到 canvas 上;
- 利用 toDataUrl 函數,將 canvas 的表示為 url;
- new image, src = 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
在一般的 web 應用中,可能很少會超過這些限制。但是,如果超過了這些限制,則會導致導出為空白或者由於內存泄露造成瀏覽器崩潰。
而且從另一方面來說,導出 png 也是一項很消耗內存的操作,粗略估算一下,導出 16, 384 px * 16, 384 px 的 svg 會消耗 16384 * 16384 * 4 / 1024 / 1024 = 1024 M 的內存。所以,在接近這些極限值的時候,瀏覽器也會反應變慢,能否導出成功也跟系統的可用內存大小等等都有關系。
對於這個問題,有如下兩種解決方法:
- 將數據發送給后端,在后端完成轉換;
- 前端將 svg 切分成多個圖片導出;
第一種方法可以使用 PhantomJS、inkscape、ImageMagick 等工具,相對來說比較簡單,這里我們主要探討第二種解決方法。
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 = canvas