html2canvas跨域問題

近期公司項(xiàng)目有個(gè)分享的功能,需要由前端生成包含有關(guān)用戶信息的圖片,點(diǎn)擊可以保存。于是選用了html2canvas

github鏈接

https://github.com/niklasvh/html2canvas

官網(wǎng)鏈接

http://html2canvas.hertzen.com/

官網(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

我把解決代碼貼一下

源碼需要修改的地方有兩處:

  1. 代碼第 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});
            }
  1. 代碼第 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è)方面:

  1. 由于圖片數(shù)據(jù)都是動(dòng)態(tài)加載的,css樣式不起作用,可以在生成圖片的時(shí)候要加上樣式
img.style.cssText = 'width: 100%;'
  1. 需要生成圖片的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;
  1. 不要使用背景圖片

源碼里也有說明

image.png
  1. 版本選擇

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

image.png

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

image.png

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

image.png

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

image.png

如果是移動(dòng)端,同時(shí)有多個(gè)數(shù)據(jù)以列表的方式展示,那每次點(diǎn)擊生成圖片都會(huì)把頁面中的圖片重新加載一次,這樣多次請(qǐng)求會(huì)讓頁面加載特別慢。

解決:
1、跳到新的頁面,避免需要生成圖片的dom裝載太多數(shù)據(jù)。
2、使用低版本的html2canvas

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,872評(píng)論 25 709
  • 1. 什么是跨域? 跨域一詞從字面意思看,就是跨域名嘛,但實(shí)際上跨域的范圍絕對(duì)不止那么狹隘。具體概念如下:只要協(xié)議...
    他在發(fā)呆閱讀 858評(píng)論 0 0
  • 最近看到一則新聞,一家創(chuàng)業(yè)公司提供死亡體驗(yàn)服務(wù),讓體驗(yàn)者躺在野外挖好的墳?zāi)估铮嚯x的體驗(yàn)死亡的感覺。網(wǎng)友在跟帖中...
    門前池塘閱讀 484評(píng)論 0 0
  • 朋友一起逛街,她帶著四歲的兒子。小孩子精力旺盛,總是喜歡跑跑跳跳,過馬路的時(shí)候也不讓人省心。朋友一遍又一遍扯著嗓子...
    Doctor方閱讀 523評(píng)論 1 5
  • 我們都要變成大人了。 我叫__。 大家都說,青春是一本譜不完的書,總在少年不知不覺中,悄悄消逝、離去。如何面對(duì)一個(gè)...
    11xi閱讀 674評(píng)論 8 5

友情鏈接更多精彩內(nèi)容