移動端js模擬截圖生成圖片並下載功能的實現方案+踩坑過程

小p發表於2019-01-18

一. 專案中有需求如下:

將營業日報生成圖片下載至使用者手機儲存

二. 踩坑思路:

  1. 首先,因為用的是第三方的app(釘釘)內嵌webview開發,所以無法拿到截圖的api(而且需要生成的日報超出一個螢幕範圍,截圖也麻煩)
  2. 所以,自然想到了使用第三方工具canvas2html,將頁面中指定範圍的dom轉換為canvas
  3. 隨後使用canvas的apitoDataUrl獲得base64格式的圖片資料
  4. 此時試著直接用a標籤下載
`<a href="base64Url" download="name.jpg"></a>`
複製程式碼
  1. 實際證明該方法移動端失效,提示圖片下載失敗,因為移動端無法直接下載base64格式的圖片(據說pc端,chrome可以直接下載,其他瀏覽器貌似也有相容寫法,有意者可自行驗證)
  2. 現在只能先將圖片傳輸至後端儲存,隨後使用伺服器地址下載
  3. 為了方便後端處理,我們這裡不直接上傳base64格式的資料,先將base64轉換成blob,再模擬一個表單物件,將blob放進去,使用post提交給後端
  4. 拿到伺服器地址後,再來嘗試a標籤下載
  5. 這裡分兩種情況,如果圖片地址和專案同源,根據網上的說法,點選a標籤應該能直接下載成功了,這裡沒有驗證過
  6. 我這裡因為圖片存到了非同源的伺服器,所以點選a標籤後,無法自動下載,會轉跳到預設瀏覽器開啟圖片,隨後可以長按下載,這裡體驗就不好了
  7. 所以,最後放棄了a標籤的方案,變為新增一個彈出層,展示該圖片,提示使用者長按下載,至此比較完美的實現了該功能

三. 實現流程:

html2canvas將頁面轉換為canvas -> canvas轉換dataURL -> base64ToBlob(dataUrl轉換為2進位制檔案流) -> new FormData,將blob放置入該表單物件 -> post請求傳送至後端儲存圖片 -> 後端返回圖片地址 -> 使用線上地址展示圖片 -> 使用者長按儲存圖片

四. 下面逐步說明:

4.1 html2canvas
// 安裝
npm install html2canvas --save

// 引入
import Html2canvas from 'html2canvas';
Vue.prototype.html2canvas = Html2canvas;

// 使用
this.html2canvas(document.querySelector('#id'))
  .then((canvas) => {
    // todo...
  })
複製程式碼
4.2 canvas轉換dataURL
let dataUrl = canvas.toDataURL('image/jpeg');
複製程式碼
4.3 base64ToBlob
/**
 * base64轉blob
 * @param {String} code base64個數資料
 * @return {undefined}
 * @author xxx
 */
base64ToBlob (code) {
  let parts = code.split(';base64,');
  let contentType = parts[0].split(':')[1];
  let raw = window.atob(parts[1]);
  let rawLength = raw.length;
  let uInt8Array = new Uint8Array(rawLength);
  for (let i = 0; i < rawLength; ++i) {
    uInt8Array[i] = raw.charCodeAt(i);
  }
  return new window.Blob([uInt8Array], {type: contentType, name: 'file_' + new Date().getTime() + '.jpg'});
}

let blob = base64ToBlob(dataUrl);
複製程式碼
4.3 模擬formData並提交
// 新建formData
let formData = new FormData();
// 將blob存入
formData.append('file', blob);
複製程式碼

此處因為使用的是vue,所以用的axios,提交的時候要注意content-type

注意:建立請求的時候需要新建axios例項去請求,不要使用import進來的axios,不然content-type修改會不成功

import axios from 'axios';
// 建立新的axios例項
let instance = axios.create({});
// 設定content-type為false就行了
instance.defaults.headers.post['Content-Type'] = false;
// 這裡特別注意,建立請求的時候,使用這個新建立的例項去進行請求,而不要使用原來的axios
instance({
  method: 'POST',
  data: formData
})
  .then(res => {
    // todo...
  })
複製程式碼
4.4 使用返回的伺服器地址展示圖片,讓使用者長按儲存

這裡就不用說什麼了,自己建立一個遮罩層去實現就行了

五. 優化點

  1. 因為canvas2html -> 發起請求之間需要走過的步驟比較多,而且如果轉換的dom比較複雜,中間的處理事件會比較久,所以在這段時間中要做好相應的loading處理;不然使用者點選後,可能會看到1s左右的空窗期,而頁面什麼提示也沒有
  2. 生成圖片的按鈕要做好連續點選限制,避免使用者頻繁觸發

以上,功能就全部實現,雖然無法實現點選直接下載,但是也在條件允許的範圍內,實現比較好的使用者體驗了


六. 最後說一下在ios上面碰到的情況

這個方案在android上面一直測試的比較順利,但是在ios上面出現過一些疑問

主要就是canvas.toDataUrl()這個api失效

當時android順利調通,在ios上測試的時候,發現js執行到canvas.toDataUrl()就停止了,沒有返回值也沒有什麼錯誤提示

最後發現是自己新增的水印效果導致在ios執行canvas.toDataUrl()的時候無響應(canvas畫出水印,轉換base64,新增到父級backgroung-image的實現方式),改變了水印實現方式就正常執行了

下面把整整一天的踩坑過程寫下,給各位參考:

剛開始懷疑html2canvas轉化出來的canvas有問題,網上看了很多提問,包括github上面html2canvas的issue,有說可能是html2canvas版本問題的,有說可能是canvas畫出的圖片過大,導致canvas.toDataUrl()在ios上執行被系統強行阻止的各種說法,經過測試,都無法解決現在的問題

雖然最後證實了不是上述的問題,但還是將測試結果寫下

1. html2canvas版本問題:

npm預設安裝的是"html2canvas": "^1.0.0-alpha.12"這個alpha版本

隨後我測試過最後的正式release版本,v0.4.1,證實可以正常執行,但是碰到的無法轉換超出螢幕部分的dom,和轉換的圖片模糊的問題要花太多精力去解決,而且作者說了在舊版本有太多的bug,建議使用新的版本,所以最後放棄了舊版本的嘗試

2. canvas畫出的圖片過大,導致canvas.toDataUrl()在ios上執行被系統強行阻止

我轉換出來的圖片大小在200k左右(用的'image/jpeg'的型別),沒查到網上說的這個極限到底在哪裡

當時用自己新建的canvas重新壓縮了圖片,壓縮到只有1kb的時候都如法正常執行canvas.toDataUrl(),就基本排除這個問題了

相關文章