6

I just started learning html5 and I am trying to create a battleship interface with draggable ships. I need help making my dragging methods work. I am purposely not using a library because I need make the ships draggable over another canvas interface (the battleship board), which I could not figure out how to do with the Kinetic library. I feel like I am close, but I cannot figure out the last bit. The ships should be smoothly dragged but they seem to snap to the location of the mouse when clicked...

Here is my clode:

<!doctype html> <html> <head> <meta charset="UTF-8" /> <title>Canvas Drag and Drop Test</title> </head> <body> <section> <div align=center> <canvas id="canvas" width="550" height="550"> This text is displayed if your browser does not support HTML5 Canvas. </canvas> </div> <script type="text/javascript"> var canvas; var ctx; var x = 75; var y = 50; var WIDTH = 550; var HEIGHT = 550; var dragok = false; var ships = []; var ship; var shipFill = "#FF0000"; //Definitions //Draggable Carrier var caRectX = 100; var caRectY = 50; var caRectHeight = 50; var caRectWidth = 5 * 50; var carrier = { x : caRectX, y : caRectY, width : caRectWidth, height : caRectHeight, fill : shipFill, dragging : false, offsetX : 0, offsetY : 0, }; ships.push(carrier); //Draggable Battleship var bsRectX = 100; var bsRectY = 150; var bsRectHeight = 50; var bsRectWidth = 4 * 50; var battleship = { x : bsRectX, y : bsRectY, width : bsRectWidth, height : bsRectHeight, fill : shipFill, dragging : false, offsetX : 0, offsetY : 0, }; ships.push(battleship); //Draggable Patrolboat var pbRectX = 100; var pbRectY = 250; var pbRectHeight = 50; var pbRectWidth = 2 * 50; var patrolboat = { x : pbRectX, y : pbRectY, width : pbRectWidth, height : pbRectHeight, fill : shipFill, dragging : false, offsetX : 0, offsetY : 0, }; ships.push(patrolboat); //Draggable Submarine var suRectX = 100; var suRectY = 350; var suRectHeight = 50; var suRectWidth = 3 * 50; var submarine = { x : suRectX, y : suRectY, width : suRectWidth, height : suRectHeight, fill : shipFill, dragging : false, offsetX : 0, offsetY : 0, }; ships.push(submarine); //Draggable destroyer var deRectX = 100; var deRectY = 450; var deRectHeight = 50; var deRectWidth = 3 * 50; var destroyer = { x : deRectX, y : deRectY, width : deRectWidth, height : deRectHeight, dragging : false, fill : shipFill }; ships.push(destroyer) function rect(x, y, w, h) { ctx.beginPath(); ctx.rect(x, y, w, h); ctx.closePath(); ctx.fill(); } function clear() { ctx.clearRect(0, 0, WIDTH, HEIGHT); } function init() { canvas = document.getElementById("canvas"); ctx = canvas.getContext("2d"); return setInterval(draw, 10); } function draw() { clear(); ctx.fillStyle = "#FAF7F8"; rect(0, 0, WIDTH, HEIGHT); ctx.fillStyle = "#444444"; for (var i = 0; i < ships.length; i++) { rect(ships[i].x, ships[i].y, ships[i].width, ships[i].height); } } function myMove(e) { if (ship.dragging) { ship.x = e.pageX - canvas.offsetLeft; ship.y = e.pageY - canvas.offsetTop; draw() } } function myDown(e) { ship = getClickedShip(e.pageX,e.pageY); if (ship!=null) { ship.x = e.pageX - canvas.offsetLeft; ship.y = e.pageY - canvas.offsetTop; ship.dragging = true; canvas.onmousemove = myMove(); } } function myUp() { ship.dragging = false; canvas.onmousemove = null; } function getClickedShip(sx,sy){ for (var i = 0; i < ships.length; i++){ if(sx > (ships[i].x )+ canvas.offsetLeft && sx < (ships[i].x+ships[i].width+ canvas.offsetLeft) && sy > (ships[i].y + canvas.offsetTop) && sy < (ships[i].y+ships[i].height)) return ships[i]; } } init(); canvas.onmousedown = myDown; canvas.onmouseup = myUp; </script> </section> </body> </html> 

1 Answer 1

10

This is the procedure for making an html shape draggable

Note that this has been answered before on SO (many times!)

But this answer illustrates the new context.isPointInPath method to hit-test whether a point is inside an html canvas path.

Hopefully, this new hit-testing method will be new & useful to the OP and others :)

Here's the general procedure for dragging shapes in html canvas:

On mouseDown:

  • save this mouseX position in a variable (lastX)
  • save this mouseY position in a variable (lastY)
  • set the mouseIsDown flag to true

On mouseUp

  • set the mouseIsDown flag to false

On mouseMove

  • Hit-test each ship to see if it should be dragged.
  • If the lastX/lastY was inside a ship, that ship is being dragged
  • Move dragging ships by the distance the mouse has just moved

MouseDown handler code:

