渲染前保存 canvas 的数据图像

Save dataImage of canvas before rendering

我正在开发一个有两个 canvases 的项目。第一个 canvas 支持缩放和绘图,第二个 canvas 是第一个 canvas 的副本,它应该显示第一个 canvas 的一般视图而不缩放。我已经问过这个 , and this what I have now. In this solution, I copy the state of the canvas before scaling using function toDataURL. But this is a very slow way and I get poor performance. I tried using getImageData instead and got not what I expected. Also i tried to play with functions after:render and before:render, but without result, please, look this。我做错了什么?

1) 如果 devicePixelRatio > 1,Fabric.js 将其 canvas 放大。这就是为什么当您绘制从 getImageData()。更好的方法是使用 drawImage() 并为其提供目标 width/height,以便您的浏览器决定是否应根据源图像尺寸缩小或放大这些像素。

2) 重置视口变换是正确的,但这还不够——您应该应用这个新变换并以某种方式重新渲染 canvas。这就是当您调用 toDataURL() 时 Fabric.js 所做的事情 - 它调用 toCanvasElement(),它制作 canvas 的副本并在其上呈现所有对象。您的实施应反映此逻辑。

下面的解决方案介绍了drawOnCopyCanvas()方法,是toCanvasElement()的补丁版本。它不使用中间 canvas 而是直接在提供的 canvas 上绘制。

fabric.StaticCanvas.prototype.drawCopyOnCanvas = function(canvasEl) {
  // save values
  var scaledWidth = this.width,
      scaledHeight = this.height,
      vp = this.viewportTransform,
      originalInteractive = this.interactive,
      newVp = [1, 0, 0, 1, 0, 0],
      originalRetina = this.enableRetinaScaling,
      originalContextTop = this.contextTop;
  // reset
  this.contextTop = null;
  this.enableRetinaScaling = false;
  this.interactive = false;
  this.viewportTransform = newVp;
  this.calcViewportBoundaries();
  // draw on copy
  this.renderCanvas(canvasEl.getContext('2d'), this._objects);
  // restore values
  this.viewportTransform = vp;
  this.calcViewportBoundaries();
  this.interactive = originalInteractive;
  this.enableRetinaScaling = originalRetina;
  this.contextTop = originalContextTop;
}

function afterRender() {
  // remove 'after:render' listener as canvas.toCanvasElement()
  // calls renderCanvas(), which results in an infinite recursion
  canvas.off('after:render', afterRender);
  // draw c1 contents on c2
  canvas.drawCopyOnCanvas(c2);
  setTimeout(() => {
    // re-attach the listener in the next event loop
    canvas.on('after:render', afterRender);
  });
}

canvas.on('after:render', afterRender);