Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Drag and drop on a canvas HTML5

I am trying to implement a drag and drop on a canvas representing 3 disks.

I would like to change with mouse the position of each mass. My main problem is that I am constrained by the length of axe for each of these 3 spheres.

For the moment, I have implemented the following function when mouse is moving inside the canvas (value of indexMass indicates which mass is moved: 1, 2 or 3 and t1, t2, t3 represents respectively the angle of mass 1, 2, 3):

// Happens when the mouse is moving inside the canvas
function myMove(event) {

   if (isDrag) {
     var x = event.offsetX;
     var y = event.offsetY;
    
     if (indexMass == 1)
       { // Update theta1 value
         t1 = t1 + 0.1*Math.atan(y/x);
       }
     else if (indexMass == 2)
       { // Update theta2 value
         t2 = t2 + 0.1*Math.atan(y/x);
       }
     else if (indexMass == 3)
       { // Update theta3 value
         t3 = t3 + 0.1*Math.atan(y/x);
       }

     // Update drawing
     DrawPend(canvas);

    }    

}

As you can see, I did for each angle:

t = t + 0.1*Math.atan(y/x);

with:

 var x = event.offsetX;
 var y = event.offsetY;

But this effect is not very nice. Once the sphere is selected with mouse (on mouse click), I would like the cursor to be stuck with this sphere or the sphere to follow the "delta" of the mouse coordinates when I am not on sphere any more.

Update 1

@Blindman67: thanks for your help, your code snippet is pretty complex for me, I didn't understand it all. But I am on the right way.

I am starting by the first issue: make rotate the selected disk with mouse staying very closed to it or over it, when dragging.

For the moment, I have modified my function myMove (which is called when I have clicked down and move the mouse for dragging) like:

// Happens when the mouse is moving inside the canvas
function myMove(event) {

   // If dragging 
   if (isDrag) {

     // Compute dx and dy before calling DrawPend
     var lastX = parseInt(event.offsetX - mx);
     var lastY = parseInt(event.offsetY - my);

     var dx = lastX - window['x'+indexMass];
     var dy = lastY - window['y'+indexMass];

     // Change angle when dragging
     window['t'+indexMass] = Math.atan2(dy, dx);

     // Update drawing
     DrawPend(canvas);
     
     // Highlight dragging disk
     fillDisk(indexMass, 'pink');
     
    }                    

}

where indexMass is the index of dragged disk and window['x'+indexMass] , window['y'+indexMass] are the current coordinates of the selected disk center.

After, I compute the dx, dy respectively from coordinates mouse clicked when starting drag (mx, my returned by getMousePos function) and mouse coordinates with moving.

Finally, I change the angle of disk by set, for global variable (theta of selected disk), i.e window['t'+indexMass]:

// Change angle when dragging
window['t'+indexMass] = Math.atan2(dy, dx);

I have took your part of code with Math.atan2.

But the result of this function doesn't make a good animation with mouse dragging, I would like to know where this could come from.

Right now, I would like to implement only the dragging without modifying the length of axis, I will see more later for this functionality.

Update 2

I keep going on to find a solution about the dragging of a selected mass with mouse.

For trying a synthesis of what I have done previously, I believe the following method is good but this dragging method is not working very well: the selected disk doesn't follow correctly the mouse and I don't know why.

In myMove function (function called when I start dragging), I decided to:

  1. Compute the dx, dy between the mouse coordinates and the selected disk coordinates, i.e:

    var dx = parseInt(event.offsetX - window['x'+indexMass]);
    
    var dy = parseInt(event.offsetY - window['y'+indexMass]);
    

