如何平移画布?

13

我代码中有这些事件监听器

canvas.addEventListener('mousemove', onMouseMove, false);
canvas.addEventListener('mousedown', onMouseDown,false);
canvas.addEventListener('mouseup', onMouseUp, false);

这些函数将帮助我移动画布。我在 onLoad 中声明了一个名为 panisDownmousePosition 和前一个鼠标位置的变量。然后在初始化函数中,我将 panmousePospremousepos 设置为包含 0,0 的向量。

function draw() {
    context.translate(pan.getX(), pan.getY());
    topPerson.draw(context);
    console.log(pan);
}

function onMouseDown(event) {
    var x = event.offsetX;
    var y = event.offsetY;
    var mousePosition = new vector(event.offsetX, event.offsetY);

    previousMousePosition = mousePosition;

    isDown = true;

    console.log(previousMousePosition);
    console.log("onmousedown" + "X coords: " + x + ", Y coords: " + y);
}

function onMouseUp(event) {
    isDown = false;
}


function onMouseMove(event) {
    if (isDown) {
        console.log(event.offsetX);
        mousePosition = new vector(event.offsetX, event.offsetY);
        newMousePosition = mousePosition;
        console.log('mouseMove' + newMousePosition);

        var panX = newMousePosition.getX() - previousMousePosition.getX();
        var panY = newMousePosition.getY() - previousMousePosition.getY();
        console.log('onMouseMove:  ' + panX);
        pan = new vector(panX, panY);
        console.log('mouseMove' + pan);

    }
}

但它没有注册新的 pan 值,因此您可能会尝试拖动画布。我知道我的鼠标拖动事件有效,但它只是不能 pan

2个回答

20

这里是一段简单的(带注释)拖动代码示例:

它通过累积鼠标水平(和垂直)拖动的净量来工作,然后重新绘制所有内容,但偏移那些累计的水平和垂直距离。

示例代码和演示:

// canvas related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// account for scrolling
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(); }

// mouse drag related variables
var isDown=false;
var startX,startY;

// the accumulated horizontal(X) & vertical(Y) panning the user has done in total
var netPanningX=0;
var netPanningY=0;

// just for demo: display the accumulated panning
var $results=$('#results');

// draw the numbered horizontal & vertical reference lines
for(var x=0;x<100;x++){ ctx.fillText(x,x*20,ch/2); }
for(var y=-50;y<50;y++){ ctx.fillText(y,cw/2,y*20); }

// listen for 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 handleMouseDown(e){
  // tell the browser we're handling this event
  e.preventDefault();
  e.stopPropagation();

  // calc the starting mouse X,Y for the drag
  startX=parseInt(e.clientX-offsetX);
  startY=parseInt(e.clientY-offsetY);

  // set the isDragging flag
  isDown=true;
}

function handleMouseUp(e){
  // tell the browser we're handling this event
  e.preventDefault();
  e.stopPropagation();

  // clear the isDragging flag
  isDown=false;
}

function handleMouseOut(e){
  // tell the browser we're handling this event
  e.preventDefault();
  e.stopPropagation();

  // clear the isDragging flag
  isDown=false;
}

function handleMouseMove(e){

  // only do this code if the mouse is being dragged
  if(!isDown){return;}
  
  // tell the browser we're handling this event
  e.preventDefault();
  e.stopPropagation();

  // get the current mouse position
  mouseX=parseInt(e.clientX-offsetX);
  mouseY=parseInt(e.clientY-offsetY);

  // dx & dy are the distance the mouse has moved since
  // the last mousemove event
  var dx=mouseX-startX;
  var dy=mouseY-startY;

  // reset the vars for next mousemove
  startX=mouseX;
  startY=mouseY;

  // accumulate the net panning done
  netPanningX+=dx;
  netPanningY+=dy;
  $results.text('Net change in panning: x:'+netPanningX+'px, y:'+netPanningY+'px'); 

  // display the horizontal & vertical reference lines
  // The horizontal line is offset leftward or rightward by netPanningX
  // The vertical line is offset upward or downward by netPanningY
  ctx.clearRect(0,0,cw,ch);
  for(var x=-50;x<50;x++){ ctx.fillText(x,x*20+netPanningX,ch/2); }
  for(var y=-50;y<50;y++){ ctx.fillText(y,cw/2,y*20+netPanningY); }

}
body{ background-color: ivory; }
#canvas{border:1px solid red; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4 id=results>Drag the mouse to see net panning in x,y directions</h4>
<canvas id="canvas" width=300 height=150></canvas>


