如何使用鼠标(按住时)从画布上移动一个圆形?

3
我有这个问题,不知道怎么处理。我努力了将近2个小时来解决它。我尝试了各种方法,但都没有成功。如果这是一个初学者的问题,请原谅我,并详细解释每一步你所做的事情。
我创建了一个画布,并在其中画了一个圆。现在,我想用鼠标移动圆圈,但只有在鼠标按住时才能移动。有办法可以做到吗,还是我浪费时间了?以下是代码:
<canvas draggable="true" id="canv" name="canv" width="600" height="500" style="border:1px solid #000000;" onclick="">Your browser does not support the HTML5 canvas tag.</canvas> <!-- < -- the canvas -->

<script>

var canv = document.getElementById("canv");
var context = canv.getContext("2d");

var width = canv.offsetWidth-1;
var height = canv.offsetHeight-1;

context.beginPath();
context.arc(width/2, height/2, 75, 0, 2*Math.PI);
context.stroke();

</script>

我尝试过的一些方法。其中一些被留在了备注中 /**/,其他的则被删除了。 1.
<canvas draggable="true" id="canv" name="canv" width="600" height="500" style="border:1px solid #000000;" onclick="circle_move()"></canvas>

function circle_move(event){

    var x = event.clientX;
    var y = event.clientY;

    document.getElementById("txt").setAttribute("value", "X: "+x+" and Y: "+y);

    document.getElementById("canv").addEventListener("onmousemove", function(){
        context.beginPath();
        context.clearRect(0, 0, width, height);
        context.arc(x, y, 75, 0, 2*Math.PI);
        context.stroke();
    });
}

2.

document.getElementById("canv").addEventListener("mousedown", 
    function(event){
        var x = event.clientX;
        var y = event.clientY;

        document.getElementById("txt").setAttribute("value", "X: "+x+" and Y: "+y);

        context.beginPath();
        context.clearRect(0, 0, width, height);
        context.arc(x, y, 75, 0, 2*Math.PI);
        context.stroke();
    }
);

3.

<canvas draggable="true" id="canv" name="canv" width="600" height="500" style="border:1px solid #000000;" onmousedown="launch"></canvas>

function loop(event){
    var x = event.clientX;
    var y = event.clientY;

    document.getElementById("txt").setAttribute("value", "X: "+x+" and Y: "+y);

    context.beginPath();
    context.clearRect(0, 0, width, height);
    context.arc(x, y, 75, 0, 2*Math.PI);
    context.stroke();
}

function launch(event){
    loop();
    if(event.which !== 1 || event.which !== 2 || event.which !== 3){
        launch(event);
    }   
}

1
你说你尝试了各种方法,但还没有展示它们。 - Joseph Young
使用 onmousemove 事件,并添加额外的 if 语句来检查左鼠标按钮是否按下。 - Luke Kot-Zaniewski
@JosephYoung 我更新了帖子。谢谢! - default
@LucasKot-Zaniewski 我想我已经尝试过那个了.. - default
请查看我的答案,如果这是您要寻找的,请告诉我。 - Luke Kot-Zaniewski
3个回答

3

动画和用户界面(UI)

当你在画布上拖动任何东西时,实际上是在给画布添加动画效果。为了获得最佳的效果,您需要停止使用DOM方式思考,而是开始像编写游戏那样编写代码。

一种函数管理所有

为了协调所有的操作,您需要一个函数来处理所有的动画效果。有时在游戏圈中称为mainLoop,它使得处理动态内容变得更加容易。

主循环

function mainLoop(time){  // this is called 60 times a second if there is no delay
    // clear the canvas ready to be rendered on.
    ctx.clearRect(0,0,canvas.width,canvas.height);

    updateDisplay(); // call  the function that is rendering the display

    // get the next frame
    requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);

从主循环中,您可以检查当前应用程序状态并调用适当的功能。在这个例子中,它只是简单地调用updateDisplay()

KISS IO 事件。

鼠标、键盘、触摸等事件处理程序不应执行任何功能逻辑。这些事件可能会非常频繁地触发,但显示器每秒只更新60次。当用户只能看到CPU正在执行的10%左右的工作时,从可能每秒触发500次的事件进行渲染是没有意义的。

编写IO事件的最佳方法就是将其视为数据记录器。尽快获取尽可能多的信息并退出,不要为每种类型的事件编写不同的事件侦听器,保持代码简单,只编写一个事件处理程序来处理尽可能多的内容。如果需要键盘,请将其添加到同一侦听器中。

