FabricJS - 运行 toDataURL 时出现"Tainted canvases may not be exported" JS 错误

7
我有一个名为ImageContainer的自定义组件,它基本上创建一个矩形,并将图像应用为填充图案。
当我在画布上运行函数toDataURL时,我会收到一个js错误。
Uncaught DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.

我想要实现的结果是创建一个 png 数据,并将其作为 src 应用于图像标签。
你有任何想法如何解决这个问题吗?
谢谢。 https://jsfiddle.net/redlive/a444p13x/
4个回答

12

您需要向图像元素添加crossOrigin: 'anonymous'。将其添加到fabric.Image.fromURL中,或者使用来自同一服务器的图像,或定义了crossOrigin的图像。

演示

fabric.ImageContainer = fabric.util.createClass(fabric.Rect, {
  type: 'image-container',
  initialize: function(options) {
   options || (options = { });
    options.content || (options.content = { });
    options.content.angle = options.content.angle || 0;
    
    this.callSuper('initialize', options);
    this.set({
      objectCaching: false,
      ddpPreviousCenter: this.getCenterPoint()
    });
    
    this.on('scaling', function(el){
      this.set({
       width: this.width * this.scaleX,
        height: this.height * this.scaleY,
        scaleX: 1,
        scaleY: 1
      });
      this.setCoords();
      this._drawImage();
    }.bind(this));
    
    this.on('modified', function(el){
    this._updateContentCoords();
    }.bind(this));
  
    this._drawImage();        
  },
  _updateContentCoords: function(){
    const ddpPreviousCenter = {...this.ddpPreviousCenter};
      const content = {...this.content};
      const shiftX = this.getCenterPoint().x - ddpPreviousCenter.x;
      const shiftY = this.getCenterPoint().y - ddpPreviousCenter.y;

      content.left += shiftX;
      content.top += shiftY;
    
      this.set({
        ddpPreviousCenter: this.getCenterPoint(),
        content
      });
  },
  _drawImage: function() {
    const scaleFactor = 1;
    const imgSrc = [
      'https://picsum.photos/',
      this.content.width * scaleFactor,
      '/',
      this.content.height * scaleFactor
    ].join('');

    fabric.Image.fromURL(imgSrc, function(img) {
      img.set({
        left: this.content.left - this.left + this.content.width / 2,
        top:  this.content.top - this.top + this.content.height / 2,
        scaleX: 1,
        scalY: 1,
        angle: this.content.angle,
        originX: 'center',
        originY: 'center',
      });
      //            img.scaleToWidth(this.content.width);
      const patternSourceCanvas = new fabric.StaticCanvas();
      patternSourceCanvas.setDimensions({
        width: this.width,
        height: this.height
      });

      patternSourceCanvas.setBackgroundColor(this.backgroundColor);
      patternSourceCanvas.add(img);
      patternSourceCanvas.renderAll();

      const pattern = new fabric.Pattern({
       source: function() {
          return patternSourceCanvas.getElement();
        },
        repeat: 'no-repeat'
      });
      
      this.set({
        fill: pattern
      });

      this.canvas.renderAll();
      this.canvas.fire('image:pattern:loaded');
      
    }.bind(this),{
      crossOrigin: 'anonymous'
    });
  },
  toObject: function(options) {
        return fabric.util.object.extend(this.callSuper('toObject'), {
            ddpPreviousCenter: this.get('ddpPreviousCenter'),
            content: this.get('content'),
        });

        // return fabric.util.object.extend(this.callSuper('toObject'), {});
    },
  fromObject: function(object, callback) {
   return fabric.Object._fromObject('ImageContainer', object, callback);
 },
  _render: function(ctx) {
    this.callSuper('_render', ctx);
  }
});

fabric.ImageContainer.__fromObject = function(object, callback, forceAsync) {
    if (!forceAsync) {
      fabric.util.enlivenPatterns([object.fill, object.stroke], function(patterns) {
      console.log(patterns);
        object.fill = patterns[0];
        object.stroke = patterns[1];
        var rect = new fabric.ImageContainer(object);
        callback && callback(rect);
      });
    }
    else {
      var rect = new fabric.ImageContainer(object);
      callback && callback(rect);
      return rect;
    }
  };

// =========================================================================

let store;
const canvas = new fabric.Canvas('paper');

const container = new fabric.ImageContainer({
  left: 10,
  top: 10,
  width: 150,
  height: 150,
  backgroundColor: 'green',
  content: {
    left: 20,
    top: 20,
    width: 130,
    height: 130
  }
});

canvas.on('image:pattern:loaded', function(){
  $('#img').attr('src', this.toDataURL());
});

canvas.add(container);
canvas.renderAll();
#paper {
  border: solid 1px red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.2.3/fabric.js"></script>
<img id="image" />
<canvas id="paper" width="400" height="200" style="border:1px solid #ccc"></canvas>
<img id="img" />


4
你可以轻松将这个内容缩减一半的行数,它仍然会实现相同的功能。 - Darren Zou
2
如果我这样做,就会出现CORS错误。 - Ionel Lupu

2

在添加了 crossOrigin: 'anonymous' 后,我需要清除缓存。


你能解释一下什么时候清除缓存吗?是在调用toDataUrl()函数之前吗? - Darren Zou
2
仍然不起作用。如果我这样做,会出现CORS错误。 - Ionel Lupu

0
这是我解决此问题的方法。该问题是因为您不能在画布内使用外部图像URL。您必须获取图像并创建其本地副本。在我的示例中,我将画布的背景设置为图像,但您也可以将其用于向画布添加图像。
  fetch("https://i.imgur.com/fHyEMsl.jpg")
    .then(result => result.blob())
    .then(blob => {
        const url = URL.createObjectURL(blob);
        fabric.Image.fromURL(url, function (Image) {
            canvas.setBackgroundImage(Image);
            canvas.renderAll();
        })
    });

-4
fabric.Image.fromURL(imgSrc, (img) => {
  img._element.crossOrigin = 'anonymous'
  canvas.add(img).requestRenderAll()
})

3
请阅读[答案]并[编辑]您的答案以包含一个解释,为什么这段代码实际上会解决手头的问题。请记住,你不仅要解决问题,还要教育楼主和未来可能阅读这篇文章的读者。此外,这似乎对此答案没有任何添加,后者已经超过4年了,并解释了为什么它可以工作。 - Adriaan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接