【问题标题】:How to pan the canvas?如何平移画布?
【发布时间】:2015-11-27 08:09:08
【问题描述】:

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

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

这些函数将帮助我平移画布。我在onLoad 中声明了一个变量,名为panisDownmousePostion 和以前的鼠标位置。然后在初始化函数中将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

【问题讨论】:

    标签: javascript canvas


    【解决方案1】:

    这是一个简单(带注释)的平移代码示例

    它的工作原理是累积鼠标水平(和垂直)拖动的净量,然后重绘所有内容,但会被那些累积的水平和垂直距离抵消。

    示例代码和演示:

    // 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,我知道这个问题已经很老了,但我想知道你能否回答一个快速的问题;平移实际上是如何工作的?可能只是因为我累了,但我看不到最后两行实际上是如何导致内容平移
    • 这个让我开始了,但是在我平移图像并使用 getImageData() 之后,画布下的平移图像数据丢失了。这个有什么帮助吗?
    【解决方案2】:

    回答问题

    您尚未提供部分代码。具体来说,您正在创建每个事件的矢量对象可能在那里。 (真的你不应该每次都创建一个新对象。创建一次并更新值)

    我所看到的是 mouseMove 事件不会更新前一个鼠标位置对象,因此您只会从最后一个鼠标向下平移。但你可能想要那个。所以没有代码我不知道什么是错的,因为给出的代码是好的。

    下面是我如何做整个shabang..

    如何平移(和缩放)。

    以下是使用鼠标进行平移和缩放的示例。它比标准平移和缩放稍微复杂一点,这是因为我在平移和缩放中添加了一些平滑,以赋予它更好的交互感。

    它是如何工作的。

    画布使用变换矩阵来变换点。这样做是维护该矩阵。我称变形的空间,真实的空间。我还维护了一个逆矩阵,用于将屏幕空间转换为真实空间。

    演示的核心是对象displayTransform,它包含矩阵、所需的所有单个值,函数update()每帧调用一次,setHome() 获取屏幕空间变换并将其应用于帆布。用于清屏。而setTransform() 这会将画布设置为真实空间(缩放后的平移空间)

    为了平滑运动,我有一个值 x, y, ox, oy, scale,rotate 的镜像。 ((ox,oy)是原点x和y)(是的,旋转有效)这些变量中的每一个都有一个以d为前缀的增量和一个以c为前缀的追踪器。追逐者值追逐所需的值。你不应该触及追逐者的价值观。有两个值称为dragaccel(加速度的缩写)drag(不是真正的模拟阻力)是增量衰减的速度。 drag > 0.5 的值将导致有弹性的响应。当你接近一个时,它会变得越来越有弹性。在 1 时,边界不会停止,超过 1 并且它无法使用。 “加速”是变换响应鼠标移动的速度。低值表示响应慢,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。为方便起见,我每次更新都将鼠标转换为真实空间。如果你想反向 realSpace 到屏幕空间。那么它只是

    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 中减去它。就是这样,我让追逐者 x 和 y 把它弄平了。

    sn-p 只显示一个可以平移和缩放的大图像。我检查完整标志而不是使用 onload。在加载图像时,sn-p 只会显示加载。主循环用requestAnimationFrame刷新,首先我更新displayTransform,然后画布在家庭空间(屏幕空间)中被清除,然后图像在真实空间中显示。与往常一样,我是一个战斗时间,所以会在时间允许时返回以添加更多 cmets,并且可能会添加一两个函数。

    如果你发现追逐变量有点多,你可以删除它们并将所有带有 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>

    【讨论】:

      猜你喜欢
      • 2016-03-29
      • 2016-01-20
      • 2018-05-13
      • 1970-01-01
      • 2017-05-30
      • 2017-06-13
      • 2018-05-02
      • 1970-01-01
      • 2014-09-14
      相关资源
      最近更新 更多