Bardelman
Bardelman

Reputation: 2298

Maintaining object size AND position while zooming in fabric js

I was trying to maintain the object size while zooming, i tried to get inspired by this answer in which the guy who wrote it didn't solve the controls issue in such as case, as a consequence you can see them not sticking to the object while zooming as in this screenshot.

But i came with this solution to maintain the object position and controls by updating its left and top after calculating them based on the inverted viewportTransform by calculating a new fabric.Point using the fabric.util.transformPoint function

fabric.Object.prototype.transform = function(ctx) {
    const obj = this;
    const {
        ignoreZoom,
        group,
        canvas,
        left,
        top
    } = obj;
    const {
        contextTop,
        viewportTransform,
        getZoom,
        requestRenderAll,
    } = canvas;

    var needFullTransform = (group && !group._transformDone) || (group && canvas && ctx === contextTop);

    if (ignoreZoom) {
        const oldP = new fabric.Point(left, top);
        const newP = fabric.util.transformPoint(oldP, fabric.util.invertTransform(viewportTransform));
        var zoom = 1 / getZoom();
        /* // here i tried to refresh the whole canvas with requestRenderAll()
        this.set({
            left: newP.x,
            top: newP.y,
            scaleX: zoom,
            scaleY: zoom,
        });
        this.setCoords();
        requestRenderAll();
        */
        // but here i try refresh the object only which is better i think
        this.left = newP.x;
        this.top = newP.y;
        this.scaleX = zoom;
        this.scaleY = zoom;
        this.drawObject(ctx);
    }
    var m = this.calcTransformMatrix(!needFullTransform);
    ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
}

I have made this codesandbox as a demo for my code. As you can see in this screenshot, controls stick around the object but the whole of them doesn't maintain their position relatively to the background and sometimes they disappear completely. I need the object to keep its position relatively to the background. How to make it better ?

// EDIT

I tried to understand better what happens while zooming, i found the fabric.Canvas.zoomToPoint() which is used for zooming (as in their tutorial)

   zoomToPoint: function (point, value) {
      // TODO: just change the scale, preserve other transformations
      var before = point, vpt = this.viewportTransform.slice(0);
      point = transformPoint(point, invertTransform(this.viewportTransform));
      vpt[0] = value;
      vpt[3] = value;
      var after = transformPoint(point, vpt);
      vpt[4] += before.x - after.x;
      vpt[5] += before.y - after.y;
      return this.setViewportTransform(vpt);
    },

i guess the best way to fix the object position relatively to the background will be to apply the inverse transformation of the one applied to the canvas for the zoom to the object. So i wrote this function

function getNewVpt(point, value) {
  var before = point, 
  vpt = canvas.viewportTransform.slice(0);

  point = fabric.util.transformPoint(point, fabric.util.invertTransform(canvas.viewportTransform));

  vpt[0] = value;
  vpt[3] = value;
  var after = fabric.util.transformPoint(point, vpt);
  vpt[4] += before.x - after.x;
  vpt[5] += before.y - after.y;
  return vpt;
}

and i used it to rewrite the fabric.Object.prototype.transform

fabric.Object.prototype.transform = function (ctx) {
  const obj = this;
  const { ignoreZoom, group, canvas: objCanvas, left, top } = obj;
  const {
    contextTop,
    viewportTransform,
  } = objCanvas;
  var needFullTransform =
    (group && !group._transformDone) ||
    (group && objCanvas && ctx === contextTop);

  if (ignoreZoom && zoomingIsOn) {
    zoomingIsOn = false;
    var zoom = 1 / objCanvas.getZoom();
    const oldP = new fabric.Point(left, top);
    console.log('transform : oldP : ', oldP);
    const newVpt = getNewVpt(oldP, zoom)
    const newP = fabric.util.transformPoint(oldP, newVpt);
    console.log('transform : newP : ', newP);    

    // here i tried to refresh the whole canvas with requestRenderAll()
    this.set({
      left: newP.x,
      top: newP.y,
      scaleX: zoom,
      scaleY: zoom
    });
    this.setCoords();
    console.log('transform : CALLING objCanvas.requestRenderAll() ');
    objCanvas.requestRenderAll();

    // but here i try refresh the object only which is better i think
    // this.left = newP.x;
    // this.top = newP.y;
    // this.scaleX = zoom;
    // this.scaleY = zoom;
    // this.drawObject(ctx);
  }
  var m = this.calcTransformMatrix(!needFullTransform);
  ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
};

And here i forked this new codesandbox for this second solution , the result seems to be better than the former solution but it still not perfect. What i may still be doing wrong ?!

// EDIT 2

I tried to pass objCanvas.getZoom() instead of zoom as second parameter to the getNewVpt() function. It seems there is some more improovement but still not perfect again

// Edit 3 In This codesandbox probably i got the best result i could get using another function which returns directly the new point:

function getNewPt(point, value) {
  // TODO: just change the scale, preserve other transformations
  var vpt = canvas.viewportTransform.slice(0);

  point = fabric.util.transformPoint(point, fabric.util.invertTransform(canvas.viewportTransform));

  vpt[0] = value;
  vpt[3] = value;
  
  return fabric.util.transformPoint(point, vpt);;
}

I still wish anybody who can tell me if there is a way to improove it more. As you can see the triangle returns back to its initial position after zooming/ dezooming and getting back to the same initial zoom value which is good but between those initial and final states , it still seems not to be in the right spot..

Upvotes: 0

Views: 1360

Answers (1)

Sheheryaar Khan
Sheheryaar Khan

Reputation: 98

You just have to call zoomToPoint where it zooms and the objects will keep their position and scale relative to the background.

Try the following

canvas.on('mouse:wheel', function(opt) {

  // console.log(opt.e.deltaY)
  let zoomLevel = canvas.getZoom();
  // console.log('zoom Level: ', (zoomLevel * 100).toFixed(0), '%');
  zoomLevel += opt.e.deltaY * -0.01;

  // Restrict scale
  zoomLevel = Math.min(Math.max(.125, zoomLevel), 20);

  canvas.zoomToPoint(
      new fabric.Point(opt.e.offsetX, opt.e.offsetY),
      zoomLevel,
  );
  canvas.renderAll();

  })

Upvotes: 1

Related Questions