vinkovsky
vinkovsky

Reputation: 310

How to display another canvas without zoom in fabricjs

In short, I am developing a project in which there are two canvases: a canvas that supports zoom and drawing, a second canvas - a duplicate of the first canvas, which should display a general view without zoom. Unfortunately, I do not understand how to implement this, since the second canvas also displays a picture with a zoom. Please, help! Here is a small example of what I have:

var c1 = document.getElementById("scale");
var c2 = document.getElementById("static");

var canvas = this.__canvas = new fabric.Canvas(c1, {
  isDrawingMode: true
});
var ctx1 = c1.getContext("2d");
var ctx2 = c2.getContext("2d");

function copy() {
    var imgData = ctx1.getImageData(0, 0, 512, 512);
    ctx2.putImageData(imgData, 0, 0);
}

fabric.Image.fromURL(
  "https://cdn-images-1.medium.com/max/1800/1*sg-uLNm73whmdOgKlrQdZA.jpeg",
  img => {
    img.scaleToWidth(canvas.width);
    canvas.setBackgroundImage(img);
    canvas.requestRenderAll();
  },
  {
    crossOrigin: "Annoymous"
  }
);

canvas.on('mouse:wheel', function(opt) {
  var delta = opt.e.deltaY;
  var pointer = canvas.getPointer(opt.e);
  var zoom = canvas.getZoom();
  zoom = zoom + delta/200;
  if (zoom > 20) zoom = 20;
  if (zoom < 0.01) zoom = 0.01;
  canvas.zoomToPoint({ x: opt.e.offsetX, y: opt.e.offsetY }, zoom);
  opt.e.preventDefault();
  opt.e.stopPropagation();
});
    

setInterval(() => {
    copy();
}, 10)
canvas {
  border: 1px solid black;
}
#static {
  position: relative;
  top: -300px;
  left: 500px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.1/fabric.min.js"> </script>

This is interactive canvas
<canvas id="scale" width="300" height="300"></canvas>

<canvas id="static"width="300" height="300"></canvas>

Upvotes: 0

Views: 979

Answers (2)

Helder Sepulveda
Helder Sepulveda

Reputation: 17574

Your issue is that you are getting the "ImageData" from the context
but you should be using the fabric.Canvas object...

The context only gives you what you see, so those two canvases will be a mirror image, you have to get the data from the fabric.Canvas that is the one that has the "big picture" with all the edits.

In my prototype I'm using canvas.toJSON to get the data then to draw that data I use loadFromJSON, but I did notice some flickering the new image is loaded so I decided to use an image instead of the static canvas, that way the transition is seamless

see my simple prototype below:

var c1 = document.getElementById("scale");
var static = document.getElementById("img");
var canvas = new fabric.Canvas(c1, {isDrawingMode: true});

function copy() {
  var data = canvas.toObject();
  var c = new fabric.Canvas();
  c.loadFromJSON(data,  function() {
    static.src = c.toDataURL({ format: 'png' });
  });  
}

fabric.Image.fromURL(
  "https://cdn-images-1.medium.com/max/1800/1*sg-uLNm73whmdOgKlrQdZA.jpeg",
  img => {
    img.scaleToWidth(canvas.width);
    canvas.setBackgroundImage(img);
    canvas.requestRenderAll();
  }, { crossOrigin: "anonymous" }
);

canvas.on('mouse:wheel', function(opt) {
  var pointer = canvas.getPointer(opt.e);
  var zoom = canvas.getZoom() + opt.e.deltaY / 200;
  if (zoom > 20) zoom = 20;
  if (zoom < 0.01) zoom = 0.01;
  canvas.zoomToPoint({
    x: opt.e.offsetX,
    y: opt.e.offsetY
  }, zoom);
  opt.e.preventDefault();
  opt.e.stopPropagation();
});

setInterval(copy, 200)
canvas {
  border: 2px solid black;
}
img {
  border: 2px solid red;
  position: absolute;
  top: 5px; right: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.1/fabric.min.js">
</script>

<canvas id="scale" width="300" height="150"></canvas>
<br><img id="img"   width="300" height="150">

Unless we get some browser compatibility issue (I only tested chrome), the result should look like:

Upvotes: 2

AndreaBogazzi
AndreaBogazzi

Reputation: 14731

There is not a perfect solution because fabricJS does not support rendering on more targets.

There is a toCanvasElement method here that creates a copy to another canvas, but it does not let you specify the canvas you want to draw on.

So here what i m doing is, at some point:

  • reset the zoom to 1
  • render to a new canvas
  • put back the zoom to what it was
  • copy that canvas on the static canvas

There is an extra copy, that is fast on modern hardware, to the point that you do not care, but would be nicer to add a canvas argument to that toCanvasElement method in order to get the drawing happening on the specified canvas immediately

Then apart from that there is the quesiton on when firing that copy. 'after:render' is not a great idea, it will loop forever and it will also redraw on zoom changes, that is not what you want.

For now i use object:added, you can add also object:modified, and maybe something else but ideally you will call a copy manually when needed when you run some code that will change some of the drawing properties.

var c1 = document.getElementById("scale");
var c2 = document.getElementById("static");

var canvas = this.__canvas = new fabric.Canvas(c1, {
  isDrawingMode: true
});
var ctx2 = c2.getContext("2d");

function copy(copiedCanvas) {
   ctx2.drawImage(copiedCanvas,0,0);
}

fabric.Image.fromURL(
  "https://cdn-images-1.medium.com/max/1800/1*sg-uLNm73whmdOgKlrQdZA.jpeg",
  img => {
    img.scaleToWidth(canvas.width);
    canvas.setBackgroundImage(img);
    canvas.requestRenderAll();
  },
  {
    crossOrigin: "Annoymous"
  }
);

canvas.on('mouse:wheel', function(opt) {
  var delta = opt.e.deltaY;
  var pointer = canvas.getPointer(opt.e);
  var zoom = canvas.getZoom();
  zoom = zoom + delta/200;
  if (zoom > 20) zoom = 20;
  if (zoom < 0.01) zoom = 0.01;
  canvas.zoomToPoint({ x: opt.e.offsetX, y: opt.e.offsetY }, zoom);
  opt.e.preventDefault();
  opt.e.stopPropagation();
});

function afterRender() {
  var originalVP = canvas.viewportTransform;
  canvas.viewportTransform = [1, 0, 0, 1, 0, 0];
  copy(canvas.toCanvasElement());
  canvas.viewportTransform = originalVP;
}

canvas.on('object:added', afterRender);
canvas.on('object:modified', afterRender);
canvas {
  border: 1px solid black;
}
#static {
  position: relative;
  top: -300px;
  left: 500px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.1/fabric.min.js"> </script>

This is interactive canvas
<canvas id="scale" width="300" height="300"></canvas>

<canvas id="static"width="300" height="300"></canvas>

Upvotes: 2

Related Questions