如何使用本机javascript在画布周围拖动形状(如rect,circle,其他多边形等)

时间:2015-12-19 09:05:59

标签: javascript canvas

enter image description here模拟形状点击并尝试使用Sahi框架在画布内拖放。我找到了一些参考资料,通过循环包含形状位置/大小的数组,我们得到是否点击了形状。使用sahi框架/本机jaascript测试其他网站时,如何实现?我想在画布中选择那个形状并在画布内拖放。这是我的目标。

1 个答案:

答案 0 :(得分:2)

在html5画布中,A" Shape"使用Path定义和绘制。

您可以通过以下方式定义形状:

  • 声明您开始了一条新路径:context.beginPath
  • 使用一个或多个路径定义命令:moveTo, lineTo, arc

    // define a triangle path
    context.beginPath();
    context.moveTo(x+50,y+50);
    context.lineTo(x+100,y+100);
    context.lineTo(x+0,y+100);
    // see not below about .closePath()
    context.closePath(); 
    

注意:context.closePath NOT 用于关闭context.beginPath命令 - 它不像右括号!相反,它用于从最后一个坐标到起始坐标绘制一条线 - 到"包围"路径。在我们的例子中,它绘制了一个"封闭的"从[x + 0,y + 100]到[x + 50,y + 50]的行。

仅定义路径不会导致它被绘制到画布上。要将形状实际绘制到画布上,您可以:

  • 描绘路径的轮廓,和/或
  • 填写路径内部。

    context.stroke();
    context.fill();
    

例如,这里是如何定义和绘制三角形的。您还可以使用偏移变量(示例中的[x,y])在画布上的任何位置重新定位三角形。

// set the offset of the triangle
var x=30;
var y=40;

// define the path
context.beginPath();
context.moveTo(x+50,y+50);
context.lineTo(x+100,y+100);
context.lineTo(x+0,y+100);
context.closePath();

// stroke the path
context.stroke();

// if desired, you can also fill the inside of the path
context.fill();

要拖动形状,您必须测试鼠标是否超过该形状。你可以"命中测试"使用context.isPointInPath最近定义的形状。

请务必仔细阅读!

您可以点击测试最近和#34;定义的" 路径。如果您定义并绘制多个路径,那么isPointInPath将仅测试最后定义的路径

if(context.isPointInPath(mouseX,mouseY)){
    console.log('Yes, the mouse is in the triangle.');
}

