2010-05-26 2 views
119

Я хочу, чтобы иметь возможность приближать точку под мышью в холсте HTML 5, например масштабирование на Google Maps. Как я могу это достичь?Увеличение масштаба (используя масштаб и перевод)

+2

Я использовал это для масштабирования моего холста, и он отлично работает! Единственное, что я должен добавить, это то, что вычисление суммы масштабирования не так, как вы ожидали. "var zoom = 1 + wheel/2;" т. е. это приводит к 1,5 для масштабирования и 0,5 для масштабирования. Я отредактировал это в своей версии, чтобы у меня было 1,5 для увеличения и 1/1.5 для масштабирования, что делает масштаб масштабирования и масштабирования равным. Поэтому, если вы увеличиваете масштаб и увеличиваете масштаб изображения, у вас будет то же изображение, что и до масштабирования. – Chris

+0

Святые шники! Это действительно работает! – sneilan

+2

Обратите внимание, что это не работает в Firefox, но этот метод можно легко применить к [jQuery mousewheel plugin] (http://plugins.jquery.com/project/mousewheel). Спасибо, что поделился! – johndodo

ответ

43

Наконец решил его:

var zoomIntensity = 0.2; 
 

 
var canvas = document.getElementById("canvas"); 
 
var context = canvas.getContext("2d"); 
 
var width = 600; 
 
var height = 200; 
 

 
var scale = 1; 
 
var originx = 0; 
 
var originy = 0; 
 
var visibleWidth = width; 
 
var visibleHeight = height; 
 

 

 
function draw(){ 
 
    // Clear screen to white. 
 
    context.fillStyle = "white"; 
 
    context.fillRect(originx,originy,800/scale,600/scale); 
 
    // Draw the black square. 
 
    context.fillStyle = "black"; 
 
    context.fillRect(50,50,100,100); 
 
} 
 
// Draw loop at 60FPS. 
 
setInterval(draw, 1000/60); 
 

 
canvas.onmousewheel = function (event){ 
 
    event.preventDefault(); 
 
    // Get mouse offset. 
 
    var mousex = event.clientX - canvas.offsetLeft; 
 
    var mousey = event.clientY - canvas.offsetTop; 
 
    // Normalize wheel to +1 or -1. 
 
    var wheel = event.wheelDelta/120; 
 

 
    // Compute zoom factor. 
 
    var zoom = Math.exp(wheel*zoomIntensity); 
 
    
 
    // Translate so the visible origin is at the context's origin. 
 
    context.translate(originx, originy); 
 
    
 
    // Compute the new visible origin. Originally the mouse is at a 
 
    // distance mouse/scale from the corner, we want the point under 
 
    // the mouse to remain in the same place after the zoom, but this 
 
    // is at mouse/new_scale away from the corner. Therefore we need to 
 
    // shift the origin (coordinates of the corner) to account for this. 
 
    originx -= mousex/(scale*zoom) - mousex/scale; 
 
    originy -= mousey/(scale*zoom) - mousey/scale; 
 
    
 
    // Scale it (centered around the origin due to the trasnslate above). 
 
    context.scale(zoom, zoom); 
 
    // Offset the visible origin to it's proper position. 
 
    context.translate(-originx, -originy); 
 

 
    // Update scale and others. 
 
    scale *= zoom; 
 
    visibleWidth = width/scale; 
 
    visibleHeight = height/scale; 
 
}
<canvas id="canvas" width="600" height="200"></canvas>

Ключ, как @Tatarize указал, чтобы вычислить положение оси таким образом, что точка масштабирования (указатель мыши) остается в том же месте после увеличения.

Первоначально мышь находится на расстоянии mouse/scale от угла, мы хотим, чтобы точка под мышью оставалась на том же месте после зума, но это mouse/new_scale в стороне от угла. Поэтому для этого необходимо сдвинуть origin (координаты угла).

originx -= mousex/(scale*zoom) - mousex/scale; 
originy -= mousey/(scale*zoom) - mousey/scale; 
scale *= zomm 

Остальной код, то необходимо применить масштабирование и перевести к контексту дро, так что это начало совпадает с брезентовой углу.

26

Это на самом деле очень сложная проблема (математически), и я работаю над одним и тем же почти. Я задал аналогичный вопрос в Stackoverflow, но не получил ответа, но отправлен в DocType (StackOverflow для HTML/CSS) и получил ответ. Проверьте это http://doctype.com/javascript-image-zoom-css3-transforms-calculate-origin-example

Я нахожусь в центре создания плагина jQuery, который делает это (масштабирование в стиле Google Maps с использованием CSS3 Transforms). У меня есть стрелка увеличения до мыши, работающая нормально, все еще пытаясь понять, как разрешить пользователю перетаскивать холст, как вы можете делать в Google Maps. Когда я получу его работу, я отправлю код здесь, но ознакомьтесь с ссылкой выше для части «мышь-зум-точка».

Я не понимал, что существует масштаб и перевод методов в контексте Canvas, вы можете добиться того же, используя CSS3, например. с помощью JQuery:

$('div.canvasContainer > canvas') 
    .css('-moz-transform', 'scale(1) translate(0px, 0px)') 
    .css('-webkit-transform', 'scale(1) translate(0px, 0px)') 
    .css('-o-transform', 'scale(1) translate(0px, 0px)') 
    .css('transform', 'scale(1) translate(0px, 0px)'); 

Убедитесь, что вы установили CSS3 преобразования происхождения 0, 0 (-moz-преобразование-происхождения: 0 0). Используя преобразование CSS3, вы можете увеличить что-либо, просто убедитесь, что контейнер DIV установлен на переполнение: скрытый, чтобы остановить увеличенные края, выливающиеся из сторон.

Независимо от того, используете ли вы преобразования CSS3, или собственный масштаб и методы перевода холста, но проверьте приведенную выше ссылку на вычисления.


Update: Мех! Я просто разместить код здесь, а не заставить вас следовать ссылке:

$(document).ready(function() 
{ 
    var scale = 1; // scale of the image 
    var xLast = 0; // last x location on the screen 
    var yLast = 0; // last y location on the screen 
    var xImage = 0; // last x location on the image 
    var yImage = 0; // last y location on the image 

    // if mousewheel is moved 
    $("#mosaicContainer").mousewheel(function(e, delta) 
    { 
     // find current location on screen 
     var xScreen = e.pageX - $(this).offset().left; 
     var yScreen = e.pageY - $(this).offset().top; 

     // find current location on the image at the current scale 
     xImage = xImage + ((xScreen - xLast)/scale); 
     yImage = yImage + ((yScreen - yLast)/scale); 

     // determine the new scale 
     if (delta > 0) 
     { 
      scale *= 2; 
     } 
     else 
     { 
      scale /= 2; 
     } 
     scale = scale < 1 ? 1 : (scale > 64 ? 64 : scale); 

     // determine the location on the screen at the new scale 
     var xNew = (xScreen - xImage)/scale; 
     var yNew = (yScreen - yImage)/scale; 

     // save the current screen location 
     xLast = xScreen; 
     yLast = yScreen; 

     // redraw 
     $(this).find('div').css('-moz-transform', 'scale(' + scale + ')' + 'translate(' + xNew + 'px, ' + yNew + 'px' + ')') 
          .css('-moz-transform-origin', xImage + 'px ' + yImage + 'px') 
     return false; 
    }); 
}); 

Вы, конечно, нужно адаптировать его использовать масштаб холста и перевести методы.


Update 2: Просто заметил я, используя преобразование происхождения вместе с переводом. Мне удалось реализовать версию, которая просто использует масштаб и переводить самостоятельно, проверьте здесь http://www.dominicpettifer.co.uk/Files/Mosaic/MosaicTest.html Дождитесь загрузки изображений, затем используйте колесо мыши для увеличения, а также поддерживайте панорамирование, перемещая изображение вокруг. Он использует CSS3 Transforms, но вы можете использовать те же вычисления для своего Canvas.

+0

Я, наконец, решил это, взял меня через 3 минуты после примерно двухнедельных занятий чем-то еще – csiz

+0

Привет @SundayIronfoot, не могли бы вы снова положить свой код! Благодаря!! – chemitaxis

+0

@Synday Ironfoot ссылка на его обновление не работает. Эта ссылка: http://www.dominicpettifer.co.uk/Files/Mosaic/MosaicTest.html Я хочу это имплицирование. Можете ли вы разместить здесь код? спасибо – Bogz

3

Я хочу разместить здесь некоторую информацию для тех, кто делает отдельный рисунок изображения и перемещает -измеряет его.

Это может быть полезно, если вы хотите сохранить масштабирование и положение окна просмотра.

Вот ящик:

function redraw_ctx(){ 
    self.ctx.clearRect(0,0,canvas_width, canvas_height) 
    self.ctx.save() 
    self.ctx.scale(self.data.zoom, self.data.zoom) // 
    self.ctx.translate(self.data.position.left, self.data.position.top) // position second 
    // Here We draw useful scene My task - image: 
    self.ctx.drawImage(self.img ,0,0) // position 0,0 - we already prepared 
    self.ctx.restore(); // Restore!!! 
} 

Примечание Шкала ДОЛЖЕН быть первым.

А вот Zoomer:

function zoom(zf, px, py){ 
    // zf - is a zoom factor, which in my case was one of (0.1, -0.1) 
    // px, py coordinates - is point within canvas 
    // eg. px = evt.clientX - canvas.offset().left 
    // py = evt.clientY - canvas.offset().top 
    var z = self.data.zoom; 
    var x = self.data.position.left; 
    var y = self.data.position.top; 

    var nz = z + zf; // getting new zoom 
    var K = (z*z + z*zf) // putting some magic 

    var nx = x - ((px*zf)/K); 
    var ny = y - ((py*zf)/K); 

    self.data.position.left = nx; // renew positions 
    self.data.position.top = ny; 
    self.data.zoom = nz; // ... and zoom 
    self.redraw_ctx(); // redraw context 
    } 

и, конечно же, нам потребуется Dragger:

this.my_cont.mousemove(function(evt){ 
    if (is_drag){ 
     var cur_pos = {x: evt.clientX - off.left, 
         y: evt.clientY - off.top} 
     var diff = {x: cur_pos.x - old_pos.x, 
        y: cur_pos.y - old_pos.y} 

     self.data.position.left += (diff.x/self.data.zoom); // we want to move the point of cursor strictly 
     self.data.position.top += (diff.y/self.data.zoom); 

     old_pos = cur_pos; 
     self.redraw_ctx(); 

    } 


}) 
7

Я столкнулся с этой проблемой с помощью C++, который я, вероятно, не имел Я просто использовал макеты OpenGL, чтобы начать с ... в любом случае, если вы используете элемент управления, начало которого - верхний левый угол, и вы хотите, чтобы панорамирование/масштабирование, например, карты google, вот макет (используя allegro в качестве моего обработчика событий):

// initialize 
double originx = 0; // or whatever its base offset is 
double originy = 0; // or whatever its base offset is 
double zoom = 1; 

. 
. 
. 

main(){ 

    // ...set up your window with whatever 
    // tool you want, load resources, etc 

    . 
    . 
    . 
    while (running){ 
     /* Pan */ 
     /* Left button scrolls. */ 
     if (mouse == 1) { 
      // get the translation (in window coordinates) 
      double scroll_x = event.mouse.dx; // (x2-x1) 
      double scroll_y = event.mouse.dy; // (y2-y1) 

      // Translate the origin of the element (in window coordinates)  
      originx += scroll_x; 
      originy += scroll_y; 
     } 

     /* Zoom */ 
     /* Mouse wheel zooms */ 
     if (event.mouse.dz!=0){  
      // Get the position of the mouse with respect to 
      // the origin of the map (or image or whatever). 
      // Let us call these the map coordinates 
      double mouse_x = event.mouse.x - originx; 
      double mouse_y = event.mouse.y - originy; 

      lastzoom = zoom; 

      // your zoom function 
      zoom += event.mouse.dz * 0.3 * zoom; 

      // Get the position of the mouse 
      // in map coordinates after scaling 
      double newx = mouse_x * (zoom/lastzoom); 
      double newy = mouse_y * (zoom/lastzoom); 

      // reverse the translation caused by scaling 
      originx += mouse_x - newx; 
      originy += mouse_y - newy; 
     } 
    } 
} 

. 
. 
. 

draw(originx,originy,zoom){ 
    // NOTE:The following is pseudocode 
    //   the point is that this method applies so long as 
    //   your object scales around its top-left corner 
    //   when you multiply it by zoom without applying a translation. 

    // draw your object by first scaling... 
    object.width = object.width * zoom; 
    object.height = object.height * zoom; 

    // then translating... 
    object.X = originx; 
    object.Y = originy; 
} 
3

Вот альтернативный способ сделать это, который использует setTransform() вместо scale() и translate(). Все хранится в одном объекте. Предполагается, что холст находится на уровне 0,0 на странице, иначе вам нужно будет вычесть его позицию из кодовых страниц.

this.zoomIn = function (pageX, pageY) { 
    var zoomFactor = 1.1; 
    this.scale = this.scale * zoomFactor; 
    this.lastTranslation = { 
     x: pageX - (pageX - this.lastTranslation.x) * zoomFactor, 
     y: pageY - (pageY - this.lastTranslation.y) * zoomFactor 
    }; 
    this.canvasContext.setTransform(this.scale, 0, 0, this.scale, 
            this.lastTranslation.x, 
            this.lastTranslation.y); 
}; 
this.zoomOut = function (pageX, pageY) { 
    var zoomFactor = 1.1; 
    this.scale = this.scale/zoomFactor; 
    this.lastTranslation = { 
     x: pageX - (pageX - this.lastTranslation.x)/zoomFactor, 
     y: pageY - (pageY - this.lastTranslation.y)/zoomFactor 
    }; 
    this.canvasContext.setTransform(this.scale, 0, 0, this.scale, 
            this.lastTranslation.x, 
            this.lastTranslation.y); 
}; 

Сопровождающего кода для обработки панорамирования:

this.startPan = function (pageX, pageY) { 
    this.startTranslation = { 
     x: pageX - this.lastTranslation.x, 
     y: pageY - this.lastTranslation.y 
    }; 
}; 
this.continuePan = function (pageX, pageY) { 
    var newTranslation = {x: pageX - this.startTranslation.x, 
          y: pageY - this.startTranslation.y}; 
    this.canvasContext.setTransform(this.scale, 0, 0, this.scale, 
            newTranslation.x, newTranslation.y); 
}; 
this.endPan = function (pageX, pageY) { 
    this.lastTranslation = { 
     x: pageX - this.startTranslation.x, 
     y: pageY - this.startTranslation.y 
    }; 
}; 

Для получения ответа на вызов себе, считаю, что один и та же координата страницы должна соответствовать тем же холсту координаты до и после увеличения. Тогда вы можете сделать некоторые алгебры, начиная с этого уравнения:

(pageCoords - перевод)/Scale = canvasCoords

2
if(wheel > 0) { 
    this.scale *= 1.1; 
    this.offsetX -= (mouseX - this.offsetX) * (1.1 - 1); 
    this.offsetY -= (mouseY - this.offsetY) * (1.1 - 1); 
} 
else { 
    this.scale *= 1/1.1; 
    this.offsetX -= (mouseX - this.offsetX) * (1/1.1 - 1); 
    this.offsetY -= (mouseY - this.offsetY) * (1/1.1 - 1); 
} 
82

Лучшим решением будет просто переместить позицию окна просмотра на основе изменения масштаба , Точка масштабирования - это просто точка старого масштабирования и новый масштаб, который вы хотите оставить неизменным. То есть предварительный просмотр в окне просмотра и увеличенное изображение в окне просмотра имеют одинаковое значение по сравнению с видовым экраном. Учитывая, что мы масштабируемся относительно происхождения. Вы можете отрегулировать положение видового соответственно:

scalechange = newscale - oldscale; 
offsetX = -(zoomPointX * scalechange); 
offsetY = -(zoomPointY * scalechange); 

Так на самом деле вы можете просто перемещаться над вниз и вправо при увеличении масштаба на коэффициент, сколько вы увеличены, по сравнению с точкой вы увеличенная на.

enter image description here

+9

Это лучшее ответьте здесь. Жаль, что все более высокие ответы содержат огромное количество материалов, специфичных для библиотеки. – Jehan

+2

Более ценным, чем вырезать и вставлять код, является объяснение того, что является лучшим решением и почему оно работает без багажа, особенно если оно длино в три строки. – Tatarize

+1

scalechange = newscale/oldscale? –

0

Вам нужно, чтобы получить точку в мировом пространстве (в отличие от пространства экрана) до и после масштабирования, а затем перевести дельту.

mouse_world_position = to_world_position(mouse_screen_position); 
zoom(); 
mouse_world_position_new = to_world_position(mouse_screen_position); 
translation += mouse_world_position_new - mouse_world_position; 

Позиция мыши находится в пространстве экрана, поэтому вы должны преобразовать ее в мировое пространство. Простой преобразующая должен быть похож на это:

world_position = screen_position/scale - translation 
5

Вот мое решение для центрально-ориентированного изображения:

var MIN_SCALE = 1; 
 
var MAX_SCALE = 5; 
 
var scale = MIN_SCALE; 
 

 
var offsetX = 0; 
 
var offsetY = 0; 
 

 
var $image  = $('#myImage'); 
 
var $container = $('#container'); 
 

 
var areaWidth = $container.width(); 
 
var areaHeight = $container.height(); 
 

 
$container.on('wheel', function(event) { 
 
    event.preventDefault(); 
 
    var clientX = event.originalEvent.pageX - $container.offset().left; 
 
    var clientY = event.originalEvent.pageY - $container.offset().top; 
 

 
    var nextScale = Math.min(MAX_SCALE, Math.max(MIN_SCALE, scale - event.originalEvent.deltaY/100)); 
 

 
    var percentXInCurrentBox = clientX/areaWidth; 
 
    var percentYInCurrentBox = clientY/areaHeight; 
 

 
    var currentBoxWidth = areaWidth/scale; 
 
    var currentBoxHeight = areaHeight/scale; 
 

 
    var nextBoxWidth = areaWidth/nextScale; 
 
    var nextBoxHeight = areaHeight/nextScale; 
 

 
    var deltaX = (nextBoxWidth - currentBoxWidth) * (percentXInCurrentBox - 0.5); 
 
    var deltaY = (nextBoxHeight - currentBoxHeight) * (percentYInCurrentBox - 0.5); 
 

 
    var nextOffsetX = offsetX - deltaX; 
 
    var nextOffsetY = offsetY - deltaY; 
 

 
    $image.css({ 
 
     transform : 'scale(' + nextScale + ')', 
 
     left  : -1 * nextOffsetX * nextScale, 
 
     right  : nextOffsetX * nextScale, 
 
     top  : -1 * nextOffsetY * nextScale, 
 
     bottom : nextOffsetY * nextScale 
 
    }); 
 

 
    offsetX = nextOffsetX; 
 
    offsetY = nextOffsetY; 
 
    scale = nextScale; 
 
});
body { 
 
    background-color: orange; 
 
} 
 
#container { 
 
    margin: 30px; 
 
    width: 500px; 
 
    height: 500px; 
 
    background-color: white; 
 
    position: relative; 
 
    overflow: hidden; 
 
} 
 
