2020-06-03 新添注意事項javascript
若不須要極致的效果的話,請全篇使用html2canvas便可,不須要domtoimage了!html
這篇文章估計面向的人很少,因此我也不大篇幅的介紹這是幹啥的了 ... 起先,我是單純想用domtoimage來解決我節點的截圖的,但嘗試了無數種方案,終是讓我敗下陣來java
緣由有仨android
敲鼓了一天半,最終得出的方案是dom-to-image與html2canvas來配合 固然你覺得單純使用就ok了嗎?No!不看下去,你依然會死在canvas.toDataURL裏ios
注意我使用的版本是否與你相匹git
{ "dom-to-image": "^2.6.0", "html2canvas": "^1.0.0-rc.5" } 複製代碼
import domtoimage from 'dom-to-image' import html2canvas from 'html2canvas' 複製代碼
主函數負責調用,而且咱們須要他來判斷兩種機型的走向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
爲何要分流?canvas
這就是爲何使用這兩個庫的緣由了,除了以上仨問題,還有就是domtoimage在safari裏支持很是不友好segmentfault
message: "The operation is insecure." (操做不安全)
知道這問題的人或許會與我感同身受吧
注意:變量名沒寫錯,這裏是IosHandle而不是IosRender,由於還要作一層處理
安卓的參數較簡單,代碼量也不多,但這並不表明他沒有問題
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的差距
這裏咱們須要定義兩個函數來用
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捕獲不到的異常
到這裏還沒完,再定義兩個函數,咱們要防白邊,固定截圖位置
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 是不行的
以上避免了絕大多數狀況,但仍有些問題需注意
而且你還須要稍微調大點,特別針對的是動態添加的元素,高度不固定的父級dom節點,這種狀況會出現下載的圖被絕對居中且被剪裁了的感受
舉例說明就是 -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-image: -webkit-linear-gradient 這種玩意
make:o︻そ╆OVE▅▅▅▆▇◤(清一色天空)
lofter:zcxy-gs.lofter.com/