3

I have been working with fabricjs for creating a drawing tool. There is a large image as the canvas background and panning feature. User can drag and drop objects on the canvas. But the Object drop on the canvas is not selected when we pan the canvas.

To check the issue drag and drop the circle on lower part of the view-port than pan the canvas upward to half and check the object put on the canvas is not selectable. Even thought if you try to move the object outside the view-port it will be not selectable.

var canvas = new fabric.Canvas('c', {
  selection: false
});
canvas.perPixelTargetFind = true;
canvas.targetFindTolerance = 4;

fabric.Image.fromURL('https://image.ibb.co/gFtpp7/8c39a3193e05996911c0d9c1df001a80.jpg', function(img) {
  var imgObj = img.set({
    left: 0,
    top: 0,
  });
  imgObj.scaleToWidth(canvas.width);
  canvas.width = imgObj.width;
  canvas.setBackgroundImage(imgObj, canvas.renderAll.bind(canvas));
});
var drawingPointer = {
  status: false,
  previousObj: false
};

function handleDragStart(e) {
  var draggables = document.querySelectorAll('#manholes div.drag-obj');
  [].forEach.call(draggables, function(img) {
    img.classList.remove('img_dragging');
  });
  this.classList.add('img_dragging');
}

function handleDragOver(e) {
  if (e.preventDefault) {
    e.preventDefault(); // Necessary. Allows us to drop.
  }
  e.dataTransfer.dropEffect = 'copy'; // See the section on the DataTransfer object.
  // NOTE: comment above refers to the article (see top) -natchiketa
  return false;
}

function handleDragEnter(e) {
  // this / e.target is the current hover target.
  this.classList.add('over');
}

function handleDragLeave(e) {
  this.classList.remove('over'); // this / e.target is previous target element.
}

function handleDrop(e) {
  // this / e.target is current target element.
  if (e.stopPropagation) {
    e.stopPropagation(); // stops the browser from redirecting.
  }

  var img = document.querySelector('#manholes div.drag-obj.img_dragging');
  if (img.getAttribute('draggable') != "true") {
    return;
  }
  img.setAttribute('draggable', false);
  var objName = img.getAttribute('data-name');
  var circle = new fabric.Circle({
    radius: 28,
    fill: '#00FFFF',
    originX: 'center',
    originY: 'center'
  });
  var pointer = canvas.getPointer(e)
  var text = new fabric.Text(objName, {
    fontSize: 13,
    originX: 'center',
    originY: 'center'
  });
  var group = new fabric.Group([circle, text], {
    left: pointer.x - 28,
    top: pointer.y - 28,
    hasRotatingPoint: false,
    dataName: objName,
  });
  group.setControlsVisibility({
    mt: false,
    ml: false,
    mr: false,
    mb: false
  });
  group.setCoords();
  canvas.add(group);
  return false;
}

function handleDragEnd(e) {
  var draggables = document.querySelectorAll('#manholes div.drag-obj');
  [].forEach.call(draggables, function(img) {
    img.classList.remove('img_dragging');
  });
}
if (Modernizr.draganddrop) {
  var draggables = $('#manholes div.drag-obj');
  [].forEach.call(draggables, function(img) {
    img.addEventListener('dragstart', handleDragStart, false);
    img.addEventListener('dragend', handleDragEnd, false);
  });
  // Bind the event listeners for the canvas
  var canvasContainer = document.querySelector('.canvas-container');
  canvasContainer.addEventListener('dragenter', handleDragEnter, false);
  canvasContainer.addEventListener('dragover', handleDragOver, false);
  canvasContainer.addEventListener('dragleave', handleDragLeave, false);
  canvasContainer.addEventListener('drop', handleDrop, false);
} else {
  // Replace with a fallback to a library solution.
  alert("This browser doesn't support the HTML5 Drag and Drop API.");
}

var drawingPointer = {
  status: false
};

function handleMouseMovement(o) {
  if (drawingPointer.status == 'panning') {
    //var delta = new fabric.Point(o.e.movementX, o.e.movementY);
    //canvas.relativePan(delta);
    var e = o.e,
      imgH = this.backgroundImage.height,
      imgW = this.backgroundImage.width;
    var zoom = canvas.getZoom();
    if (zoom < 0.4) {
      this.viewportTransform[4] = 200 - imgW * zoom / 2;
      this.viewportTransform[5] = 200 - imgH * zoom / 2;
    } else {
      this.viewportTransform[4] += e.clientX - this.lastPosX;
      this.viewportTransform[5] += e.clientY - this.lastPosY;
      if (this.viewportTransform[4] >= 0) {
        this.viewportTransform[4] = 0;
      } else if (this.viewportTransform[4] < canvas.getWidth() - imgW * zoom) {
        this.viewportTransform[4] = canvas.getWidth() - imgW * zoom;
      }
      if (this.viewportTransform[5] >= 0) {
        this.viewportTransform[5] = 0;
      } else if (this.viewportTransform[5] < canvas.getHeight() - imgH * zoom) {
        this.viewportTransform[5] = canvas.getHeight() - imgH * zoom;
      }
    }
    this.requestRenderAll();
    this.lastPosX = e.clientX;
    this.lastPosY = e.clientY;
  }
}