var mouse = (function(){
    var bounds;
    var m = {x:0,y:0,button:false};
    function mouseEvent(event){
        bounds = event.currentTarget.getBoundingClientRect();
        m.x = event.pageX - bounds.left + scrollX;
        m.y = event.pageY - bounds.top + scrollY;
        if(event.type === "mousedown"){
            m.button = true;
        }else if(event.type === "mouseup"){
            m.button = false;
        }
    }
    m.start = function(element){
        ["mousemove","mousedown","mouseup"].forEach(eventType => element.addEventListener(eventType, mouseEvent)); 
    }
    return m;
}())
mouse.start(canvas);

现在,您可以通过简单的鼠标界面随时访问鼠标。

圆形

如果没有东西可以移动,添加漂亮的界面就没有意义。以下是一个帮助管理圆形的对象。它可以创建、绘制和定位圆形。

其中包括findClosest函数,它可以获取鼠标下方的圆形。它将返回鼠标下的最小圆形。如果鼠标下没有任何东西,它将返回未定义。

var circles = {
    items [],
    drawCircle(){  // function for the circle
        ctx.beginPath();    
        ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
        ctx.stroke();
    },
    each(callback){  // iterator
        var i;
        for(i = 0; i < this.items.length; i++){
            callBack(this.items[i],i);
        }
    },
    drawCircles(){
        this.each(c => {
            c.draw();
        })
    },
    addCircle(x,y,radius){
        var circle = {x, y, radius, draw : this.drawCircle};
        this.items.push(circle);
        return circle;
    },
    getClosest(pos){
        var minDist, i, dist, x, y, foundCircle;
        minDist = Infinity;
        this.each(c =>{
            x = pos.x - c.x;
            y = pos.y - c.y;
            dist = Math.sqrt(x * x + y * y);
            if(dist <= c.radius && dist < minDist){
                minDist = dist;
                foundCircle = c;
            }
        })
        return foundCircle;
    }
}

如何进行拖放操作

在拖放对象时需要注意许多细节。你需要一个函数来找到鼠标最接近的对象,还需要提供反馈信息,以便用户可以看到哪些对象可以被拖动。

你可能会遇到许多不同的拖放类型事件。例如:拖动创建、拖动移动、拖动将某物体放置到画布上或者操纵自定义呈现的 UI 对象。如果你从一个对象管理拖放状态,就可以确保当拖动圆形时不会意外点击 UI 项。

当开始拖动时,检查拖动将要执行的操作。然后标记正在进行拖动,并指示拖动要做什么(参见 updateDisplay 函数)。

当拖动处于活动状态时,执行该操作。当鼠标松开时,只需停止拖动。

var dragging = {
    started : false, // true if dragging
    type : null,     // string with the type of drag event
    currentObj : null,  // what we are dragging
    startX : 0,      // info about where the drag started
    startY : 0,
    start(type, obj){  // called at the start of a drag.
        this.startX = mouse.x;
        this.startY = mouse.y;
        this.started = true;
        this.type = type;
        this.currentObj = obj;
    }
}

接下来是渲染函数。它在每一帧中被调用。它会检查鼠标按钮是否被按下,如果是,则执行相应操作,并设置光标以便人们知道该做什么,然后绘制圆形。