indexMass represents the index of the selected disk.

  1. Increment the position of selected disk (stored in temporary variables tmpX, tmpY) by dx, dy.

  2. Compute the new angle theta (identified in code by global variable window['t'+indexMass]

  3. Compute the new positions of selected disk with this new value of theta, i.e for example with disk1 (indexMass=1 and theta = t1):

    x1= x0 +l1 * sin(t1)
    y1= y0 +l1 * sin(t1)
    

I want to draw readers' attention to the fact that I want dragging with mouse not to modify the lengths of axes with mouse, this is a constraint.

Here's the entire myMove function (called when drag is starting) :

// Happens when the mouse is moving inside the canvas
function myMove(event) {

   // If dragging 
   if (isDrag) {

     console.log('offsetX', event.offsetX);
     console.log('offsetY', event.offsetY);
     var dx = parseInt(event.offsetX - window['x'+indexMass]);
     var dy = parseInt(event.offsetY - window['y'+indexMass]);
     console.log('dx', dx);
     console.log('dy', dy);
  
      // Temp variables
      var tmpX = window['x'+indexMass];  
      var tmpY = window['y'+indexMass];  

      // Increment temp positions
      tmpX += dx;
      tmpY += dy;
      // Compute new angle for indexMass
      window['t'+indexMass] = Math.atan2(tmpX, tmpY);   
      console.log('printf', window['t'+indexMass]);

      // Compute new positions of disks
      dragComputePositions();

      // Update drawing
      DrawPend(canvas);

      // Highlight dragging disk
      fillDisk(indexMass, 'pink');

   }
}
like image 922
youpilat13 Avatar asked Jul 11 '16 01:07

youpilat13


People also ask

How do I drag-and-drop in HTML5 Canvas?

to true when we instantiate a shape, or we can use the draggable() method. applications automatically. bind dragstart , dragmove , or dragend events to a node.

How do I drag a canvas in HTML?

lineCap = 'round'; clearCanvas = document. getElementById("clearCanvas"); canvas. addEventListener('mousedown', dragStart, false); canvas. addEventListener('mousemove', drag, false); canvas.

Does HTML5 support canvas element?

The canvas element is part of HTML5 and allows for dynamic, scriptable rendering of 2D shapes and bitmap images.


1 Answers

You can not move the OS mouse position. You can hide the mouse canvas.style.cursor = "none"; and then draw a mouse on the canvas your self but it will lag behind by one frame because when you get the mouse coordinates the OS has already placed the mouse at that position, and if you use requestAnimationFrame (RAF) the next presentation of the canvas will be at the next display refresh interval. If you don't use RAF you may or may not present the canvas on the current display refresh, but you will get occasional flicker and shearing.

To solve the problem (which is subjective) draw a line from the rotation point through the ball to the mouse position this will at least give the user some feedback as to what is happening.

I would also add some handles to the balls so you could change the mass (volume of sphere * density) and the length of axis.. The resize cursors are a problem as the will not match the direction of required movement when the angles have changes. You would need to find one closest to the correct angle or render a cursor to a canvas and use that.

Example code shows what I mean. (does not include sim) Move mouse over balls to move, when over you will also see two circles appear to change distance and radius (mass)

/*-------------------------------------------------------------------------------------
 answer code
---------------------------------------------------------------------------------------*/






var balls = [];
var startX,startY;
var mouseOverBallIndex = -1;
var mouseOverDist = false;
var mouseOverMass = false;
const DRAG_CURSOR = "move";
const MASS_CURSOR = "ew-resize";
const DIST_CURSOR = "ns-resize";
var dragging = false;
var dragStartX = 0;
var dragStartY = 0;
function addBall(dist,radius){
    balls.push({
        dist : dist,
        radius : Math.max(10,radius),
        angle : -Math.PI / 2,
        x : 0,
        y : 0,
        mass : (4/3) * radius * radius * radius * Math.PI,
    });
}
function drawBalls(){
    var i = 0;
    var len = balls.length;
    var x,y,dist,b,minDist,index,cursor;
    ctx.lineWidth = 2;
    ctx.strokeStyle = "black";
    ctx.fillStyle = "blue"
    ctx.beginPath();
    x = startX;
    y = startY;
    ctx.moveTo(x, y)
    for(; i < len; i += 1){
        b = balls[i];
        x += Math.cos(b.angle) * b.dist;
        y += Math.sin(b.angle) * b.dist;
        ctx.lineTo(x, y);
        b.x = x;
        b.y = y;
    }
    ctx.stroke();
    minDist = Infinity;
    index = -1;
    for(i = 0; i < len; i += 1){
        b = balls[i];
        ctx.beginPath();
        ctx.arc(b.x, b.y, b.radius, 0, Math.PI * 2);
        ctx.fill();
        if(!dragging){
            x = b.x - mouse.x;
            y = b.y - mouse.y;
            dist = Math.sqrt(x * x + y * y);
            if(dist < b.radius + 5 && dist < minDist){
                minDist = dist;
                index = i;
            }
        }
    }
    if(!dragging){
        mouseOverBallIndex = index;
        if(index !== -1){
            cursor = DRAG_CURSOR;
            b = balls[index];
            ctx.fillStyle = "Red"
            ctx.beginPath();
            ctx.arc(b.x, b.y, b.radius, 0, Math.PI * 2);
            ctx.fill();
            dx = b.x - Math.cos(b.angle) * b.radius;
            dy = b.y - Math.sin(b.angle) * b.radius;
            x = dx - mouse.x;
            y = dy - mouse.y;
            dist = Math.sqrt(x * x + y * y);
            ctx.beginPath();
            if(dist < 6){
                ctx.strokeStyle = "Yellow"
                mouseOverDist = true;
                ctx.arc(dx, dy, 12, 0, Math.PI * 2);
                cursor = DIST_CURSOR;
            }else{
                ctx.strokeStyle = "black"
                mouseOverDist = false;
                ctx.arc(dx, dy, 5, 0, Math.PI * 2);

            }
            ctx.stroke();MASS_CURSOR
            dx = b.x - Math.cos(b.angle + Math.PI/2) * b.radius;
            dy = b.y - Math.sin(b.angle + Math.PI/2) * b.radius;
            x = dx - mouse.x;
            y = dy - mouse.y;
            dist = Math.sqrt(x * x + y * y);
            ctx.beginPath();
            if(dist < 6){
                ctx.strokeStyle = "Yellow"
                mouseOverMass = true;
                ctx.arc(dx, dy, 12, 0, Math.PI * 2);
                cursor = MASS_CURSOR;
            }else{
                ctx.strokeStyle = "black"
                mouseOverMass = false;
                ctx.arc(dx, dy, 5, 0, Math.PI * 2);

            }
            ctx.stroke();
            canvas.style.cursor = cursor;
        }else{
            canvas.style.cursor = "default";
        }
    }else{
        b = balls[mouseOverBallIndex];
        ctx.fillStyle = "Yellow"
        ctx.beginPath();
        ctx.arc(b.x, b.y, b.radius, 0, Math.PI * 2);
        ctx.fill();        
        
    }

}
function display(){  // put code in here
    var x,y,b
  
    if(balls.length === 0){
        startX = canvas.width/2;
        startY = canvas.height/2;
        addBall((startY * 0.8) * (1/4), startY * 0.04);
        addBall((startY * 0.8) * (1/3), startY * 0.04);
        addBall((startY * 0.8) * (1/2), startY * 0.04);
        
    }
    ctx.setTransform(1,0,0,1,0,0); // reset transform
    ctx.globalAlpha = 1;           // reset alpha
    ctx.clearRect(0,0,w,h);
    if((mouse.buttonRaw & 1) && mouseOverBallIndex > -1){
        b = balls[mouseOverBallIndex];
        if(dragging === false){
            dragging = true;
            dragStartX = balls[mouseOverBallIndex].x;
            dragStartY = balls[mouseOverBallIndex].y;
        }else{
            b = balls[mouseOverBallIndex];
            if(mouseOverBallIndex === 0){
                x = startX;
                y = startY;
            }else{
                x = balls[mouseOverBallIndex-1].x
                y = balls[mouseOverBallIndex-1].y
            }
            if(mouseOverDist){
                var dist = Math.sqrt(Math.pow(x-mouse.x,2)+Math.pow(y-mouse.y,2));
                b.dist = dist + b.radius;
                
            }else    
            if(mouseOverMass){
                var dist = Math.sqrt(Math.pow(dragStartX-mouse.x,2)+Math.pow(dragStartY-mouse.y,2));
                b.radius = Math.max(10,dist);
                b.mass = dist * dist * dist * (4/3) * Math.PI;
            }else{
                b.angle = Math.atan2(mouse.y - y, mouse.x - x);
                ctx.beginPath();
                ctx.lineWidth = 1;
                ctx.strokeStyle = "grey";
                ctx.moveTo(x,y);
                ctx.lineTo(mouse.x, mouse.y);
                ctx.stroke();
            }
        }
        
    }else if(dragging){
        dragging = false;
    }

    drawBalls();
}

/*-------------------------------------------------------------------------------------
 answer code END
---------------------------------------------------------------------------------------*/





































/** SimpleFullCanvasMouse.js begin **/
const CANVAS_ELEMENT_ID = "canv";
const U = undefined;
var w, h, cw, ch; // short cut vars 
var canvas, ctx, mouse;
var globalTime = 0; 
var createCanvas, resizeCanvas, setGlobals;
var L = typeof log === "function" ? log : function(d){ console.log(d); }
createCanvas = function () {
    var c,cs;
    cs = (c = document.createElement("canvas")).style; 
    c.id = CANVAS_ELEMENT_ID;    
    cs.position = "absolute";
    cs.top = cs.left = "0px";
    cs.zIndex = 1000;
    document.body.appendChild(c); 
    return c;
}
resizeCanvas = function () {
    if (canvas === U) { canvas = createCanvas(); }
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight; 
    ctx = canvas.getContext("2d"); 
    if (typeof setGlobals === "function") { setGlobals(); }
}
setGlobals = function(){ cw = (w = canvas.width) / 2; ch = (h = canvas.height) / 2; balls.length = 0; }
mouse = (function(){
    function preventDefault(e) { e.preventDefault(); }
    var mouse = {
        x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false, buttonRaw : 0,
        over : false,  // mouse is over the element
        bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
        mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
    };
    var m = mouse;
    function mouseMove(e) {
        var t = e.type;
        m.x = e.offsetX; m.y = e.offsetY;
        if (m.x === U) { m.x = e.clientX; m.y = e.clientY; }
        m.alt = e.altKey; m.shift = e.shiftKey; m.ctrl = e.ctrlKey;
        if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1]; }  
        else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2]; }
        else if (t === "mouseout") { m.buttonRaw = 0; m.over = false; }
        else if (t === "mouseover") { m.over = true; }
        else if (t === "mousewheel") { m.w = e.wheelDelta; }
        else if (t === "DOMMouseScroll") { m.w = -e.detail; }
        if (m.callbacks) { m.callbacks.forEach(c => c(e)); }
        e.preventDefault();
    }
    m.addCallback = function (callback) {
        if (typeof callback === "function") {
            if (m.callbacks === U) { m.callbacks = [callback]; }
            else { m.callbacks.push(callback); }
        } else { throw new TypeError("mouse.addCallback argument must be a function"); }
    }
    m.start = function (element, blockContextMenu) {
        if (m.element !== U) { m.removeMouse(); }        
        m.element = element === U ? document : element;
        m.blockContextMenu = blockContextMenu === U ? false : blockContextMenu;
        m.mouseEvents.forEach( n => { m.element.addEventListener(n, mouseMove); } );
        if (m.blockContextMenu === true) { m.element.addEventListener("contextmenu", preventDefault, false); }
    }
    m.remove = function () {
        if (m.element !== U) {
            m.mouseEvents.forEach(n => { m.element.removeEventListener(n, mouseMove); } );
            if (m.contextMenuBlocked === true) { m.element.removeEventListener("contextmenu", preventDefault);}
            m.element = m.callbacks = m.contextMenuBlocked = U;
        }
    }
    return mouse;
})();
var done = function(){
    window.removeEventListener("resize",resizeCanvas)
    mouse.remove();
    document.body.removeChild(canvas);    
    canvas = ctx = mouse = U;
    L("All done!")
}

resizeCanvas(); // create and size canvas
mouse.start(canvas,true); // start mouse on canvas and block context menu
window.addEventListener("resize",resizeCanvas); // add resize event

function update(timer){ // Main update loop
    globalTime = timer;
    display();  // call demo code
    // continue until mouse right down
    if (!(mouse.buttonRaw & 2)) { requestAnimationFrame(update); } else { done(); }
}
requestAnimationFrame(update);

/** SimpleFullCanvasMouse.js end **/
like image 121
Blindman67 Avatar answered Sep 30 '22 16:09

Blindman67