Search code examples
javascriptcanvashtml5-canvaspaperjs

Maintaining focus of PaperJS drag item (Sketch)


I have a function closestPointData to determine the closest point of a line, highlight that point by showing a red dot and to determine which point needs to be dragged.

I'm having a problem with the tolerance, when I drag too fast, the mouse "drops" the point.

How can I ensure I don't exceed the tolerance of 30px

See Sketch for working version

line = new Path.Line([90, 90], [250, 250]);
line.strokeColor = 'black';
line.strokeWidth = 3;

var redDot = new Path.Circle({
  center: view.center,
  radius: 3,
  fillColor: 'transparent',
  position: [90, 90]
});

function onMouseDrag(e) {
  var cpd = closestPointData(e);
  if (cpd.closestDistance < 30) {
    line.segments[cpd.closestPointIndex].point = e.point;
    redDot.position = cpd.usePoint;
  }
}


function onMouseMove(e) {
  var cpd = closestPointData(e);
  if (cpd.closestDistance < 30) {
    redDot.position = cpd.usePoint;
    redDot.fillColor = 'red';
  }else{
    redDot.fillColor = 'transparent';
  }
}

// Determine which of the line segment points is closest to the mouse pointer
function closestPointData(e) {
  var closestPointIndex = null;
  var closestDistance = Infinity;
  var usePoint; // segment point coordinates
  // console.log(line.segments);
  
  // get point from each end of segment
  for (var i = 0; i < line.segments.length; i++) {
    var pointAt = line.getPointAt(line.segments[i]);
    var distance = e.point.getDistance(pointAt);
    
    // Qualify shortest distance
    if (distance < closestDistance) {
      closestDistance = distance;
      closestPointIndex = i;
      usePoint = pointAt;
    }
  }
  return {
    closestPointIndex: closestPointIndex,
    closestDistance: closestDistance,
    usePoint: usePoint
  };
}

Solution

  • In my opinion, the onMouseDrag handler is usefull only when there is no onMouseMove, otherwise, it gets a bit confusing. I would do it this way.

    // Set target size.
    const MIN_DISTANCE = 30;
    
    // Create items.
    const path = new Path.Line({
        from: view.center,
        to: view.center + 150,
        strokeColor: 'black',
        strokeWidth: 2,
    });
    const dot = new Path.Circle({
        center: view.center,
        radius: 5,
        fillColor: 'transparent',
    });
    
    // Create state variables.
    // This will store the active path point.
    let activePoint;
    // This will allow us to check if we are currently dragging or not.
    let dragging = false;
    
    function onMouseDown() {
        dragging = true;
    }
    
    function onMouseMove(event) {
        // If we are not currently dragging...
        if (!dragging) {
            // ...look for the closest path point...
            const closest = path.segments
                .map((it) => ({point: it.point, distance: it.point.getDistance(event.point)}))
                .sort((a, b) => a.distance - b.distance)
                .shift();
            // ...and if it is close enough...
            if (closest.distance <= MIN_DISTANCE) {
                // ...show the dot...
                dot.fillColor = 'red';
                dot.position = closest.point;
                // ...and store the active point.
                activePoint = closest.point;
            }
            // Otherwise...
            else {
                // ...hide the dot...
                dot.fillColor = 'transparent';
                // ...and store no active point.
                activePoint = null;
            }
        }
        // If we are dragging and there is an active point...
        else if (activePoint) {
            // ...move the dot...
            dot.position += event.delta;
            // ...and move the path point.
            activePoint.x = dot.position.x;
            activePoint.y = dot.position.y;
        }
    }
    
    function onMouseUp() {
        dragging = false;
    }