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

說在前頭

2020-06-03 新添注意事項javascript

若不須要極致的效果的話,請全篇使用html2canvas便可,不須要domtoimage了!html

正文

這篇文章估計面向的人很少,因此我也不大篇幅的介紹這是幹啥的了 ... 起先,我是單純想用domtoimage來解決我節點的截圖的,但嘗試了無數種方案,終是讓我敗下陣來java

緣由有仨android

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

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


1、下載導入

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

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

2、如何使用

一、定義主函數

主函數負責調用,而且咱們須要他來判斷兩種機型的走向github

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,其中各參數介紹:web

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

爲何要分流?canvas

這就是爲何使用這兩個庫的緣由了,除了以上仨問題,還有就是domtoimage在safari裏支持很是不友好segmentfault

message: "The operation is insecure." (操做不安全)

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

注意:變量名沒寫錯,這裏是IosHandle而不是IosRender,由於還要作一層處理

二、安卓執行 - AndroidRender

安卓的參數較簡單,代碼量也不多,但這並不表明他沒有問題

const AndroidRender = (el, options, success, error, handle) => {
  domtoimage.toPng(el, {
    ...options,
    quality: 0.95,
  })
    .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)
    })
}
複製代碼

注意toJpeg質量會有點低,但toPng在某些機型上好像會有黑白屏的兼容性問題(不肯定)

注意:別小看了quality參數,若不設置,你仍然會出現速度慢的狀況,0.95質量的差異就是1mb與200kb的差距

三、IOS執行

3.1 預處理 - IosHandle

這裏咱們須要定義兩個函數來用

const IosHandle = (el, options, success, error, handle) => {
  const imgArr = el.querySelectorAll('img')
  let i = 0
  if (imgArr[0]) {
    let timer = setInterval(() => {
      clearInterval(timer)
      if (imgArr.length !== i) {
        error && error('超時')
      }
    }, 10000);
    [...imgArr].forEach((dom) => {
      getUrlBlob(dom.src, ((blob) => {
        if (blob !== false) {
          dom.src = blob
        }
        i ++
        console.log(i)
        // 校驗是否所有替換完畢
        if ((imgArr.length) === i) {
          clearInterval(timer)
          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)
  }
  // 避免img未有src屬性的狀況,致使未返回
  if (!str) {
    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

問題二:爲何要使用setInterval來進行超時的判斷,由於要避免跨域圖片的存在而致使try捕獲不到的異常

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就等着哭吧

四、調用

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 是不行的


3、異常總結(持續更新)

以上避免了絕大多數狀況,但仍有些問題需注意

1)Dom必須設minHeight最小高

而且你還須要稍微調大點,特別針對的是動態添加的元素,高度不固定的父級dom節點,這種狀況會出現下載的圖被絕對居中且被剪裁了的感受

2)CSS3部分高級語法不支持

舉例說明就是 -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-image: -webkit-linear-gradient 這種玩意


4、外部連接

html2Canvas Options 配置項

dom-to-imgage Options 配置項


關於

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

blog:blog.csdn.net/mcky_love

掘金:juejin.cn/user/199636…

lofter:zcxy-gs.lofter.com/

sf:segmentfault.com/u/mybestang…

git:github.com/gs3170981

相關文章
相關標籤/搜索