【JS】節點截圖的最終解決方案dom-to-image與html2canvas

說在前頭

這篇文章估計面向的人不多,所以我也不大篇幅的介紹這是幹啥的了

起先,我是單純想用domtoimage來解決我節點的截圖的,但嘗試了無數種方案,終是讓我敗下陣來

原因有仨

  1. 不使用代理且必須不能本地引入(當然前提是你圖片服務器不在內網,也就是外網也能請求到)
  2. 兼容所有移動端(難點在safari)
  3. 必須得到png圖的base64(domtoimage的svg導出模式是可行的,但除非你是下載svg格式,否則轉來轉去永遠也無法變成png的base64,這是一個看似有希望,但實則是一面南牆的坑)

敲鼓了一天半,最終得出的方案是dom-to-image與html2canvas來配合
當然你以爲單純使用就ok了嗎?No!不看下去,你依然會死在canvas.toDataURL裏


一、下載導入

注意我使用的版本是否與你相匹

{
	"dom-to-image": "^2.6.0",
    "html2canvas": "^1.0.0-rc.5"
}
import domtoimage from 'dom-to-image'
import html2canvas from 'html2canvas'

二、如何使用

1、定義主函數

主函數負責調用,並且我們需要他來判斷兩種機型的走向

const domToImage = ({ el, android, ios, success, error, handle } = {}) => {
    if (!el) {
      console.warn('domToImage: 未找到該節點,無法執行後續的截圖操作')
      return
    }
    // ios = html2canvas
    if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) {
      IosHandle(el, ios, success, error, handle)
    }
    // 安卓 || pc = domtoimage
    else {
      AndroidRender(el, android, success, error, handle)
    }
  }
}

我們來分下流,ios走html2canvas,安卓與PC走domtoimage,其中各參數介紹:

  • el:element dom節點
  • android:dom-to-image 配置項 - 具體options可參考後面官網的鏈接
  • ios:html2canvas 配置項
  • success:成功後的回調
  • error:失敗回調
  • handle:自處理回調,這裏我們將接收一個回調的返回值,布爾,若是false,則我們將拋出庫的執行結果返回給開發者,且不執行自身後續處理

爲什麼要分流?

這就是爲什麼使用這兩個庫的原因了,除了以上仨問題,還有就是domtoimage在safari裏支持非常不友好

message: “The operation is insecure.” (操作不安全)

知道這問題的人或許會與我感同身受吧

注意:變量名沒寫錯,這裏是IosHandle而不是IosRender,因爲還要做一層處理

2、安卓執行 - AndroidRender

安卓的參數較簡單,代碼量也很少並且出現的問題也不多

const AndroidRender = (el, options, success, error, handle) => {
  domtoimage.toPng(el, {
    ...options,
  })
    .then(base64 => {
      const isNext = handle && handle(base64)
      // 這就是上面所提的,若返回false,則讓開發人員執行自己的處理
      if (handle === false) {
        return
      }
      try {
        success && success(type, base64)
      } catch (err) {
        error && error(err)
      }
    })
    .catch(err => {
      error && error(err)
    })
}

安卓就沒什麼好說的了,無痛點

3、IOS執行

3.1 預處理 - IosHandle

這裏我們需要定義兩個函數來用

const IosHandle = (el, options, success, error, handle) => {
  const imgArr = el.querySelectorAll('img')
  let i = 0
  if (imgArr[0]) {
    [...imgArr].forEach((dom) => {
      getUrlBlob(dom.src, ((blob) => {
        if (blob !== false) {
          dom.src = blob
        }
        i ++
        console.log(i)
        // 校驗是否全部替換完畢
        if ((imgArr.length) === i) {
          IosRender(el, options, success, error, handle)
        }
      }))
    })
    return
  }
  IosRender(el, options, success, error, handle)
}