function handleMouseDown(o) {
  if (!o.target && !drawingPointer.status) {
    this.lastPosX = o.e.clientX;
    this.lastPosY = o.e.clientY;
    canvas.defaultCursor = 'url("https://image.ibb.co/g94jNS/icon_grab.png") 8 2 ,auto';
    drawingPointer.status = 'panning';
  }
}

function handleMouseUp(o) {
  if (!o.target && drawingPointer.status == 'panning') {
    drawingPointer.status = false;
    canvas.defaultCursor = 'default';
  }
}
canvas.on('mouse:move', handleMouseMovement);
canvas.on('mouse:down', handleMouseDown);
canvas.on('mouse:up', handleMouseUp);
.drag-obj {
    width: 56px;
    height: 56px;
    display: inline;
    margin: 5px;
    border: 1px solid #CCCCCC;
    float: left;
    padding: 16px 5px;
    text-align: center;
    border-radius: 50%;
    background-color: #CCCCCC;
  }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<canvas id="c" width="400" height="400" style="border: 1px solid;margin: 10px;"></canvas>
<div class="row">
  <div class="col-xs-10 col-xs-offset-1">
    <h3>Drag and Drop Objects</h3>
    <div class="drag-obj-container" id="manholes">
      <div draggable="true" class="drag-obj" data-name="OV01">
        OV01
      </div>
      <div draggable="true" class="drag-obj" data-name="OV02">
        OV02
      </div>
      <div draggable="true" class="drag-obj" data-name="OV03">
        OV03
      </div>
    </div>
  </div>
</div>
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/modernizr/2.6.2/modernizr.min.js"></script>
Kathak Dabhi
  • 399
  • 3
  • 16

1 Answers1

5

After panning (onMouseUp) , set all object coords using setCoords.

DEMO

var canvas = new fabric.Canvas('c', {
  selection: false
});
canvas.perPixelTargetFind = true;
canvas.targetFindTolerance = 4;

fabric.Image.fromURL('https://image.ibb.co/gFtpp7/8c39a3193e05996911c0d9c1df001a80.jpg', function(img) {
  var imgObj = img.set({
    left: 0,
    top: 0,
  });
  imgObj.scaleToWidth(canvas.width);
  canvas.width = imgObj.width;
  canvas.setBackgroundImage(imgObj, canvas.renderAll.bind(canvas));
});
var drawingPointer = {
  status: false,
  previousObj: false
};

function handleDragStart(e) {
  var draggables = document.querySelectorAll('#manholes div.drag-obj');
  [].forEach.call(draggables, function(img) {
    img.classList.remove('img_dragging');
  });
  this.classList.add('img_dragging');
}

function handleDragOver(e) {
  if (e.preventDefault) {
    e.preventDefault(); // Necessary. Allows us to drop.
  }
  e.dataTransfer.dropEffect = 'copy'; // See the section on the DataTransfer object.
  // NOTE: comment above refers to the article (see top) -natchiketa
  return false;
}

function handleDragEnter(e) {
  // this / e.target is the current hover target.
  this.classList.add('over');
}

function handleDragLeave(e) {
  this.classList.remove('over'); // this / e.target is previous target element.
}

function handleDrop(e) {
  // this / e.target is current target element.
  if (e.stopPropagation) {
    e.stopPropagation(); // stops the browser from redirecting.
  }

  var img = document.querySelector('#manholes div.drag-obj.img_dragging');
  if (img.getAttribute('draggable') != "true") {
    return;
  }
  img.setAttribute('draggable', false);
  var objName = img.getAttribute('data-name');
  var circle = new fabric.Circle({
    radius: 28,
    fill: '#00FFFF',
    originX: 'center',
    originY: 'center'
  });
  var pointer = canvas.getPointer(e)
  var text = new fabric.Text(objName, {
    fontSize: 13,
    originX: 'center',
    originY: 'center'
  });
  var group = new fabric.Group([circle, text], {
    left: pointer.x - 28,
    top: pointer.y - 28,
    hasRotatingPoint: false,
    dataName: objName,
  });
  group.setControlsVisibility({
    mt: false,
    ml: false,
    mr: false,
    mb: false
  });
  group.setCoords();
  canvas.add(group);
  return false;
}

