Search code examples
fabricjskonvajs

How to not select object when clicking negative-space?


Using FabricJS, is there a way to not select an object when clicking its negative space? Assume the object is a dynamically drawn not-rectangle shape, like a star.

Diagram showing problem

Alternative; works: For reference, apparently the Konva drawing library has this ability by default. Here's a Konva setup in codepen, with star shapes, showing negative-space not-clickable: https://codepen.io/MarsAndBack/pen/zYmRReE?editors=1111

FabricJS star test:

Codepen: https://codepen.io/MarsAndBack/pen/MWPQQVe?editors=1111

const canvasEl = document.getElementById("canvas")
var canvas, object_star

init()
drawObjects()   


function init() {
  canvas = new fabric.Canvas(canvasEl)
  let width = document.getElementById("canvasWrapper").clientWidth
  let height = document.getElementById("canvasWrapper").clientHeight
  canvas.setWidth(width)
  canvas.setHeight(height)
}

function drawObjects() {  
  let points = starPolygonPoints(5,50,25)
  object_star = new fabric.Polygon(points, {
    stroke: 'black',
    fill: 'yellow',
    left: 50,
    top: 50,
    strokeWidth: 4,
    strokeLineJoin: 'bevil'
  }, false)
  canvas.add(object_star)
}

function starPolygonPoints(spikeCount, outerRadius, innerRadius) {
  var rot = Math.PI / 2 * 3
  var cx = outerRadius
  var cy = outerRadius
  var sweep = Math.PI / spikeCount
  var points = []
  var angle = 0
  for (var i = 0; i < spikeCount; i++) {
    var x = cx + Math.cos(angle) * outerRadius
    var y = cy + Math.sin(angle) * outerRadius
    points.push({x: x, y: y})
    angle += sweep
    x = cx + Math.cos(angle) * innerRadius
    y = cy + Math.sin(angle) * innerRadius
    points.push({x: x, y: y})
    angle += sweep
  }
  return (points)
}
#canvasWrapper {
  width: 100%;
  height: 100%;
}

#canvas {
  background-color: pink;
}
<script src="https://cdn.jsdelivr.net/npm/fabric"></script>

<div id="canvasWrapper">
  <canvas id="canvas" width="400" height="400"></canvas>
</div>


Solution

  • Use perPixelTargetFind & targetFindTolerance

    Took way longer than expected to find this (with help from others too)!

    FabricJS has two properties relevant to this:

    perPixelTargetFind: Boolean - "When true, object detection happens on per-pixel basis rather than on per-bounding-box" http://fabricjs.com/docs/fabric.Canvas.html#perPixelTargetFind

    targetFindTolerance: Number - "Number of pixels around target pixel to tolerate (consider active) during object detection" http://fabricjs.com/docs/fabric.Canvas.html#targetFindTolerance

    Apply them to the entire scene:

    canvas = new fabric.Canvas(canvasElement, {
        // ...
        perPixelTargetFind:true,
        targetFindTolerance: 15
        // ...
    })
    

    or per-object:

    line = new fabric.Line(points, {
        // ...
        perPixelTargetFind: true,
        targetFindTolerance: 10
        // ...
    })