const getUrlBlob = (url, callback) => {
  const str = url.substring(0, 50)
  if (str.includes('blob:')) {
    return callback(false)
  }
  let canvas = document.createElement("canvas")
  let ctx = canvas.getContext("2d")
  let img = new Image
  img.crossOrigin = 'Anonymous'
  img.src = url
  img.onload = function () {
    canvas.height = img.height
    canvas.width = img.width
    ctx.drawImage(img, 0, 0)
    try {
      canvas.toBlob((blob) => {
        callback(URL.createObjectURL(blob))
      })
    } catch (err) {
      callback(img.src)
      console.error('轉換失敗,使用原圖', err)
    }
    canvas = null
  }
}

以上兩段代碼非常重要 - 核心是在html2canvas執行前先替換所有圖片轉換爲Blob,這種方式不會出現圖片缺失的情況

圖片缺失:表現的症狀是截圖時,偶爾有圖片丟失,這種情況是因爲html2Canvas內部又對節點內的圖片進行了一次請求,而此次請求不會管加載是否完畢,將直接轉換爲canvas生成圖,恰恰這種情況若是本地圖就不會出現(初次請求就被緩存了),而根據Blob不會重複請求的特性,我們需要在IosRender前先對他進行Blob的轉換,所以纔有了上方的IosHandle

3.2 執行 - IosRender

到這裏還沒完,再定義兩個函數,我們要防白邊固定截圖位置

const getOffsetTop = (el) => {
  let top = el.offsetTop
  let parent = el.offsetParent
  while (parent) {
    top += parent.offsetTop
    parent = parent.offsetParent
  }
  return top
}

const getOffsetLeft = (el) => {
  let left = el.offsetLeft
  let parent = el.offsetParent
  while (parent) {
    left += parent.offsetLeft
    parent = parent.offsetParent
  }
  return left
}

const IosRender = (el, options, success, error, handle) => {
  // 脫離下主線程
  setTimeout(() => {
    html2canvas(el, {
      scale: 2,
      allowTaint: true,
      useCORS: true,
      width: el.offsetWidth,
      height: el.offsetHeight,
      x: getOffsetLeft(el),
      y: getOffsetTop(el),
      ...options,
    })
      .then(canvas => {
        const isNext = handle && handle(canvas)
        // 若返回爲false,則讓開發人員執行自己的處理
        if (handle === false) {
          return
        }
        try {
          const base64 = canvas.toDataURL('image/png')
          success && success(type, canvas, base64)
        } catch (err) {
          error && error(err)
        }
      })
      .catch(err => {
        error && error(err)
      })
  }, 500)
}

到這裏爲止也就差不多了,拿到canvas圖片的base64,於是便想下載就下載,想幹嘛就幹嘛,但是也有弊端,因爲IosHandle的緣故,處理時間略微會有點長,所以得添加下loading界面讓用戶感知一下

注意:html2canvas的ignoreElements過濾屬性該版本是不支持的,你可以選擇給需要過濾的標籤動態添加“data-html2canvas-ignore”屬性,不過這又會有一個問題,隱藏後會與導出的截圖高度不匹,需要你手動處理

注意:scale: 2 不是爲了讓圖片更清楚,而是你不設置這個值,iphoneX就等着哭吧

4、調用

domToImage({
  el: document.querySelector('.snapshot'),
  android: {
    // options ...
  },
  ios: {
    // options ...
  },
  handle (data) {
  	console.log(data)
  },
  success (val) {
    console.log(val)
  },
  error(err) {
    console.error(err)
  }
})

正常來說是夠用了,若是你還需要更優的效果或處理(高清、銳化),可以用options or handle自行使用

Tips:用於截圖的組件通常是隱藏着的,父節點 + opacity 或 定位 + zIndex 又或者離開可視範圍都行,但要注意 display: none 與 visibility: hidden 是不行的


三、外部鏈接

html2Canvas Options 配置項

dom-to-imgage Options 配置項


關於

make:o︻そ╆OVE▅▅▅▆▇◤(清一色天空)

blog:http://blog.csdn.net/mcky_love

掘金:https://juejin.im/user/59fbe6c66fb9a045186a159a/posts

lofter:http://zcxy-gs.lofter.com/

sf:https://segmentfault.com/u/mybestangel

git:https://github.com/gs3170981

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章