嘿@markE,我知道这个问题很旧了,但我想知道你能否回答一个简单的问题; 播放是如何实际工作的?可能只是因为我累了,但我看不出最后两行是如何导致内容滚动的。 - Jhecht
这个问题让我有些困扰,当我平移图像并使用getImageData()时,画布下的平移图像数据丢失了。对此有什么帮助吗? - Niranth Reddy

17

回答问题

您没有提供一些代码。具体来说,您正在创建每个事件的向量对象可能会在那里。(实际上,你不应该每次都创建一个新对象。只需创建一次并更新值即可)

我看到的是mouseMove事件不会更新之前的鼠标位置对象,因此您将只从上次鼠标按下开始移动。但是您可能希望如此。因此,没有代码,我不知道哪里有问题,因为给定的代码是可以的。

以下是我整个shabang的方法..

如何平移(和缩放)。

以下是使用鼠标进行平移和缩放的示例。它比标准平移和缩放要复杂一些,因为我添加了一些平滑处理以使其具有更好的交互感。

它的工作原理。

画布使用变换矩阵来转换点。这样做的效果是维护该矩阵。我称变换空间为实际空间。我还保持一个反转矩阵,用于将屏幕空间转换为实际空间。

演示的核心在于对象 displayTransform ,它保存矩阵,所有需要的单个值以及函数 update()(每帧调用), setHome()获取屏幕空间变换并将其应用于画布。用于清除屏幕。和 setTransform()这将画布设置为实际空间(缩放平移空间)

为了平滑运动,我有一个值的镜像 x,y,ox,oy,scale,rotate 。(ox,oy是原点x和y)(是的,旋转可以工作)每个这些变量都有一个delta前缀和一个chaser前缀。追逐者值追逐所需的值。您不应该触摸追逐者值。有两个值称为 drag accel (加速度的简称) drag (不是真正模拟的drag)是delta衰减的速度。 drag > 0.5的值会产生弹性响应。随着你接近一个,它会变得越来越有弹性。在1处,绑定将不会停止,在1以上,它是无法使用的。 "accel"是转换对鼠标移动的响应速度。低值为慢响应,0为完全无响应,而1为即时响应。尝试使用这些值找到您喜欢的值。

追逐者值逻辑的示例

var x = 100; // the value to be chased
var dx = 0; // the delta x or the change in x per frame
var cx = 0; // the chaser value. This value chases x;
var drag = 0.1;  // quick decay
var accel = 0.9; // quick follpw
// logic
dx += (x-cx)*accel; // get acceleration towards x
dx *= drag;          // apply the drag
cx += dx;           // change chaser by delta x.

转换坐标

如果您不知道物体在哪里,就没有必要拥有一个缩放、平移和旋转的画布。为此,我保留了一个反矩阵来将屏幕上的x和y坐标转换为实际空间中的x和y坐标。为方便起见,我每次更新都将鼠标转换为实际空间。如果您想将实际空间转换为屏幕空间,则只需执行以下操作:

var x; // real x coord (position in the zoom panned rotate space)
var y; // real y coord