function handleDragEnd(e) {
  var draggables = document.querySelectorAll('#manholes div.drag-obj');
  [].forEach.call(draggables, function(img) {
    img.classList.remove('img_dragging');
  });
}
if (Modernizr.draganddrop) {
  var draggables = $('#manholes div.drag-obj');
  [].forEach.call(draggables, function(img) {
    img.addEventListener('dragstart', handleDragStart, false);
    img.addEventListener('dragend', handleDragEnd, false);
  });
  // Bind the event listeners for the canvas
  var canvasContainer = document.querySelector('.canvas-container');
  canvasContainer.addEventListener('dragenter', handleDragEnter, false);
  canvasContainer.addEventListener('dragover', handleDragOver, false);
  canvasContainer.addEventListener('dragleave', handleDragLeave, false);
  canvasContainer.addEventListener('drop', handleDrop, false);
} else {
  // Replace with a fallback to a library solution.
  alert("This browser doesn't support the HTML5 Drag and Drop API.");
}

var drawingPointer = {
  status: false
};

function handleMouseMovement(o) {
  if (drawingPointer.status == 'panning') {
    //var delta = new fabric.Point(o.e.movementX, o.e.movementY);
    //canvas.relativePan(delta);
    var e = o.e,
      imgH = this.backgroundImage.height,
      imgW = this.backgroundImage.width;
    var zoom = canvas.getZoom();
    if (zoom < 0.4) {
      this.viewportTransform[4] = 200 - imgW * zoom / 2;
      this.viewportTransform[5] = 200 - imgH * zoom / 2;
    } else {
      this.viewportTransform[4] += e.clientX - this.lastPosX;
      this.viewportTransform[5] += e.clientY - this.lastPosY;
      if (this.viewportTransform[4] >= 0) {
        this.viewportTransform[4] = 0;
      } else if (this.viewportTransform[4] < canvas.getWidth() - imgW * zoom) {
        this.viewportTransform[4] = canvas.getWidth() - imgW * zoom;
      }
      if (this.viewportTransform[5] >= 0) {
        this.viewportTransform[5] = 0;
      } else if (this.viewportTransform[5] < canvas.getHeight() - imgH * zoom) {
        this.viewportTransform[5] = canvas.getHeight() - imgH * zoom;
      }
    }
    this.requestRenderAll();
    this.lastPosX = e.clientX;
    this.lastPosY = e.clientY;
  }
}

function handleMouseDown(o) {
  if (!o.target && !drawingPointer.status) {
    this.lastPosX = o.e.clientX;
    this.lastPosY = o.e.clientY;
    canvas.defaultCursor = 'url("https://image.ibb.co/g94jNS/icon_grab.png") 8 2 ,auto';
    drawingPointer.status = 'panning';
  }
}

function handleMouseUp(o) {
  if (!o.target && drawingPointer.status == 'panning') {
    drawingPointer.status = false;
    canvas.defaultCursor = 'default';
    canvas.forEachObject(function (object){
      object.setCoords();
    });
    canvas.requestRenderAll();
  }
}
canvas.on('mouse:move', handleMouseMovement);
canvas.on('mouse:down', handleMouseDown);
canvas.on('mouse:up', handleMouseUp);
.drag-obj {
    width: 56px;
    height: 56px;
    display: inline;
    margin: 5px;
    border: 1px solid #CCCCCC;
    float: left;
    padding: 16px 5px;
    text-align: center;
    border-radius: 50%;
    background-color: #CCCCCC;
  }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<canvas id="c" width="400" height="400" style="border: 1px solid;margin: 10px;"></canvas>
<div class="row">
  <div class="col-xs-10 col-xs-offset-1">
    <h3>Drag and Drop Objects</h3>
    <div class="drag-obj-container" id="manholes">
      <div draggable="true" class="drag-obj" data-name="OV01">
        OV01
      </div>
      <div draggable="true" class="drag-obj" data-name="OV02">
        OV02
      </div>
      <div draggable="true" class="drag-obj" data-name="OV03">
        OV03
      </div>
    </div>
  </div>
</div>
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/modernizr/2.6.2/modernizr.min.js"></script>
Durga
  • 15,263
  • 2
  • 28
  • 52
  • Suppose I have more than 1000 objects and doing like this on panning will poor the performance of the system. So how can I improve it more. Do you have any suggestion. – Kathak Dabhi Apr 16 '18 at 06:16
  • @KathakDabhi We need to set the coords, and thats why did that in mouse up event, it will fire only once not like mouse move. – Durga Apr 16 '18 at 06:44
  • The most relevant part is: `canvas.forEachObject(function (object){ object.setCoords(); }); canvas.requestRenderAll();` – thanks for sharing! @KathakDabhi You could show a loading indicator while reprocessing object locations and enable selection again after it's done. That's how graphics programs do it, too. – Thomas Ebert Jan 23 '19 at 10:36