把網頁導出為圖片的兩種方案以及其適用場景
先列出解決方案給出結論,再開啟啰嗦模式:
- html網頁通過html2canvas插件--->canvas畫布通過(toDataURL/toBlob)--->png/pdf/jpg--->通過a標籤 download屬性下載(方案一:純前端方案)
- 直接用phantom.js 或casper.js 之類的node插件,用headless WebKit 去模擬打開頁面,然後導出為圖片/pdf,接著上傳到OSS,把對應鏈接返回給前端,實現下載功能。(方案二:後端方案)
結論:當html內容在canvas可繪製能力範圍內的,用方案一(版本2:blob方式),當html內容超過了canvas可繪製能力範圍,用方案二。
啰嗦模式中,主要描述的是使用方案一在開發過程中遇到的一些問題以及探究引起這些問題的原因,尋找解決方案的過程。
===============啰嗦模式分割線,下面開啟啰嗦模式===================
直觀地對比兩種方案:
方案一:只需要一個前端參與 + 引入一個第三方插件就能搞定,而且實現的鏈路也不長。
方案二:需要前後端協同 + 項目需另起一個node環境 + OSS資源 + 多用戶並發問題 不管是從實現複雜度還是成本上,都比較大。
直觀對比結果:在理想情況下,顯然方案一更優。
方案一在開發中遇到的問題
最直觀的表現為:當html網頁內容過多時,圖片下載失敗。
先貼一下方案一(版本1)的代碼
// canvas對象為 html2canvas 插件得到的canvas對象var base64 = canvas.toDataURL();var link = document.createElement(a);link.textContent = download image;link.href = base64;link.download = "mypainting.jpeg";link.click()
圖片下載失敗有兩個原因:1. base64圖片通過a標籤href+download方式下載對圖片的尺寸也有上限。2.canvas 畫布繪製能力有上限。
隨後我拋出了這麼幾個問題:
- base64圖片本地實測大於1.6M的為什麼下載不下來?具體的下載零界點怎麼界定?
- 瀏覽器本地下載大圖(大於1.6M,網上查到一些資料說是2M)有什麼替代方案?
- 影響canvas畫布容積上限的因素有哪些?(畫布寬?高?面積?顏色複雜程度?)上限具體是多少?
為了回答這幾個問題,我寫了個小demo(代碼詳見本文最後): 頁面上總共兩個元素,一個按鈕和一張canvas畫布,點下按鈕就可以把canvas作為圖片導出。我們可以改變canvas的寬高或者顏色複雜度去觀察canvas的渲染狀態和圖片的下載狀態。
可以貼下我實驗測得的結果【canvas畫布採用純黑色渲染,下載方式是base64(demo中mode的值為base64)】:
寬*高 canvas渲染狀態 base64長度 圖片可下載與否 圖片尺寸 1000*32700 canvas可以被渲染 base64長度 511403 圖片可下載 1000*32800 canvas不能被渲染 2400*32700 canvas可以被渲染 base64長度 1226803 圖片可下載 圖片尺寸1.4M 2600*32700 canvas可以被渲染 base64長度 2084390 圖片可下載 圖片尺寸1.6M 2700*32700 canvas可以被渲染 base64長度 2152518 圖片不可下載 8200*32700 canvas可以被渲染 base64長度 6397386 圖片不可下載 8300*32700 canvas不能被渲染
回答問題1:
從實驗結果可以看到,當base64長度為 2084390 時候,圖片可下載,當base64長度為2152518 時候,圖片下載失敗。先推測一下,這個下載極限就是base64長度為2 X 1024 X 1024 = 2097152。那為什麼會下載失敗呢?先來看下canvas.toDataURL(),MDN文檔上對Data URLs 的定義
Data URLs,即為前綴為
data:scheme
的URL,其允許內容創建者向文檔中嵌入小文件。
在data URLs相關的常見問題中能看到
長度限制
雖然 Firefox 支持無限長度的data
URLs,但是標準中並沒有規定瀏覽器必須支持任意長度的data
URIs。比如,Opera 11瀏覽器限制 URLs 最長為 65535 個字元,這意外著 data URLs 最長為 65529 個字元(如果你使用純文本 data:, 而不是指定一個 MIME 類型的話,那麼 65529 字元長度是編碼後的長度,而不是源文件)。
所以data URLs 當初的提出就是為了小文件,不同的瀏覽器對最長data URLs 有自己的字元限制,chrome下載限度是 base64長度 2M(而非圖片大小2M)。
另附chrome 上的一個 issue,在2011年的時候已經有一哥們反應這個問題了,到現在這個issue的status 還是 available。可以去圍觀一下:https://bugs.chromium.org/p/chromium/issues/detail?id=69227。沒找到chrome官方對data URLs的長度limit申明,暫且也可認為這是一個chrome一個歷史悠久的bug。
回答問題2:
從問題1可以知道引發不能下載的原因就是data URL 的長度導致圖片不可下載。有兩個方法來解決這個問題:
方法1:拿到base64編碼後,把它發給後端,讓後端轉成 www.xxx.com/a.jpg 類似的oss網路圖片。該方法可行,但傳過長的base64編碼需要後台去改一些一個請求大小上限之類的配置,速度體驗也非常的慢,不建議用此方法。
方法2:用canvas toBlob() 方式把canvas 轉化成 blob對象,再用 url = URL.createObjectURL(blob); 為blob對象生成一個指向blob圖片對象URL,url的長度一般就40多(類似blob:null/580f6db8-8a79-43c1-baef-f07e84484492)。實測只要是canvas能渲染出來的,通過這種方式都能下載成功。(實測用canvas畫了幅極其複雜的圖,就是文章開頭的那副五顏六色怪圖,通過此方法能導出成功,圖片大小為196M。)
方案一(版本2)代碼:
canvas.toBlob(function(blob) { url = URL.createObjectURL(blob); var link = document.createElement(a); link.textContent = download image; link.href = url; link.download = "mypainting.jpeg"; link.click() // no longer need to read the blob so its revoked URL.revokeObjectURL(url);});
回答問題3:
canvas畫布容積會有一個上限,和寬,高,顏色複雜程度,電腦顯卡性能有關,
不相關:
- 和面積(寬*高)不太相關。因為例子中 8200*32700 畫布可以被渲染,而1000*32800卻不可被渲染。
- 此容積也無法用導出圖片的體積來衡量,通過此方法可以導出大小為196M的圖片,已經是非常大了依舊可以導出,但在全黑模式下增加canvas寬高,導出的極限尺寸在4.8M。
因為canvas畫布容積和畫面的顏色複雜程度,電腦顯卡性能相關,所以上面測出的寬高並沒有代表性。從我的角度,暫時找不到用具體的數值來量化。(如果你有更好的想法,非常歡迎評論交流)。
總之,在方案一圖片下載失敗的兩個原因中,瀏覽器base64圖片下載上限問題可以通過blob方式(方案一版本2)解決,但canvas畫布繪製能力上限問題沒法通過前端來解決,需採用方案二。
方案二我相信這是圖片下載的一個終極實現(雖然實現是麻煩了點兒),沒有任何限制,之前做過類似的項目,用casperjs的capture()函數去做這個事情,幾百M的pdf導出問題都不大。在現在這個項目中,因為後台是java,所以會採用java的方案去做這個事情。
所以回到開頭的那個結論:當html內容在canvas可繪製能力範圍內的,用方案一(版本2:blob方式),當html內容超過了canvas可繪製能力範圍,用方案二。
最後附上demo代碼(其中 mode,canvasWidth,canvasHeight,drawTimes可自定義調節)
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>canvas test</title></head><body> <button id="btn">下載canvas</button> <canvas id="canvas"></canvas></body><script type="text/javascript">/** * canvas全黑的狀況下,mode = base64 * 寬*高 canvas渲染狀態 base64長度 圖片可下載與否 圖片尺寸 * 1000*32700 canvas可以被渲染 base64長度 511403 圖片可下載 * 1000*32800 canvas不能被渲染 * 2400*32700 canvas可以被渲染 base64長度 1226803 圖片可下載 圖片尺寸1.4M * 2600*32700 canvas可以被渲染 base64長度 2084390 圖片可下載 圖片尺寸1.6M * 2700*32700 canvas可以被渲染 base64長度 2152518 圖片不可下載 * 8200*32700 canvas可以被渲染 base64長度 6397386 圖片不可下載 * 8300*32700 canvas不能被渲染 * * 結論 * 1.canvas 渲染畫布有一個上限。相關因素:寬,高(但與寬*高不呈現正相關關係),畫面顏色複雜程度 * 2.相同尺寸的畫布,增加顏色會增加圖片的尺寸。==》所以上面測試出來的寬高數據沒代表性。 * 3.base64模式下,可支持下載的零界點 在 2*1024*1024 = 2097152 左右 *//** * 可選值 base64, blob * @type {String} */// var mode = base64;var mode = blob;var canvasWidth = 2400var canvasHeight = 32700;/** * 更改drawTimes 可以增加畫面顏色複雜度 * 可選值 0 ~ 10020000 */var drawTimes = 0;// var drawTimes = 10020000;var canvas = document.getElementById(canvas);var ctx = canvas.getContext(2d);canvas.width = canvasWidth;canvas.height = canvasHeight;ctx.fillStyle = black;ctx.fillRect(0, 0, canvasWidth, canvasHeight);/** * [根據drawTimes隨機化一些色塊,增加canvas顏色複雜度] */for(var i =0;i<drawTimes;i++){ var colors = [red,yellow,grey,green,blue,white] ctx.fillStyle = colors[i%6]; ctx.fillRect(i/drawTimes*canvasWidth, Math.random()*canvasHeight, 10, 10);} if (mode === base64) { document.getElementById("btn").addEventListener("click", function(event) { var base64 = canvas.toDataURL(); console.log(base64.length) var link = document.createElement(a); link.textContent = download image; link.href = base64; link.download = "mypainting.jpeg"; link.click() }, false);} else if (mode === blob) { document.getElementById("btn").addEventListener("click", function(event) { canvas.toBlob(function(blob) { url = URL.createObjectURL(blob); console.log(url) console.log(url.length) var link = document.createElement(a); link.textContent = download image; link.href = url; link.download = "mypainting.jpeg"; link.click() // no longer need to read the blob so its revoked URL.revokeObjectURL(url); }); }, false);}</script></html>
推薦閱讀:
※網頁中實現正六邊形的N種姿勢
※小白記Canvas實現的一個小玩意 - 你的名字頭像生成
※vue-schart : vue.js 的圖表組件
※canvas可以替代html與css了嗎?
※如何優雅的用Flash開發Html5交互項目