// "this" is displayTransform
x -= this.cx;
y -= this.cy;    
// screenX and screen Y are the screen coordinates.
screenX = (x * this.matrix[0] + y * this.matrix[2])+this.cox;
screenY = (x * this.matrix[1] + y * this.matrix[3])+this.coy;
你可以在鼠标的末尾看到它 displayTransform.update,我在那里使用逆变换将鼠标屏幕坐标转换为实际坐标。然后在主更新循环中,我使用鼠标实际坐标来显示帮助文本。我让代码的用户创建一个函数来转换任何屏幕坐标(很容易,只需挤压将鼠标转换的部分)。 缩放 使用鼠标滚轮进行缩放。这会产生一些问题,并且通常您希望缩放以鼠标为中心。但是,变换实际上是相对于屏幕左上角的。为了解决这个问题,我还保留了一个原点x和y。这基本上漂浮着,直到需要滚轮缩放,然后设置为鼠标的实际位置,并将鼠标距离左上角的距离放置在变换x和y位置。然后只需增加或减小比例即可缩放。我将原点和偏移浮动(未设置追逐值)。这适用于当前的拖动和加速设置,但如果您注意到其他设置不起作用,则设置cx,cy,cox,coy值。(我已经在代码中添加了一个注释) 平移 使用左鼠标按钮进行平移。单击并拖动以平移。这很简单。我获取上次鼠标位置和新位置屏幕空间(由鼠标事件给出的坐标)之间的差异。这给我一个鼠标增量向量。我将增量鼠标向量转换为实际空间,并从左上角坐标 displayTransform.xdisplayTransform.y 中减去该向量。就这样,我让chaser x和y平滑处理它所有。
代码片段只显示可以平移和缩放的大图像。我检查完整标志而不是使用onload。在图像加载时,片段将仅显示加载。主循环使用requestAnimationFrame刷新,首先更新displayTransform然后在home空间(屏幕空间)中清除画布,然后在实际空间中显示图像。像往常一样,我会与时间战斗,因此将根据时间允许返回添加更多注释,也许还有一两个函数。
如果您发现chase变量有些太多,请只需删除它们,并将所有带前缀的c变量替换为未带前缀的变量。
好了,希望对您有所帮助。还没有完成,需要清理,但需要做一些真正的工作一段时间。
var canvas = document.getElementById("canV"); 
var ctx = canvas.getContext("2d");
var mouse = {
    x : 0,
    y : 0,
    w : 0,
    alt : false,
    shift : false,
    ctrl : false,
    buttonLastRaw : 0, // user modified value
    buttonRaw : 0,
    over : false,
    buttons : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
};
function mouseMove(event) {
    mouse.x = event.offsetX;
    mouse.y = event.offsetY;
    if (mouse.x === undefined) {
        mouse.x = event.clientX;
        mouse.y = event.clientY;
    }
    mouse.alt = event.altKey;
    mouse.shift = event.shiftKey;
    mouse.ctrl = event.ctrlKey;
    if (event.type === "mousedown") {
        event.preventDefault()
        mouse.buttonRaw |= mouse.buttons[event.which-1];
    } else if (event.type === "mouseup") {
        mouse.buttonRaw &= mouse.buttons[event.which + 2];
    } else if (event.type === "mouseout") {
        mouse.buttonRaw = 0;
        mouse.over = false;
    } else if (event.type === "mouseover") {
        mouse.over = true;
    } else if (event.type === "mousewheel") {
        event.preventDefault()
        mouse.w = event.wheelDelta;
    } else if (event.type === "DOMMouseScroll") { // FF you pedantic doffus
       mouse.w = -event.detail;
    }
  

}

function setupMouse(e) {
    e.addEventListener('mousemove', mouseMove);
    e.addEventListener('mousedown', mouseMove);
    e.addEventListener('mouseup', mouseMove);
    e.addEventListener('mouseout', mouseMove);
    e.addEventListener('mouseover', mouseMove);
    e.addEventListener('mousewheel', mouseMove);
    e.addEventListener('DOMMouseScroll', mouseMove); // fire fox
    
    e.addEventListener("contextmenu", function (e) {
        e.preventDefault();
    }, false);
}
setupMouse(canvas);