var cursor = "default";  // to hold the cursor
var overCircle = null;   // this holds whatever circle happens to be under the mouse when not dragging
function updateDisplay(){
    var x,y, c;
    cursor = "default";  // set default cursor
    // check that the mouse if over the canvas
    if(mouse.x >= 0 && mouse.x < canvas.width && mouse.y >= 0 && mouse.y < canvas.height){
        cursor = "crosshair";
    }
    // is the mouse button down
    if(mouse.button){  // the button is down
        if(!dragging.started){  // if not dragging start 
            if(overCircle){  // start a move drag if over a circle
                dragging.start("move",overCircle)
                overCircle = null;
            }else{ // start a create drag 
                dragging.start("create",circles.addCircle(mouse.x, mouse.y, 1));
            }
        }
        c = dragging.currentObj;
        // Update the drag state o fthe object being draged and the type of drag
        if(dragging.type === "create"){
            x = c.x - mouse.x;
            y = c.y - mouse.y;
            c.radius = Math.sqrt(x * x + y * y);
        }else if(dragging.type === "move"){
            x = dragging.startX - mouse.x;
            y = dragging.startY - mouse.y;
            c.x -= x;
            c.y -= y;
            dragging.startX = mouse.x;
            dragging.startY = mouse.y;
        }
        cursor = "none";
    } else {  // button must be up
        if(dragging.started){ // have we been dragging something.
            dragging.started = false; // drop it
        }
    }
    // draw the circles
    ctx.strokeStyle = "black";
    circles.draw();
    // if not dragging 
    if(!dragging.started){
        // find circle under the mouse
        c = circles.getClosest(mouse);
        if(c !== undefined){  // if there is a circle under the mouse highlight it
            cursor = "move";
            ctx.strokeStyle = "red";
            ctx.fillStyle = "rgba(0,255,255,0.1)";
            c.draw();
            ctx.fill();
            overCircle = c;
        }else{
            overCircle = null;
        }
    }
    // set the cursor.
    canvas.style.cursor = cursor;
}

作为一个工作示例。

    var canvas = document.createElement("canvas");
    canvas.style.border = "1px black solid";
    canvas.width = 512;
    canvas.height = 200;
    var ctx = canvas.getContext("2d");
    document.body.appendChild(canvas);

    function mainLoop(time){  // this is called 60 times a second if there is no delay
        ctx.clearRect(0,0,canvas.width,canvas.height);        
        updateDisplay(); // call  the function that is rendering the display
        // get the next frame
        requestAnimationFrame(mainLoop);
    }
    // request the first frame. It will not start untill all the code below has been run
    requestAnimationFrame(mainLoop);

    
    var mouse = (function(){
        var bounds;
        var m = {x:0,y:0,button:false};
        function mouseEvent(event){
            bounds = event.currentTarget.getBoundingClientRect();
            m.x = event.pageX - bounds.left + scrollX;
            m.y = event.pageY - bounds.top + scrollY;
            if(event.type === "mousedown"){
                m.button = true;
            }else if(event.type === "mouseup"){
                m.button = false;
            }
        }
        m.start = function(element){
            ["mousemove","mousedown","mouseup"].forEach(eventType => element.addEventListener(eventType, mouseEvent)); 
        }
        return m;
    }())
    mouse.start(canvas);


    var circles = {
        items : [],
        drawCircle(){  // function for the circle
            ctx.beginPath();    
            ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
            ctx.stroke();
        },
        each(callback){  // iterator
            var i;
            for(i = 0; i < this.items.length; i++){
                callback(this.items[i],i);
            }
        },
        draw(){
            this.each(c => {
                c.draw();
            })
        },
        addCircle(x,y,radius){
            var circle = {x, y, radius, draw : this.drawCircle};
            this.items.push(circle);
            return circle;
        },
        getClosest(pos){
            var minDist, i, dist, x, y, foundCircle;
            minDist = Infinity;
            this.each(c =>{
                x = pos.x - c.x;
                y = pos.y - c.y;
                dist = Math.sqrt(x * x + y * y);
                if(dist <= c.radius){
                    if(foundCircle === undefined || (foundCircle && c.radius < foundCircle.radius)){
                        minDist = dist;
                        foundCircle = c;
                    }
                }
            })
            return foundCircle;
        }
    }
    var dragging = {
        started : false,
        type : null,
        currentObj : null,  // what we are dragging
        startX : 0,
        startY : 0,
        start(type, obj){
            this.startX = mouse.x;
            this.startY = mouse.y;
            this.started = true;
            this.type = type;
            this.currentObj = obj;
        }
        
    }
    var cursor = "default";
    var overCircle = null;
    function updateDisplay(){
        var x,y, c;
        cursor = "default"
        if(mouse.x >= 0 && mouse.x < canvas.width && mouse.y >= 0 && mouse.y < canvas.height){
            cursor = "crosshair";
        }
        if(mouse.button){  // the button is down
            if(!dragging.started){
                if(overCircle){
                    dragging.start("move",overCircle)
                    overCircle = null;

                }else{
                    dragging.start("create",circles.addCircle(mouse.x, mouse.y, 1));
                }
            }
            c = dragging.currentObj;
            if(dragging.type === "create"){
                x = c.x - mouse.x;
                y = c.y - mouse.y;
                c.radius = Math.sqrt(x * x + y * y);
            }else if(dragging.type === "move"){
                x = dragging.startX - mouse.x;
                y = dragging.startY - mouse.y;
                c.x -= x;
                c.y -= y;
                dragging.startX = mouse.x;
                dragging.startY = mouse.y;
            }
            cursor = "none";
        } else {  // button must be up
            if(dragging.started){ // have we been dragging something.
                dragging.started = false; // drop it
            }
        }
        ctx.strokeStyle = "black";
        circles.draw();
        
        if(!dragging.started){
            c = circles.getClosest(mouse);
            if(c !== undefined){
                cursor = "move";
                ctx.strokeStyle = "red";
                ctx.fillStyle = "rgba(0,255,255,0.1)";
                c.draw();
                ctx.fill();
                overCircle = c;
            }else{
                overCircle = null;
            }
        }
        canvas.style.cursor = cursor;
    }
