Как панорамировать холст?

У меня есть эти слушатели событий в моем коде

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

Эти функции помогут мне панорамировать холст. Я объявил переменную в onLoad называется pan, isDown, mousePostion и предыдущие позиции мыши. Затем в функции инициализации устанавливается pan,mousePos а также premousepos к векторам, содержащим 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 ответа

Вот простой (аннотированный) пример кода панорамирования

Он работает, накапливая чистую сумму, которую мышь перетаскивает по горизонтали (и по вертикали). Затем он перерисовывает все, но компенсирует эти накопленные горизонтальные и вертикальные расстояния.

Пример кода и демонстрация:

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

Ответить на вопрос

Вы не предоставили часть кода. В частности, векторный объект, который вы создаете для каждого события, может быть там. (на самом деле вы не должны создавать новый объект каждый раз. Создайте один раз и обновите значения)

Что я вижу, так это то, что события mouseMove не обновляют предыдущий объект положения мыши, поэтому вы будете получать панорамирование только от последней нажатия мыши. Но вы можете этого хотеть. Поэтому без кода я не знаю, что не так, поскольку приведенный код в порядке.

Ниже, как я делаю весь шабанг..

Как панорамировать (и масштабировать).

Ниже приведен пример панорамирования и масштабирования с помощью мыши. Это немного сложнее, чем стандартное панорамирование и масштабирование, потому что я добавил некоторое сглаживание для панорамирования и масштабирования, чтобы улучшить интерактивность.

Как это устроено.

Холст использует матрицу преобразования для преобразования точек. Что это делает, так это поддерживать эту матрицу. Я называю преобразованное пространство реальным пространством. Я также поддерживаю обратную матрицу, которая используется для преобразования из пространства экрана в реальное пространство.

Суть демонстрации - вокруг объекта displayTransform он содержит матрицу, все необходимые индивидуальные значения и функции update() вызвать один раз кадр, setHome() получить преобразование пространства экрана и применить его к холсту. Используется для очистки экрана. А также setTransform() это устанавливает холст в реальное пространство (увеличенное пространство панорамирования)

Для сглаживания движений у меня есть зеркало значений x, y, ox, oy, scale, а также rotate, ((ox,oy) являются источниками x и y) (и yes rotate works) каждая из этих переменных имеет дельту с префиксом d и преследователь с префиксом c. Значения чейза преследуют требуемые значения. Вы не должны касаться значений Chaser. Есть два значения, называемых drag а также accel (сокращение от ускорения) drag (не реальное моделируемое перетаскивание) - как быстро распадаются дельты. Значения для drag > 0,5 приведет к оживлённому ответу. По мере приближения к нему оно будет становиться все более и более бодрым. На 1 граница не остановится, выше 1, и это непригодно для использования. "Accel" - это то, как быстро преобразование реагирует на движение мыши. Низкие значения - это медленный ответ, 0 - это вообще отсутствие ответа, а одно - мгновенный ответ. Поиграйте со значениями, чтобы найти то, что вам нравится.

Пример логики для значений Chaser

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.x а также displayTransform.y, Вот и все, что я позволил преследователю х и у все сгладить.

Фрагмент просто отображает большое изображение, которое можно панорамировать и масштабировать. Я проверяю полный флаг, а не использую onload. Во время загрузки изображения фрагмент будет отображать загрузку. Основной цикл обновляется с помощью requestAnimationFrame, сначала я обновляю displayTransform затем холст очищается в домашнем пространстве (экранное пространство), а затем изображение отображается в реальном пространстве. Как всегда, у меня будет время борьбы, поэтому я вернусь, если позволит время, чтобы добавить больше комментариев и, возможно, функцию или два.

Если вы находите переменные погони чуть-чуть, вы можете просто удалить их и заменить все префиксы 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>

Другие вопросы по тегам