// terms.
// Real space, real, r (prefix) refers to the transformed canvas space.
// c (prefix), chase is the value that chases a requiered value
var displayTransform = {
    x:0,
    y:0,
    ox:0,
    oy:0,
    scale:1,
    rotate:0,
    cx:0,  // chase values Hold the actual display
    cy:0,
    cox:0,
    coy:0,
    cscale:1,
    crotate:0,
    dx:0,  // deltat values
    dy:0,
    dox:0,
    doy:0,
    dscale:1,
    drotate:0,
    drag:0.1,  // drag for movements
    accel:0.7, // acceleration
    matrix:[0,0,0,0,0,0], // main matrix
    invMatrix:[0,0,0,0,0,0], // invers matrix;
    mouseX:0,
    mouseY:0,
    ctx:ctx,
    setTransform:function(){
        var m = this.matrix;
        var i = 0;
        this.ctx.setTransform(m[i++],m[i++],m[i++],m[i++],m[i++],m[i++]);
    },
    setHome:function(){
        this.ctx.setTransform(1,0,0,1,0,0);
        
    },
    update:function(){
        // smooth all movement out. drag and accel control how this moves
        // acceleration 
        this.dx += (this.x-this.cx)*this.accel;
        this.dy += (this.y-this.cy)*this.accel;
        this.dox += (this.ox-this.cox)*this.accel;
        this.doy += (this.oy-this.coy)*this.accel;
        this.dscale += (this.scale-this.cscale)*this.accel;
        this.drotate += (this.rotate-this.crotate)*this.accel;
        // drag
        this.dx *= this.drag;
        this.dy *= this.drag;
        this.dox *= this.drag;
        this.doy *= this.drag;
        this.dscale *= this.drag;
        this.drotate *= this.drag;
        // set the chase values. Chase chases the requiered values
        this.cx += this.dx;
        this.cy += this.dy;
        this.cox += this.dox;
        this.coy += this.doy;
        this.cscale += this.dscale;
        this.crotate += this.drotate;
        
        // create the display matrix
        this.matrix[0] = Math.cos(this.crotate)*this.cscale;
        this.matrix[1] = Math.sin(this.crotate)*this.cscale;
        this.matrix[2] =  - this.matrix[1];
        this.matrix[3] = this.matrix[0];

        // set the coords relative to the origin
        this.matrix[4] = -(this.cx * this.matrix[0] + this.cy * this.matrix[2])+this.cox;
        this.matrix[5] = -(this.cx * this.matrix[1] + this.cy * this.matrix[3])+this.coy;        


        // create invers matrix
        var det = (this.matrix[0] * this.matrix[3] - this.matrix[1] * this.matrix[2]);
        this.invMatrix[0] = this.matrix[3] / det;
        this.invMatrix[1] =  - this.matrix[1] / det;
        this.invMatrix[2] =  - this.matrix[2] / det;
        this.invMatrix[3] = this.matrix[0] / det;
        
        // check for mouse. Do controls and get real position of mouse.
        if(mouse !== undefined){  // if there is a mouse get the real cavas coordinates of the mouse
            if(mouse.oldX !== undefined && (mouse.buttonRaw & 1)===1){ // check if panning (middle button)
                var mdx = mouse.x-mouse.oldX; // get the mouse movement
                var mdy = mouse.y-mouse.oldY;
                // get the movement in real space
                var mrx = (mdx * this.invMatrix[0] + mdy * this.invMatrix[2]);
                var mry = (mdx * this.invMatrix[1] + mdy * this.invMatrix[3]);   
                this.x -= mrx;
                this.y -= mry;
            }
            // do the zoom with mouse wheel
            if(mouse.w !== undefined && mouse.w !== 0){
                this.ox = mouse.x;
                this.oy = mouse.y;
                this.x = this.mouseX;
                this.y = this.mouseY;
                /* Special note from answer */
                // comment out the following is you change drag and accel
                // and the zoom does not feel right (lagging and not 
                // zooming around the mouse 
                /*
                this.cox = mouse.x;
                this.coy = mouse.y;
                this.cx = this.mouseX;
                this.cy = this.mouseY;
                */
                if(mouse.w > 0){ // zoom in
                    this.scale *= 1.1;
                    mouse.w -= 20;
                    if(mouse.w < 0){
                        mouse.w = 0;
                    }
                }
                if(mouse.w < 0){ // zoom out
                    this.scale *= 1/1.1;
                    mouse.w += 20;
                    if(mouse.w > 0){
                        mouse.w = 0;
                    }
                }

            }
            // get the real mouse position 
            var screenX = (mouse.x - this.cox);
            var screenY = (mouse.y - this.coy);
            this.mouseX = this.cx + (screenX * this.invMatrix[0] + screenY * this.invMatrix[2]);
            this.mouseY = this.cy + (screenX * this.invMatrix[1] + screenY * this.invMatrix[3]);            
            mouse.rx = this.mouseX;  // add the coordinates to the mouse. r is for real
            mouse.ry = this.mouseY;
            // save old mouse position
            mouse.oldX = mouse.x;
            mouse.oldY = mouse.y;
        }
        
    }
}
// image to show
var img = new Image();
img.src = "https://upload.wikimedia.org/wikipedia/commons/e/e5/Fiat_500_in_Emilia-Romagna.jpg"
// set up font
ctx.font = "14px verdana";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
// timer for stuff
var timer =0;
function update(){
    timer += 1; // update timere
    // update the transform
    displayTransform.update();
    // set home transform to clear the screem
    displayTransform.setHome();
    ctx.clearRect(0,0,canvas.width,canvas.height);
    // if the image loaded show it
    if(img.complete){
        displayTransform.setTransform();
        ctx.drawImage(img,0,0);
        ctx.fillStyle = "white";
        if(Math.floor(timer/100)%2 === 0){
            ctx.fillText("Left but to pan",mouse.rx,mouse.ry);
        }else{
            ctx.fillText("Wheel to zoom",mouse.rx,mouse.ry);
        }
    }else{
        // waiting for image to load
        displayTransform.setTransform();
        ctx.fillText("Loading image...",100,100);
        
    }
    if(mouse.buttonRaw === 4){ // right click to return to homw
         displayTransform.x = 0;
         displayTransform.y = 0;
         displayTransform.scale = 1;
         displayTransform.rotate = 0;
         displayTransform.ox = 0;
         displayTransform.oy = 0;
     }
    // reaquest next frame
    requestAnimationFrame(update);
}
update(); // start it happening
.canC { width:400px;  height:400px;}
div {
  font-size:x-small;
}
<div>Wait for image to load and use <b>left click</b> drag to pan, and <b>mouse wheel</b> to zoom in and out. <b>Right click</b> to return to home scale and pan. Image is 4000 by 2000 plus so give it time if you have a slow conection. Not the tha help text follows the mouse in real space. Image from wiki commons</div>
<canvas class="canC" id="canV" width=400 height=400></canvas>


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