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

一. 項目中有需求以下:

將營業日報生成圖片下載至用戶手機保存html

二. 踩坑思路:

  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請求發送至後端保存圖片 -> 後端返回圖片地址 -> 使用線上地址展現圖片 -> 用戶長按保存圖片vue

四. 下面逐步說明:

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-typeandroid

注意:建立請求的時候須要新建axios實例去請求,不要使用import進來的axios,否則content-type修改會不成功ios

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 使用返回的服務器地址展現圖片,讓用戶長按保存

這裏就不用說什麼了,本身建立一個遮罩層去實現就好了git

五. 優化點

  1. 由於canvas2html -> 發起請求之間須要走過的步驟比較多,並且若是轉換的dom比較複雜,中間的處理事件會比較久,因此在這段時間中要作好相應的loading處理;否則用戶點擊後,可能會看到1s左右的空窗期,而頁面什麼提示也沒有
  2. 生成圖片的按鈕要作好連續點擊限制,避免用戶頻繁觸發

以上,功能就所有實現,雖然沒法實現點擊直接下載,可是也在條件容許的範圍內,實現比較好的用戶體驗了github


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

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

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

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

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

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

剛開始懷疑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(),就基本排除這個問題了

相關文章
相關標籤/搜索