近期公司項(xiàng)目有個(gè)分享的功能,需要由前端生成包含有關(guān)用戶信息的圖片,點(diǎn)擊可以保存。于是選用了html2canvas
github鏈接
官網(wǎng)鏈接
官網(wǎng)的案例
// document.body 可以替換成想要生成圖片的dom元素
html2canvas(document.body, {
onrendered: function(canvas) {
document.body.appendChild(canvas);
}
});
圖片跨域
使用接口返回的圖片數(shù)據(jù),會(huì)出現(xiàn)以下報(bào)錯(cuò)(一般來說會(huì)出現(xiàn)下面的報(bào)錯(cuò),但是在vue項(xiàng)目中沒有報(bào)錯(cuò))
Uncaught SecurityError: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
解決:
方法一: 獲取數(shù)據(jù)后,將圖片先轉(zhuǎn)為base64,再重新賦值到src 上。(缺點(diǎn):需要每一張圖片都進(jìn)行一次轉(zhuǎn)換)
方法二: 配置參數(shù) useCORS: true
html2canvas(document.body, {
useCORS: true,// 允許跨域
onrendered: function(canvas) {
document.body.appendChild(canvas);
}
});
完美解決跨域和模糊的方法
網(wǎng)上的大神已經(jīng)很好的解決了這個(gè)問題
https://segmentfault.com/q/1010000004006610
https://segmentfault.com/a/1190000007707209
我把解決代碼貼一下
源碼需要修改的地方有兩處:
- 代碼第 999 行 renderWindow 的方法中 修改判斷條件 增加一個(gè)options.scale存在的條件
源碼:
if (options.type === "view") {
canvas = crop(renderer.canvas, {width: renderer.canvas.width, height: renderer.canvas.height, top: 0, left: 0, x: 0, y: 0});
} else if (node === clonedWindow.document.body || node === clonedWindow.document.documentElement || options.canvas != null) {
canvas = renderer.canvas;
} else {
canvas = crop(renderer.canvas, {width: options.width != null ? options.width : bounds.width, height: options.height != null ? options.height : bounds.height, top: bounds.top, left: bounds.left, x: 0, y: 0});
}
改為:
if (options.type === "view") {
canvas = crop(renderer.canvas, {width: renderer.canvas.width, height: renderer.canvas.height, top: 0, left: 0, x: 0, y: 0});
} else if (node === clonedWindow.document.body || node === clonedWindow.document.documentElement) {
canvas = renderer.canvas;
}else if(options.scale && options.canvas !=null){
log("放大canvas",options.canvas);
var scale = options.scale || 1;
canvas = crop(renderer.canvas, {width: bounds.width * scale, height:bounds.height * scale, top: bounds.top *scale, left: bounds.left *scale, x: 0, y: 0});
}
else {
canvas = crop(renderer.canvas, {width: options.width != null ? options.width : bounds.width, height: options.height != null ? options.height : bounds.height, top: bounds.top, left: bounds.left, x: 0, y: 0});
}
- 代碼第 943 行 html2canvas 的方法中 修改width,height
源碼:
return renderDocument(node.ownerDocument, options, node.ownerDocument.defaultView.innerWidth, node.ownerDocument.defaultView.innerHeight, index).then(function(canvas) {
if (typeof(options.onrendered) === "function") {
log("options.onrendered is deprecated, html2canvas returns a Promise containing the canvas");
options.onrendered(canvas);
}
return canvas;
});
改為:
width = options.width != null ? options.width : node.ownerDocument.defaultView.innerWidth;
height = options.height != null ? options.height : node.ownerDocument.defaultView.innerHeight;
return renderDocument(node.ownerDocument, options, width, height, index).then(function(canvas) {
if (typeof(options.onrendered) === "function") {
log("options.onrendered is deprecated, html2canvas returns a Promise containing the canvas");
options.onrendered(canvas);
}
return canvas;
});
使用方式:
html2Canvas() {
const shareContent = document.getElementById('share-content') // 需要繪制的部分的 (原生)dom 對(duì)象 ,注意容器的寬度不要使用百分比,使用固定寬度,避免縮放問題
const width = shareContent.offsetWidth // 獲取(原生)dom 寬度
const height = shareContent.offsetHeight // 獲取(原生)dom 高
const offsetTop = shareContent.offsetTop // 元素距離頂部的偏移量
const canvas = document.createElement('canvas') // 創(chuàng)建canvas 對(duì)象
const context = canvas.getContext('2d')
const scaleBy = getPixelRatio(context) // 獲取像素密度的方法 (也可以采用自定義縮放比例)
canvas.width = width * scaleBy // 這里 由于繪制的dom 為固定寬度,居中,所以沒有偏移
canvas.height = (height + offsetTop) * scaleBy // 注意高度問題,由于頂部有個(gè)距離所以要加上頂部的距離,解決圖像高度偏移問題
context.scale(scaleBy, scaleBy)
const opts = {
// allowTaint: true, // 允許加載跨域的圖片 (使用這個(gè)會(huì)報(bào)錯(cuò))
useCORS: true, // 允許加載跨域圖片
tainttest: true, // 檢測(cè)每張圖片都已經(jīng)加載完成
scale: scaleBy, // 添加的scale 參數(shù)
canvas: canvas, // 自定義 canvas
// logging: true, // 日志開關(guān),發(fā)布的時(shí)候記得改成false
width: width, // dom 原始寬度
height: height // dom 原始高度
}
html2canvas(shareContent, opts).then(function (canvas) {
const _src = canvas.toDataURL()
const img = document.createElement('img')
img.id = 'shareImgCanvas'
img.src = _src
img.style.cssText = 'width: 100%;'
console.log('html2canvas')
})
},
getPixelRatio(context) { // 獲取設(shè)備的pixel ratio
const backingStore = context.backingStorePixelRatio ||
context.webkitBackingStorePixelRatio ||
context.mozBackingStorePixelRatio ||
context.msBackingStorePixelRatio ||
context.oBackingStorePixelRatio ||
context.backingStorePixelRatio || 1
return (window.devicePixelRatio || 1) / backingStore
}
需要注意的幾個(gè)方面:
- 由于圖片數(shù)據(jù)都是動(dòng)態(tài)加載的,css樣式不起作用,可以在生成圖片的時(shí)候要加上樣式
img.style.cssText = 'width: 100%;'
- 需要生成圖片的dom背景不能透明,也不能設(shè)置為display:none,否則截圖出來就會(huì)是透明的圖片或者一片空白。如果不想將生成圖片的dom展示給用戶,可以將dom層級(jí)降低.
// 如果內(nèi)容過多,視圖可以滾動(dòng),則要設(shè)置為fixed,使得dom一直處于視圖內(nèi),不然無法正確的截取到dom
// left 、top最好設(shè)置為0,否則在生成圖片的時(shí)候會(huì)把偏移量計(jì)算出來,生成的圖片會(huì)有偏移量相對(duì)應(yīng)的空白高度
position: fixed;
left:0;
top:0;
z-index: -1;
- 不要使用背景圖片
源碼里也有說明

- 版本選擇
html2canvas 0.5.n的beta版本中已經(jīng)采用了iframe rendering的方法,作者也有說明

將ops中的logging: true打開會(huì)看到整個(gè)過程

在源碼中注釋1023行的removeChild,可以看的更加清晰

再次操作,會(huì)發(fā)現(xiàn)頁面中多了一個(gè)iframe,它是將這個(gè)dom樹都clone了一遍,再插入body中

如果是移動(dòng)端,同時(shí)有多個(gè)數(shù)據(jù)以列表的方式展示,那每次點(diǎn)擊生成圖片都會(huì)把頁面中的圖片重新加載一次,這樣多次請(qǐng)求會(huì)讓頁面加載特別慢。
解決:
1、跳到新的頁面,避免需要生成圖片的dom裝載太多數(shù)據(jù)。
2、使用低版本的html2canvas