Search code examples
javascriptdrag-and-drophtml5-canvaseaseljsanimate-cc

Drag doesn't detect drop when the canvas is made responsive


In Animate CC, and using the CreateJS/EaselJS API, I created a considerable amount of different HTML5 canvas, all of them featuring drag and drop elements, each time with progressive features.

To build the drag and drop interaction, I added to a Drag MovieClip some EventListeners, on mousedown, pressmove and pressup events. All of them have associated functions.

(Part of the) Actions Code for the Drag MovieClip

/* this refers to the Drag MovieClip */ 
root = this;

/* Mousedown Events */

/* removingFromDrop: Detects when a Drag is removed from certain Drop */
root.on("mousedown", removingFromDrop);

/* Pressmove Events */ 

/* onPressMove: Moves the Drag on pressmove */ 
root.on("pressmove", onPressMove);

/* detectDrop: While pressmoving, detects if the Drag is over a Drop */ 
root.on("pressmove", detectDrop); 

/* Pressup events */

/* setDragOverDrop: Releasing the left button of the mouse, 
   if a Drag is over a Drop, the Drag is positioned over the Drop */ 
root.on("pressup", setDragOverDrop); 

As I said, all the drag and drop features were implemented in a progressive way, so in some canvas the drags doesn't detect the drops (intentionally), in others a transparency (alpha) is applied to the drags when they are over a drop, in others the drag must be put over a drop or return to their original position, in others the drag when is put over a drop, it can't be dropped to other positions; etc.

What doesn't change in almost all of this implementations is the way the drag moves on the pressmove event...

onPressMove Function

function onPressMove(event) {
    // There is more code here, but for effects 
    // of this question, it's irrelevant

    /* In layer terms, drag must be over the drop  */ 
    root.parent.setChildIndex(event.currentTarget, root.parent.numChildren-1);

    /* Drag takes the actual position of the mouse pointer  */ 
    var point = stage.globalToLocal(event.stageX, event.stageY); 
    event.currentTarget.x = point.x;
    event.currentTarget.y = point.y; 
}

...and how it detects a drop when it is below, also on the pressmove event.

detectDrop Function

function detectDrop(event){
    /* The original function is more complex than this. For example, the drop detection 
       in the original function is being done against all the available drops. 
       For effects of this question, I'm simplyfying this function, doing the detection 
       over a single drop */

    /* Initial variables */
    var dropLocation;       //  Drop Location
    var point;              //  Point for the collision comparison
    var dropFound = false;  //  Drop is found? By default, no. 

    /* Drop is cloned. For effects of this question, the drop is a global variable. */
    dropLocation = drop.clone();

    /* Position of the drop is converted to local using the mouse pointer position */   
    point = dropLocation.globalToLocal(event.stageX, event.stageY);

    /* In local coordinates, the drag intersects with the point defined before? */  
    if (event.currentTarget.hitTest(point.x,point.y)) {   
        /* If yes, the drop was found */ 
        dropFound = true;                      
    }

    if (dropFound) {  
        // Irrelevant for effects of this question 
    }
}

There is only one case scenario when the drag and drop interaction doesn't work as expected, and that case is when the canvas is made responsive.

There is no problem when the canvas is positioned in the center, both the drag movement and the drop detection works fine.

But when the canvas is made responsive, the movement of the drag works fine, but a drop is not detected when a drag is over it. As I have tested until now, the drag detects a certain drop, in another position, which is obviously not the position of the previously mentioned drop.


Solution

  • After some tests I was doing to drags/drops similar to the core implementation shown above, I've came to a solution that involves additional transformations on the drop detecting function.

    First, the point variable now holds the current mouse position within the coordinate space of the stage:

    var point = stage.globalToLocal(event.stageX, event.stageY);
    

    Then, I use that point to transform to the coordinate space of the drop. That transformation is being held in another point variable:

    var pointGTL = dropLocation.globalToLocal(point.x, point.y); 
    

    The intersection test in local coordinates, is being done with the point variable defined before:

    event.currentTarget.hitTest(pointGTL.x,pointGTL.y) 
    

    Now the drop detecting function looks like this:

    detectDrop Function

    function detectDrop(event){
        /* Initial variables */
        var dropLocation;       //  Drop Location
        var point, pointGTL;    //  Points for the collision comparison
        var dropFound = false;  //  Drop is found? By default, no. 
    
        /* Drop is cloned */
        dropLocation = drop.clone();
    
        /* Position of the stage is converted to local 
           using the mouse pointer position */  
        point = stage.globalToLocal(event.stageX, event.stageY);
    
        /* Then, the position of the drop is converted to local 
           using the point defined before */ 
        pointGTL = dropLocation.globalToLocal(point.x, point.y); 
    
        /* In local coordinates, the drag intersects with the point defined before? */  
        if (event.currentTarget.hitTest(pointGTL.x,pointGTL.y)) {   
            /* If yes, the drop was found */ 
            dropFound = true;                      
        }
    
        /* Rest of the code */
    }
    

    The drags works fine in both responsive modes (Fit in view or Stretch to fit).