How can I pan the background image while it's zoomed in?

Viewed 337

I am using Fabric JS. I have a canvas of 800x800 pixels, and my canvas background image is 1920x1080 pixels.

  1. When zoomed in, I can pan only part of the photo, other parts are not visible.

    Video Example

    NOTE: I use Shift key for panning.

  2. What is the method to fit the background photo to the canvas like this:

    Background Example

  3. I want to avoid further zooming out from the incoming image. It's getting too small, and that's unnecessary.

    Background Example 2

Note: I'm using this example for zooming and panning with the mouse

Here's my current code in Codepen and Stack Snippets

var canvas = new fabric.Canvas('c');

var json ='{"version":"3.1.0","objects":[{"type":"group","version":"3.1.0","originX":"left","originY":"top","left":1656.26,"top":355.65,"width":76.48,"height":76,"fill":"rgb(0,0,0)","stroke":null,"strokeWidth":0,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeMiterLimit":4,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","transformMatrix":null,"skewX":0,"skewY":0,"id":"1298","objects":[{"type":"rect","version":"3.1.0","originX":"left","originY":"top","left":-20,"top":-13,"width":35,"height":50,"fill":"#0000ff","stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeMiterLimit":4,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","transformMatrix":null,"skewX":0,"skewY":0,"rx":0,"ry":0,"id":"1298"},{"type":"text","version":"3.1.0","originX":"center","originY":"top","left":0,"top":-38,"width":32.99,"height":20.34,"fill":"red","stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeMiterLimit":4,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"white","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","transformMatrix":null,"skewX":0,"skewY":0,"text":"Rect","fontSize":18,"fontWeight":"normal","fontFamily":"Quicksand","fontStyle":"normal","lineHeight":1.16,"underline":false,"overline":false,"linethrough":false,"textAlign":"left","textBackgroundColor":"","charSpacing":0,"styles":{}}]}],"backgroundImage":{"type":"image","version":"3.1.0","originX":"left","originY":"top","left":0,"top":0,"width":1920,"height":1080,"fill":"rgb(0,0,0)","stroke":null,"strokeWidth":0,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeMiterLimit":4,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","transformMatrix":null,"skewX":0,"skewY":0,"crossOrigin":"","cropX":0,"cropY":0,"src":"https://i.hizliresim.com/0pIPiv.jpg","filters":[]}}'
canvas.loadFromJSON(json, () => canvas.renderAll(), (o,object) => {
  object.on('selected', () => {
    console.log(object.id);
  });
  canvas.selection = false;
}
                   );
//canvas.setBackgroundImage('https://i.hizliresim.com/iBHC0t.jpg', canvas.renderAll.bind(canvas));
//https://i1.wp.com/onideal.com/wp-content/uploads/2020/03/Schlafzimmer-Grundriss-ideale-Position-Bett-Moebel-Kleiderschrank-dreieckchen-4-780x646.jpg
// dünya https://hizliresim.com/0pIPiv.jpg
//var uniqid = "0";
var uniqids = 0;
$("#door").on("click", function(e) {
  rect = new fabric.Rect({
        id:uniqid,
                left: 40,
                top: 40,
                width: 35,
                height: 50,      
                fill: 'blue',
                stroke: 'blue',
                strokeWidth: 5,
                strokeUniform: false,
                hasControls : true,
                 });  
  
                var uniqid = uniqids.toString(); 
  
               var text = new fabric.Text(uniqid, {
                  fontSize: 30,
                  originX: 'center',
                  originY: 'right'
                });
                
                var group = new fabric.Group([ rect, text ], {
                  left: 0,
                  top: 100,
                });
                                        
              canvas.add(group);
        uniqids++;
  
      canvas.on('selection:cleared', c => {
              console.log("empty");
          });
 canvas.selection = false;
  
});
//*****************************
canvas.on('mouse:wheel', function(opt) {
  var delta = opt.e.deltaY;
  var zoom = canvas.getZoom();
  zoom *= 0.999 ** delta;
  if (zoom > 20) zoom = 20;
  if (zoom < 0.01) zoom = 0.01;
  canvas.setZoom(zoom);
  opt.e.preventDefault();
  opt.e.stopPropagation();
  
})

$('#getid').click(function() {
  var activeObject = canvas.getActiveObjects();
  alert(canvas.getActiveObject().id); 
});



//***************************************

$("#save").on("click", function(e) {
    $(".save").html(canvas.toSVG());
});

$('#delete').click(function() {
  var activeObject = canvas.getActiveObjects(); 
    canvas.discardActiveObject();
    canvas.remove(...activeObject); 
});


$("#btnResetZoom").on("click", function(e) {
    canvas.setViewportTransform([1,0,0,1,0,0]);
});

canvas.on('mouse:wheel', function(opt) {
  var delta = opt.e.deltaY;
  var zoom = canvas.getZoom();
  zoom *= 0.999 ** delta;
  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();
});
var shiftKeyDown = true;
var mouseDownPoint = null;
canvas.on('mouse:move', function(options) {
  if (shiftKeyDown && mouseDownPoint) {
    var pointer = canvas.getPointer(options.e, true);
    var mouseMovePoint = new fabric.Point(pointer.x, pointer.y);
    canvas.relativePan(mouseMovePoint.subtract(mouseDownPoint));
    mouseDownPoint = mouseMovePoint;
    keepPositionInBounds(canvas);
  }
});



 
 var Direction = {
  LEFT: 0,
  UP: 1,
  RIGHT: 2,
  DOWN: 3
};