<div style="font-family:12px arial;">Click drag to create circle</div>

请注意,这是用ES6编写的,如果没有某种类型的编译器,它将无法在IE上运行。


非常详细的回答,但我仍然是个新手,我正在寻找最简单的答案。非常感谢您的工作! - default
1
@default 一切都取决于最终结果是什么。如果是一个更复杂的游戏,那么你可能需要一个像上面描述的更强大的渲染循环。但如果只是一个愚蠢的小动画,那么这可能会过度设计。 - Luke Kot-Zaniewski

2
你可以从注释和代码中看到正在发生的事情。基本上,你需要一个全局鼠标按键状态变量,让函数知道是否有鼠标按键被按下。我使用了mousemove事件来实际触发圆形的绘制。最后,你需要计算光标与圆心之间的距离小于半径,否则你可以从圆外拖动圆(我认为这不是你想要的)。

//keep track of previous x and y coordinates
var x0 = 200;
var y0 = 300;
//keep track of mouse key state
var down = false;

var x;
var y;

//draw the initial circle someplace random
var context = document.getElementById("canv").getContext("2d");
context.clearRect(0, 0, 600, 500);
context.arc(200,300, 75, 0, 2*Math.PI);
context.stroke();

document.addEventListener("mousedown", function()
  {
    //if mousedown event is logged and we are within the area of the circle then state of down is true
    if(getDistance(x, x0, y, y0) < 75) down = true;
  });

document.addEventListener("mouseup", function()
  {
    //if mouseup event is logged then down is now false
    down = false;
  });

document.getElementById("canv").addEventListener("mousemove", 
    function(event){
      x = event.clientX;
      y = event.clientY;
     //we need to be in "down" state in order for a redraw to be necessary
     if(down)
     {  
        //set the previous coordinates to the new coordinates we are drawing.
        x0 = x;
        y0 = y;
        
        //draw the darn thing
        context.beginPath();
        context.clearRect(0, 0, 600, 500);
        context.arc(x, y, 75, 0, 2*Math.PI);
        context.stroke();
     }
    }
);

function getDistance(x0, x1, y0, y1)
{
  return Math.sqrt(Math.pow(x1-x0,2) + Math.pow(y1-y0, 2));  
  
}
<canvas id="canv" name="canv" width="600" height="500" style="border:1px solid #000000;" onclick="">Your browser does not support the HTML5 canvas tag.</canvas>


1
这正是我一直在寻找的!非常感谢!PS:我从DOMContentLoaded中排除了事件监听器,因为我在脚本开始时绘制了自己的圆圈,并且它也有效。我做错了什么吗? - default
1
不,你的方法其实更好,所以我重构了我的答案……我只是不想引用尚未加载的DOM部分,所以我把它放在那个函数中以确保安全,结果证明这是不必要的。现在的额外好处是我们只需要一个全局上下文变量,而且不必每次调用mousemove事件时都重新实例化它。 - Luke Kot-Zaniewski

0
var offsetx,offsety,posx,posy,run;
  window.onmousedown=function(e){
 run=true;
 offsetx=e.clientX;
 offsety=e.clientY;

 };
 window.onmouseup=function(){
 run=false;
 };
window.onmousemove=function(e){
if(run){
 movedx=e.clientX-offsetx;//mouse moved this value
 movedy=e.clientY-offsety;
 posx+=movedx;
 posy+=movedyy;
 offsetx=e.clientX;
 offsety=e.clientY
 //todo draw at posx,posy
 }
 };
 //todo draw circle+set posx,posy to its position

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