另请注意,您不必重新测试正在测试的路径,因此您的绘图不会被命中测试过程改变。所以你通过以下方式测试多条路径:

  1. 定义其中一条路径
  2. 使用isPointInPath(mouseX,mouseY)
  3. 进行测试
  4. 对下一个路径(路径==形状)重复步骤#1
  5. 画布上没有任何东西可以被移动 - 一切都像干漆一样永久。因此,移动"在画布上的形状,您清除整个画布并在其移动位置重绘形状:

    // clear the canvas
    context.clearRect(canvas.width,canvas.height);
    
    // move the canvas by changing it's offsets
    x+=20;
    y+=30;
    
    // redefine and restroke the shape
    context.beginPath();
    context.moveTo(x+50,y+50);
    context.lineTo(x+100,y+100);
    context.lineTo(x+0,y+100);
    context.closePath();
    context.stroke();
    

    为了重新定义和重新描绘形状更可重用,您可以将代码放在函数中:

    function myTriangle(alsoStroke){
        context.beginPath();
        context.moveTo(x+50,y+50);
        context.lineTo(x+100,y+100);
        context.lineTo(x+0,y+100);
        context.closePath();
        if(alsoStroke){
            context.stroke();
        }
    }
    

    您可以阅读有关拖动形状in this previous post的更多信息。由于你不能移动一个形状,你不能拖动"一个形状。您必须再次清除画布并在其新拖动的位置重绘它。

    要拖动形状,您需要收听4个鼠标事件。

    在mousedown:检查鼠标是否在形状上方,如果是,则设置一个标志,指示拖动已经开始。要检查鼠标是否在形状上,可以使用画布上下文的isPointInPath方法,该方法测试[x,y]点是否在最近绘制的路径内。

    在mousemove中:如果设置了拖动标记(表示拖动正在进行中),请将所选文本的位置更改为用户拖动的距离并将形状重新绘制到新位置

    在mouseup或mouseout中:拖动结束,拖动标记清晰。

    以下是示例代码和演示:

    
    
    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");
    var cw=canvas.width;
    var ch=canvas.height;
    function reOffset(){
      var BB=canvas.getBoundingClientRect();
      offsetX=BB.left;
      offsetY=BB.top;        
    }
    var offsetX,offsetY;
    reOffset();
    window.onscroll=function(e){ reOffset(); }
    window.onresize=function(e){ reOffset(); }
    
    var isDown=false;
    var startX,startY;
    
    var poly={
      x:0,
      y:0,
      points:[{x:50,y:50},{x:75,y:25},{x:100,y:50},{x:75,y:125},{x:50,y:50}],
    }
    
    ctx.fillStyle='skyblue';
    ctx.strokeStyle='gray';
    ctx.lineWidth=3;
    
    draw();
    
    // listen to mouse events
    $("#canvas").mousedown(function(e){handleMouseDown(e);});
    $("#canvas").mousemove(function(e){handleMouseMove(e);});
    $("#canvas").mouseup(function(e){handleMouseUp(e);});
    $("#canvas").mouseout(function(e){handleMouseOut(e);});
    
    
    function draw(){
      ctx.clearRect(0,0,cw,ch);
      define();
      ctx.fill();
      ctx.stroke()
    }
    
    function define(){
      ctx.beginPath();
      ctx.moveTo(poly.points[0].x+poly.x,poly.points[0].y+poly.y);
      for(var i=0;i<poly.points.length;i++){
        ctx.lineTo(poly.points[i].x+poly.x,poly.points[i].y+poly.y);
      }
      ctx.closePath();
    }
    
    function handleMouseDown(e){
      // tell the browser we're handling this event
      e.preventDefault();
      e.stopPropagation();
    
      startX=parseInt(e.clientX-offsetX);
      startY=parseInt(e.clientY-offsetY);
    
      // Put your mousedown stuff here
      define();
      if(ctx.isPointInPath(startX,startY)){
        isDown=true;
      }
    }
    
    function handleMouseUp(e){
      // tell the browser we're handling this event
      e.preventDefault();
      e.stopPropagation();
    
      mouseX=parseInt(e.clientX-offsetX);
      mouseY=parseInt(e.clientY-offsetY);
    
      // Put your mouseup stuff here
      isDown=false;
    }
    
    function handleMouseOut(e){
      // tell the browser we're handling this event
      e.preventDefault();
      e.stopPropagation();
    
      mouseX=parseInt(e.clientX-offsetX);
      mouseY=parseInt(e.clientY-offsetY);
    
      // Put your mouseOut stuff here
      isDown=false;
    }
    
    function handleMouseMove(e){
      if(!isDown){return;}
      // tell the browser we're handling this event
      e.preventDefault();
      e.stopPropagation();
    
      mouseX=parseInt(e.clientX-offsetX);
      mouseY=parseInt(e.clientY-offsetY);
    
      // Put your mousemove stuff here
      var dx=mouseX-startX;
      var dy=mouseY-startY;
      startX=mouseX;
      startY=mouseY;
    
      poly.x+=dx;
      poly.y+=dy;
      draw();
    
    }
    &#13;
    body{ background-color: ivory; }
    #canvas{border:1px solid red; }
    &#13;
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
    <h4>Drag the polygon</h4>
    <canvas id="canvas" width=300 height=300></canvas>
    &#13;
    &#13;
    &#13;

    [另外:使用context.getImageData发现矩形]

    如果你没有形状的位置/大小而你有一个形状的图像,那么你必须通过搜索像素来获得位置/大小。这是一个示例,说明如何通过搜索像素来隔离矩形:

    &#13;
    &#13;
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");
    var cw, ch;
    
    // background definition
    // OPTION: look at the top-left pixel and assume == background
    //         then set these vars automatically
    var isTransparent = false;
    var bkColor = {
        r: 255,
        g: 255,
        b: 255
    };
    var bkFillColor = "rgb(" + bkColor.r + "," + bkColor.g + "," + bkColor.b + ")";
    
    cw = canvas.width;
    ch = canvas.height;
    ctx.fillStyle = "white";
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    drawTestRect(30, 30, 50, 50, "1");
    drawTestRect(100, 30, 50, 30, "2");
    drawTestRect(170, 30, 30, 50, "3");
    
    function drawTestRect(x, y, w, h, label) {
        ctx.fillStyle = "black";
        ctx.fillRect(x, y, w, h);
        ctx.fillStyle = "white";
        ctx.font = "24px verdana";
        ctx.fillText(label, x + 10, y + 25);
    }
    
    function clipBox(data) {
        var pos = findEdge(data);
        if (!pos.valid) {
            return;
        }
        var bb = findBoundary(pos, data);
        alert("Found target at "+bb.x+"/"+bb.y+", size: "+bb.width+"/"+bb.height);            
    
        clipToImage(bb.x, bb.y, bb.width, bb.height);
        if (isTransparent) {
            // clear the clipped area
            // plus a few pixels to clear any anti-aliasing
            ctx.clearRect(bb.x - 2, bb.y - 2, bb.width + 4, bb.height + 4);
        } else {
            // fill the clipped area with the bkColor
            // plus a few pixels to clear any anti-aliasing
            ctx.fillStyle = bkFillColor;
            ctx.fillRect(bb.x - 2, bb.y - 2, bb.width + 4, bb.height + 4);
        }
    }
    
    function xyIsInImage(data, x, y) {
        // find the starting index of the r,g,b,a of pixel x,y
        var start = (y * cw + x) * 4;
        if (isTransparent) {
            return (data[start + 3] > 25);
        } else {
            var r = data[start + 0];
            var g = data[start + 1];
            var b = data[start + 2];
            var a = data[start + 3]; // pixel alpha (opacity)
            var deltaR = Math.abs(bkColor.r - r);
            var deltaG = Math.abs(bkColor.g - g);
            var deltaB = Math.abs(bkColor.b - b);
            return (!(deltaR < 5 && deltaG < 5 && deltaB < 5 && a > 25));
        }
    }
    
    function findEdge(data) {
        for (var y = 0; y < ch; y++) {
            for (var x = 0; x < cw; x++) {
                if (xyIsInImage(data, x, y)) {
                    return ({
                        x: x,
                        y: y,
                        valid: true
                    });
                }
            }
        }
        return ({
            x: -100,
            y: -100,
            valid: false
        });
    }
    
    function findBoundary(pos, data) {
        var x0 = x1 = pos.x;
        var y0 = y1 = pos.y;
        while (y1 <= ch && xyIsInImage(data, x1, y1)) {
            y1++;
        }
        var x2 = x1;
        var y2 = y1 - 1;
        while (x2 <= cw && xyIsInImage(data, x2, y2)) {
            x2++;
        }
        return ({
            x: x0,
            y: y0,
            width: x2 - x0,
            height: y2 - y0 + 1
        });
    }
    
    function drawLine(x1, y1, x2, y2) {
        ctx.beginPath();
        ctx.moveTo(x1, y1);
        ctx.lineTo(x2, y2);
        ctx.strokeStyle = "red";
        ctx.lineWidth = 0.50;
        ctx.stroke();
    }
    
    function clipToImage(x, y, w, h) {
        // don't save anti-alias slivers
        if (w < 3 || h < 3) {
            return;
        }
        // save clipped area to an img element
        var tempCanvas = document.createElement("canvas");
        var tempCtx = tempCanvas.getContext("2d");
        tempCanvas.width = w;
        tempCanvas.height = h;
        tempCtx.drawImage(canvas, x, y, w, h, 0, 0, w, h);
        var image = new Image();
        image.width = w;
        image.height = h;
        image.src = tempCanvas.toDataURL();
        $("#clips").append(image);
    }
    
    $("#unbox").click(function () {
        var imgData = ctx.getImageData(0, 0, cw, ch);
        var data = imgData.data;
        clipBox(data);
    });
    &#13;
    body {
        background-color: ivory;
    }
    canvas {
        border:1px solid red;
    }
    #clips {
        border:1px solid blue;
        padding:5px;
    }
    img {
        margin:3px;
    }
    &#13;
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
    <button id="unbox">Clip next sub-image</button><br>
    <canvas id="canvas" width=300 height=150></canvas><br>
    <h4>Below are images clipped from the canvas above.</h4><br>
    <div id="clips"></div>
    &#13;
    &#13;
    &#13;

    [另外:发现红色描边矩形的边界]

    你可以测试&#34; reddishness&#34;通过检查像素的红色分量值是否远大于绿色&amp;蓝色组件值。

    function xyIsInImage(data, x, y) {
        // find the starting index of the r,g,b,a of pixel x,y
        var n = (y * cw + x) * 4;
        return(data[n+3]>240 &&        // this pixel is mostly opaque
            data[n]-data[n+1]>180 &&   // this pixel is more reddish than green
            data[n]-data[n+2]>180      // this pixel is more reddish then blue
        );
    

    然后使用此红色测试来查找带红色描边矩形的边界:

    • 找到红色矩形的理论左上角像素。发现的像素可能位于矩形的顶部。它可能不在rect的左侧,因为你的图像显示了rect的角点像素偏红得多。因此,将y值声明为矩形的顶部。
    • 向矩形中心移动几个像素。
    • 向下测试每个垂直像素,直到找到描边矩形的底部带红色边框。
    • 向右测试每个水平像素,直到找到描边矩形的右边红色边框。
    • 向左测试每个水平像素,直到找到描边矩形的左红色边框。

    现在你已经找到了rect的左上角和右下角。

    enter image description here

    以下是使用您的图片的示例代码和演示:

    &#13;
    &#13;
    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");
    var cw=canvas.width;
    var ch=canvas.height;
    
    var img=new Image();
    img.crossOrigin='anonymous';
    img.onload=start;
    img.src="https://dl.dropboxusercontent.com/u/139992952/multple/raw.png";
    function start(){
      cw=canvas.width=img.width;
      ch=canvas.height=img.height;
      ctx.drawImage(img,0,0);
      var data=ctx.getImageData(0,0,cw,ch).data;
      var edge=findEdge(data);
      var top=edge.y;
      var x,y,left,bottom,right;
      var off=25;
      for(var y=edge.y+off;y<ch;y++){if(xyIsInImage(data,edge.x+off,y)){bottom=y; break;}}
      for(var x=edge.x+off;x<cw;x++){if(xyIsInImage(data,x,edge.y+off)){right=x;break;}}
      for(var x=edge.x+off;x>=0;x--){if(xyIsInImage(data,x,edge.y+off)){left=x;break;}}
      dot({x:left,y:top});
      dot({x:right,y:bottom});
    }
    
    //
    function dot(pt){
      ctx.beginPath();
      ctx.arc(pt.x,pt.y,4,0,Math.PI*2);
      ctx.closePath();
      ctx.fillStyle='red';
      ctx.fill();
      ctx.strokeStyle='gold';
      ctx.lineWidth=2;
      ctx.stroke();
    }
    
    function xyIsInImage(data, x, y) {
      // find the starting index of the r,g,b,a of pixel x,y
      var n = (y * cw + x) * 4;
      return(data[n+3]>240 &&
             data[n]-data[n+1]>180 && 
             data[n]-data[n+2]>180
            );
    }
    
    function findEdge(data) {
      for (var y = 0; y < ch; y++) {
        for (var x = 0; x < cw; x++) {
          if(xyIsInImage(data, x, y)){ return ({x:x,y:y,valid:true}); }
        }
      }
      return ({x:-100,y:-100,valid:false});
    }
    &#13;
    body{ background-color: ivory; }
    #canvas{border:1px solid red; margin:0 auto; }
    &#13;
    <h4>Red-gold dots are on top-left & bottom-right of target rect.</h4>
    <canvas id="canvas" width=300 height=300></canvas>
    &#13;
    &#13;
    &#13;