var zoomLevel = 0;
var zoomLevelMin = 0;
var zoomLevelMax = 3;

var shiftKeyDown = false;
var mouseDownPoint = null;

canvas.on('mouse:down', function(options) {
  var pointer = canvas.getPointer(options.e, true);
  mouseDownPoint = new fabric.Point(pointer.x, pointer.y);
});
canvas.on('mouse:up', function(options) {
  mouseDownPoint = null;
});
canvas.on('mouse:move', function(options) {
  if (shiftKeyDown && mouseDownPoint) {
    var pointer = canvas.getPointer(options.e, true);
    var mouseMovePoint = new fabric.Point(pointer.x, pointer.y);
    canvas.relativePan(mouseMovePoint.subtract(mouseDownPoint));
    mouseDownPoint = mouseMovePoint;
    keepPositionInBounds(canvas);
  }
});
fabric.util.addListener(document.body, 'keydown', function(options) {
  if (options.repeat) {
    return;
  }
  var key = options.which || options.keyCode; // key detection
  if (key == 16) { // handle Shift key
    canvas.defaultCursor = 'move';
    canvas.selection = false;
    shiftKeyDown = true;
  } else if (key === 37) { // handle Left key
    move(Direction.LEFT);
  } else if (key === 38) { // handle Up key
    move(Direction.UP);
  } else if (key === 39) { // handle Right key
    move(Direction.RIGHT);
  } else if (key === 40) { // handle Down key
    move(Direction.DOWN);
  }
});
fabric.util.addListener(document.body, 'keyup', function(options) {
  var key = options.which || options.keyCode; // key detection
  if (key == 16) { // handle Shift key
    canvas.defaultCursor = 'default';
    canvas.selection = true;
    shiftKeyDown = false;
  }
});
jQuery('.canvas-container').on('mousewheel', function(options) {
  var delta = options.originalEvent.wheelDelta;
  if (delta != 0) {
    var pointer = canvas.getPointer(options.e, true);
    var point = new fabric.Point(pointer.x, pointer.y);
    if (delta > 0) {
      zoomIn(point);
    } else if (delta < 0) {
      zoomOut(point);
    }
  }
});

function move(direction) {
  switch (direction) {
    case Direction.LEFT:
      canvas.relativePan(new fabric.Point(-10 * canvas.getZoom(), 0));
      break;
    case Direction.UP:
      canvas.relativePan(new fabric.Point(0, -10 * canvas.getZoom()));
      break;
    case Direction.RIGHT:
      canvas.relativePan(new fabric.Point(10 * canvas.getZoom(), 0));
      break;
    case Direction.DOWN:
      canvas.relativePan(new fabric.Point(0, 10 * canvas.getZoom()));
      break;
  }
  keepPositionInBounds(canvas);
}


function zoomIn(point) {
  if (zoomLevel < zoomLevelMax) {
    zoomLevel++;
    canvas.zoomToPoint(point, Math.pow(2, zoomLevel));
    keepPositionInBounds(canvas);
  }
}

function zoomOut(point) {
  if (zoomLevel > zoomLevelMin) {
    zoomLevel--;
    canvas.zoomToPoint(point, Math.pow(2, zoomLevel));
    keepPositionInBounds(canvas);
  }
}

function keepPositionInBounds() {
  var zoom = canvas.getZoom();
  var xMin = (2 - zoom) * canvas.getWidth() / 2;
  var xMax = zoom * canvas.getWidth() / 2;
  var yMin = (2 - zoom) * canvas.getHeight() / 2;
  var yMax = zoom * canvas.getHeight() / 2;

  var point = new fabric.Point(canvas.getWidth() / 2, canvas.getHeight() / 2);
  var center = fabric.util.transformPoint(point, canvas.viewportTransform);

  var clampedCenterX = clamp(center.x, xMin, xMax);
  var clampedCenterY = clamp(center.y, yMin, yMax);

  var diffX = clampedCenterX - center.x;
  var diffY = clampedCenterY - center.y;

  if (diffX != 0 || diffY != 0) {
    canvas.relativePan(new fabric.Point(diffX, diffY));
  }
}

function clamp(value, min, max) {
  return Math.max(min, Math.min(value, max));
}
#c {
  background-color: grey;
  margin-top: 10px;
}

button {
  padding: 10px 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.1.0/fabric.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<button id="door">Door</button>
<button id="delete">Delete Door</button>
<button id="save">Save</button>
<button id="getid">GET ID</button>
<button id="btnResetZoom">Reset Zoom</button>

<canvas id="c" width="800" height="800"></canvas>
<br>  
<p class="save">
</p>

1 Answers

Did you try loading image like this, instead from JSON? This will actually load image correctly, and it will know its size. While when you added it it only was able to pan the image that was visible when it was initially loaded, it kinda got cut.

new fabric.Image.fromURL('https://i.hizliresim.com/0pIPiv.jpg', (img) => {
  img.set({
    left: 0,
    top: 0
  });
  canvas.add(img);
});

Furthermore, if you want to scale image to fit to view, you can use it by using following methods

img.scaleToHeight(800);
img.scaleToWidth(800);
Related