Как панорамировать холст?
У меня есть эти слушатели событий в моем коде
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>