說在前頭
這篇文章估計面向的人不多,所以我也不大篇幅的介紹這是幹啥的了
…
起先,我是單純想用domtoimage來解決我節點的截圖的,但嘗試了無數種方案,終是讓我敗下陣來
原因有仨
- 不使用代理且必須不能本地引入(當然前提是你圖片服務器不在內網,也就是外網也能請求到)
- 兼容所有移動端(難點在safari)
- 必須得到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 是不行的
三、外部鏈接
關於
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