我尝试在代表3个磁盘的 Canvas 上实现不错的拖放。

我想用鼠标改变每个块的位置。我的主要问题是这三个球体中每个球体的轴长受到限制。

目前,当鼠标在 Canvas 内移动时,我已经实现了以下功能(indexMass的值指示要移动的质量:1, 2 or 3t1, t2, t3分别表示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);

    }

}

如您所见,我对每个 Angular 都做了:
t = t + 0.1*Math.atan(y/x);

和 :
 var x = event.offsetX;
 var y = event.offsetY;

但是这种效果不是很好。一旦用鼠标(单击鼠标)选择了球体,当我不再在球体上时,我希望光标卡在该球体上,或者希望球体遵循鼠标坐标的“delta”。

总而言之,我不知道如何创建精细且用户友好的拖放,如果有人可以帮助我或给我一些建议,那就太好了。

谢谢

更新1

@ Blindman67:感谢您的帮助,您的代码段对我来说非常复杂,我一无所知。但是我走对了。

我从第一个问题开始:拖动鼠标时,使选定的磁盘非常靠近或上方旋转来旋转选定的磁盘。

目前,我已经修改了函数myMove(当我单击并移动鼠标进行拖动时会调用),如下所示:
// 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');

    }

}

其中indexMass是已拖动磁盘的索引,window['x'+indexMass]window['y'+indexMass]是所选磁盘中心的当前坐标。

之后,我分别从开始拖动时鼠标单击的坐标(dx, dy返回的mx, my)和移动的鼠标坐标分别计算出getMousePos function

最后,我针对全局变量(所选磁盘的θ)通过设置更改磁盘的 Angular ,即window['t'+indexMass]:
// Change angle when dragging
window['t'+indexMass] = Math.atan2(dy, dx);

我已经使用Math.atan2处理了您的部分代码。

但是此功能的结果不能通过鼠标拖动产生良好的动画效果,我想知道它的来源。

现在,我只想执行拖动操作而不修改轴的长度,稍后我会看到更多有关此功能的信息。

更新2

我一直在寻找有关使用鼠标拖动选定质量的解决方案。

为了尝试综合我之前所做的工作,我相信以下方法不错,但是此拖动方法效果不佳:所选磁盘无法正确跟随鼠标,我也不知道为什么。

myMove function(开始拖动时调用的函数)中,我决定:
  • 计算鼠标坐标与所选磁盘坐标之间的dx,dy,即:

  • var dx = parseInt(event.offsetX-window ['x'+ indexMass]);

    var dy = parseInt(event.offsetY-window ['y'+ indexMass]);
    indexMass代表所选磁盘的索引。
  • 通过tmpX, tmpY增加所选磁盘(存储在临时变量dx, dy中)的位置。
  • 计算新 Angular theta(在代码中由全局变量window['t'+indexMass]标识
  • 使用此新值theta计算选定磁盘的新位置,例如,使用disk1(indexMass=1和theta = t1):
    x1= x0 +l1 * sin(t1)
    y1= y0 +l1 * sin(t1)
    

  • 我必须使您注意,我要用鼠标拖动而不用鼠标修改轴的长度,这是一个约束。

    这是整个myMove function(在拖动开始时调用):
    // 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');
    
       }
    }
    

    更新4-赏金:

    问题解决了 !我忘记考虑“indexMass-1”磁盘的位置来使用Math.atan2函数来计算新 Angular 。

    最佳答案

    您无法移动OS鼠标的位置。您可以隐藏鼠标canvas.style.cursor = "none";,然后在自己的 Canvas 上绘制鼠标,但是它会落后一帧,因为当您获得鼠标坐标时,操作系统已经将鼠标放置在该位置,并且如果您使用requestAnimationFrame(RAF) Canvas 的下一个显示将在下一个显示刷新间隔。如果不使用RAF,则可能会或可能不会在当前显示刷新中显示 Canvas ,但是偶尔会出现闪烁和剪切。

    为了解决该问题(是主观的),请从旋转点穿过球到鼠标位置画一条线,这至少会给用户一些有关正在发生的事情的反馈。

    我还将在球上添加一些 handle ,以便可以更改质量(球体的体积*密度)和轴的长度。.调整大小的游标是一个问题,因为当 Angular 发生变化时,游标的大小将与所需移动的方向不匹配。您需要找到最接近正确 Angular 一个,或将光标渲染到 Canvas 上并使用它。

    示例代码显示了我的意思。 (不包括SIM卡)将鼠标移到球上进行移动,移到球上时,您还会看到两个圆圈似乎在改变距离和半径(质量)

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

    10-04 19:24