Saturday, July 23, 2016

Nice Drag and drop on a canvas HTML5

Leave a Comment

I try to implement a nice 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 stucked with this sphere or the sphere to follow the "delta" of the mouse coordinates when I am not on sphere anymore.

To summarize, I don't know how to create a fine and user friendly drag and drop, if someone could help me or give me some advices, this would be great.

Thanks

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 have to make you notice 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');     } } 

UPDATE 4 - Bounty :

Problem solved ! I forgot to take into account the position of "indexMass-1" disk to compute the new angle with Math.atan2 function.

1 Answers

Answers 1

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 **/

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment