如何在画布上实现HTML5可拖动对象?

5
我刚开始学习html5,并尝试使用可拖动的船只创建战舰界面。我需要帮助使我的拖动方法能够正常工作。出于需要使船只可以在另一个画布接口(战舰游戏板)中进行拖动,我故意不使用库,但我无法使用Kinetic库找到如何实现这一点。我感觉离成功很近了,但我无法解决最后一点问题。船只应该可以平稳地拖动,但它们似乎会在点击时瞬间跳到鼠标指针的位置...

这是我的代码:

<!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个回答

11

这是制作可拖动html形状的步骤:

请注意,这在SO上已经有很多次回答了。

但是,这个答案展示了新的context.isPointInPath方法,以测试一个点是否在HTML画布路径内。

希望这个新的命中测试方法对OP和其他人都是新的和有用的 :)

这是在HTML画布中拖动形状的一般步骤:

在MouseDown事件上:

  • 将该鼠标X位置保存到变量lastX中
  • 将该鼠标Y位置保存到变量lastY中
  • 设置mouseIsDown标志为true

在MouseUp事件上:

  • 设置mouseIsDown标志为false

在MouseMove事件上:

  • 对每个ship进行命中测试,看它是否应该被拖动。
  • 如果lastX/lastY在某个ship内部,则该ship正在被拖动
  • 移动正在被拖动的ships的距离就是鼠标刚刚移动的距离

MouseDown处理程序代码:

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;
}

鼠标抬起事件处理代码:

function handleMouseUp(e){

  // clear the mouseIsDown flag

  mouseIsDown=false;
}

MouseMove处理程序代码:

此代码演示了如何使用context.isPointInPath测试html画布路径的命中测试。

执行该操作的步骤是:

  • 定义一个路径(但不绘制它--无填充、无描边)
  • 使用context.isPointInPath(x,y)来测试x,y是否在上述定义的路径内。

这里是使用context.isPointInPath的mouseMove处理程序。

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();
}

关于提高性能的说明:

  • 在生产中,您需要让mouseMove仅保存鼠标位置。
  • 然后使用另一个程序检索这些保存的位置并执行命中测试/重绘。
  • 该程序可能位于类似requestAnimationFrame的定时循环内部。

以下是代码和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>


如果我们想在画布上使用多个可拖动的图像,那么我们能否让相同的演示文稿起作用? - StackUser
在drawAllShips中使用ctx.drawImage代替'ctx.fill和ctx.stroke'。图像将被显示,路径仍然提供了isPointInPath中使用的命中区域。我相信你可以做出必要的调整。干杯! - markE
这个真的很好用,谢谢!但除此之外我还有一些关于画布等其他问题 [无法在此处分享],我想向像您这样的专家咨询一下 [太完美了]。您还会在 Stack Overflow 以外进行咨询吗?如果您能抽出时间帮忙,我将非常感激。 - StackUser

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接