img { 
 
    position: absolute; 
 
    top: 0; 
 
    bottom: 0; 
 
    left: 0; 
 
    right: 0; 
 
    max-width: 100%; 
 
    max-height: 100%; 
 
    margin: auto; 
 
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> 
 

 
<div id="container"> 
 
    <img id="myImage" src="http://s18.postimg.org/eplac6dbd/mountain.jpg"> 
 
</div>

0

вы можете использовать scrollto (х, у) функция отрегулируйте положение полосы прокрутки вправо до точки, которую нужно отобразить после масштабирования. Для нахождения позиции мыши используйте event.clientX и event.clientY. this will help you

2

Вот реализация кода ответа @ tatarize с использованием PIXI.js. У меня есть окно просмотра, которое смотрит на часть очень большого изображения (например, стиль карт Google).

$canvasContainer.on('wheel', function (ev) { 

    var scaleDelta = 0.02; 
    var currentScale = imageContainer.scale.x; 
    var nextScale = currentScale + scaleDelta; 

    var offsetX = -(mousePosOnImage.x * scaleDelta); 
    var offsetY = -(mousePosOnImage.y * scaleDelta); 

    imageContainer.position.x += offsetX; 
    imageContainer.position.y += offsetY; 

    imageContainer.scale.set(nextScale); 

    renderer.render(stage); 
}); 
  • $canvasContainer мой HTML контейнер.
  • imageContainer - это мой контейнер PIXI, в котором есть изображение.
  • mousePosOnImage - это положение мыши относительно всего изображения (а не только порт представления).

Вот как я получил позицию мыши:

imageContainer.on('mousemove', _.bind(function(ev) { 
    mousePosOnImage = ev.data.getLocalPosition(imageContainer); 
    mousePosOnViewport.x = ev.data.originalEvent.offsetX; 
    mousePosOnViewport.y = ev.data.originalEvent.offsetY; 
    },self)); 
+0

Не совсем прямой ответ на вопрос, но это то, что мне нужно. Спасибо. – newguy

5

мне нравится ответ Tatarize, но я предоставлю альтернативу. Это тривиальная проблема линейной алгебры, и предлагаемый мной метод хорошо работает с панорамированием, масштабированием, перекосом и т. Д. То есть он хорошо работает, если ваш образ уже преобразован.

При масштабировании матрицы шкала находится в точке (0, 0). Итак, если у вас есть изображение и масштабируйте его в 2 раза, нижняя правая точка будет удвоена как в направлениях x, так и в направлении y (используя соглашение, которое [0, 0] является левым верхним краем изображения).

Если вместо этого вы хотите увеличить изображение по центру, то решение будет следующим: (1) перевести изображение таким образом, чтобы его центр находился в (0, 0); (2) масштабировать изображение по х и у факторам; (3) перевести изображение назад. то есть

myMatrix 
    .translate(image.width/2, image.height/2) // 3 
    .scale(xFactor, yFactor)       // 2 
    .translate(-image.width/2, -image.height/2); // 1 

Более абстрактно, та же стратегия работает для любой точки. Если, например, вы хотите, чтобы масштабировать изображение в точке Р:

myMatrix 
    .translate(P.x, P.y) 
    .scale(xFactor, yFactor) 
    .translate(-P.x, -P.y); 

И, наконец, если изображение уже преобразуется каким-либо образом (например, если он вращается, искажено, переводиться или масштабируется) , то текущая трансформация должна быть сохранена. В частности, преобразование, определенное выше, должно быть после умножено (или умножено справа) на текущее преобразование.

myMatrix 
    .translate(P.x, P.y) 
    .scale(xFactor, yFactor) 
    .translate(-P.x, -P.y) 
    .multiply(myMatrix); 

У вас есть это. Вот план, который показывает это в действии. Прокрутите колесико мыши по точкам, и вы увидите, что они постоянно остаются на месте. (Протестировано только в Chrome.) http://plnkr.co/edit/3aqsWHPLlSXJ9JCcJzgH?p=preview

Смежные вопросы