function handleMouseDown(e){ // get the current mouse position relative to the canvas mouseX=parseInt(e.clientX-offsetX); mouseY=parseInt(e.clientY-offsetY); // save this last mouseX/mouseY lastX=mouseX; lastY=mouseY; // set the mouseIsDown flag mouseIsDown=true; } 

MouseUp handler code:

function handleMouseUp(e){ // clear the mouseIsDown flag mouseIsDown=false; } 

MouseMove handler code:

This code illustrates using context.isPointInPath to hit-test an html canvas path

The procedure to do that is:

  • define a path (but not draw it -- no fill, no stroke)
  • use context.isPointInPath(x,y) to test if x,y are inside the path defined above.

Here's the mouseMove handler using context.isPointInPath

function handleMouseMove(e){ // if the mouseIsDown flag is’nt set, no work to do if(!mouseIsDown){ return; } // get mouseX/mouseY mouseX=parseInt(e.clientX-offsetX); mouseY=parseInt(e.clientY-offsetY); // for each ship in the ships array // use context.isPointInPath to test if it’s being dragged for(var i=0;i<ships.length;i++){ var ship=ships[i]; drawShip(ship); if(ctx.isPointInPath(lastX,lastY)){ // if this ship’s being dragged, // move it by the change in mouse position from lastXY to currentXY ship.x+=(mouseX-lastX); ship.y+=(mouseY-lastY); ship.right=ship.x+ship.width; ship.bottom=ship.y+ship.height; } } // update the lastXY to the current mouse position lastX=mouseX; lastY=mouseY; // draw all ships in their new positions drawAllShips(); } 

Note about enhancing performance:

  • In production, you'll want to have the mouseMove just save the mouse positions.
  • Then have another procedure retrieve those saved positions and do hit-testing/redrawing.
  • That other procedure will probably be inside a timed loop like requestAnimationFrame.

Here’s code and a Fiddle: http://jsfiddle.net/m1erickson/sEBAC/

$(function() { var canvas = document.getElementById("canvas"); var ctx = canvas.getContext("2d"); ctx.strokeStyle = "lightgray"; var canvasOffset = $("#canvas").offset(); var offsetX = canvasOffset.left; var offsetY = canvasOffset.top; var mouseIsDown = false; var lastX = 0; var lastY = 0; var ships = []; // make some ship makeShip(20, 30, 50, 25, "skyblue"); makeShip(20, 100, 30, 25, "skyblue"); makeShip(20, 170, 50, 25, "salmon"); makeShip(20, 240, 30, 25, "salmon"); function makeShip(x, y, width, height, fill) { var ship = { x: x, y: y, width: width, height: height, right: x + width, bottom: y + height, fill: fill } ships.push(ship); return (ship); } drawAllShips(); function drawAllShips() { ctx.clearRect(0, 0, canvas.width, canvas.height); for (var i = 0; i < ships.length; i++) { var ship = ships[i] drawShip(ship); ctx.fillStyle = ship.fill; ctx.fill(); ctx.stroke(); } } function drawShip(ship) { ctx.beginPath(); ctx.moveTo(ship.x, ship.y); ctx.lineTo(ship.right, ship.y); ctx.lineTo(ship.right + 10, ship.y + ship.height / 2); ctx.lineTo(ship.right, ship.bottom); ctx.lineTo(ship.x, ship.bottom); ctx.closePath(); } function handleMouseDown(e) { mouseX = parseInt(e.clientX - offsetX); mouseY = parseInt(e.clientY - offsetY); // mousedown stuff here lastX = mouseX; lastY = mouseY; mouseIsDown = true; } function handleMouseUp(e) { mouseX = parseInt(e.clientX - offsetX); mouseY = parseInt(e.clientY - offsetY); // mouseup stuff here mouseIsDown = false; } function handleMouseMove(e) { if (!mouseIsDown) { return; } mouseX = parseInt(e.clientX - offsetX); mouseY = parseInt(e.clientY - offsetY); // mousemove stuff here for (var i = 0; i < ships.length; i++) { var ship = ships[i]; drawShip(ship); if (ctx.isPointInPath(lastX, lastY)) { ship.x += (mouseX - lastX); ship.y += (mouseY - lastY); ship.right = ship.x + ship.width; ship.bottom = ship.y + ship.height; } } lastX = mouseX; lastY = mouseY; drawAllShips(); } $("#canvas").mousedown(function(e) { handleMouseDown(e); }); $("#canvas").mousemove(function(e) { handleMouseMove(e); }); $("#canvas").mouseup(function(e) { handleMouseUp(e); }); }); // end $(function(){});
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script> <canvas id="canvas" width=300 height=300></canvas>

Sign up to request clarification or add additional context in comments.

3 Comments

Can we have the same demo working in case we want to use images ...multiple images in canvas dragable.
Use ctx.drawImage instead of 'ctx.fill and ctx.stroke' in drawAllShips. The image will display and the path still provides the hit region used in isPointInPath. I have confidence that you can make the necessary adjustments. Cheers!
that worked well thanks, apart from this actually i have some other issues as well regarding canvas and all [wont be possible to share here]..and i want advice from someone like you [perfect]. Do you consult outside stack overflow as well...would be great and thankful, if you can give some of your time, if possible.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.