Увеличение точки (с помощью масштабирования и перевода)
Я хочу иметь возможность увеличивать масштаб точки под мышью на холсте HTML 5, например, масштабирование Google Maps. Как я могу этого достичь?
12 ответов:
лучшее решение-просто переместить положение видового экрана на основе изменения масштаба. Точка масштабирования-это просто точка в старом зуме и новом Зуме, которую вы хотите сохранить. То есть видовой экран с предварительным масштабированием и видовой экран с последующим масштабированием имеют одну и ту же точку масштабирования относительно видового экрана. Учитывая, что мы масштабируемся относительно начала координат. Вы можете настроить положение видового экрана соответственно:
scalechange = newscale - oldscale; offsetX = -(zoomPointX * scalechange); offsetY = -(zoomPointY * scalechange);Так что на самом деле вы можете просто панорамировать вниз и справа при увеличении масштаба, в зависимости от того, насколько вы увеличили масштаб, относительно точки, на которую вы увеличили.
наконец-то решить это:
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оставшийся код затем должен применить масштабирование и перевести в контекст рисования, чтобы его происхождение совпадало с углом холста.
это на самом деле очень сложная проблема (математически), и я работаю на то же самое почти. Я задал аналогичный вопрос о Stackoverflow, но не получил ответа, но опубликовал в DocType (StackOverflow для HTML/CSS) и получил ответ. Зацени!http://doctype.com/javascript-image-zoom-css3-transforms-calculate-origin-example
Я нахожусь в середине создания плагина jQuery, который делает это (масштабирование в стиле Google Maps с использованием преобразований CSS3). У меня есть увеличить до мыши курсор бит работает нормально, все еще пытаясь выяснить, как позволить пользователю перетащить холст вокруг, как вы можете сделать в Google Maps. Когда я получу его работу, я отправлю код здесь, но проверьте ссылку выше для части мыши-масштабирования до точки.
Я не понимал, что есть методы масштабирования и перевода на контекст холста, вы можете достичь того же, используя 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 transform-origin в 0, 0 (- moz-transform-origin: 0 0). Использование CSS3 transform позволяет увеличить масштаб на что угодно, просто убедитесь, что контейнер DIV установлен на переполнение: скрыто, чтобы остановить масштабированные края, выливающиеся из сторон.
используете ли вы преобразования CSS3 или собственные методы масштабирования и перевода canvas, зависит от вас, но проверьте приведенную выше ссылку для вычислений.
обновление: Meh! Я просто опубликую код здесь, а не заставлю вас следовать ссылка:
$(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; }); });вам, конечно, нужно будет адаптировать его, чтобы использовать масштаб холста и методы перевода.
обновление 2: просто заметил, что я использую transform-origin вместе с translate. Мне удалось реализовать версию, которая просто использует масштаб и переводит самостоятельно, проверьте это здесь http://www.dominicpettifer.co.uk/Files/Mosaic/MosaicTest.html дождитесь загрузки изображений, а затем используйте колесо мыши для увеличения, также поддерживает панорамирование путем перетаскивания изображения вокруг. Он использует преобразования CSS3, но вы должны иметь возможность использовать те же вычисления для вашего холста.
я столкнулся с этой проблемой, используя c++, который я, вероятно, не должен был просто использовать матрицы OpenGL для начала...в любом случае, если вы используете элемент управления, источником которого является верхний левый угол, и вы хотите панорамировать/масштабировать, как Google maps, вот макет (используя 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; }
вот мое решение для центрированного изображения:
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>
мне нравится ответ Tatarize, но я предоставлю альтернативу. Это тривиальная задача линейной алгебры, а способ я представляю хорошо работает с панорамирование, масштабирование, наклон и т. д. То есть, это хорошо работает, если ваш образ уже преобразован.
когда матрица масштабируется, масштаб находится в точке (0, 0). Итак, если у вас есть изображение и масштабировать его в 2 раза, нижняя правая точка будет удваиваться в обоих направлениях x и y (используя соглашение о том, что [0, 0] является верхним левым изображение.)
если вместо этого вы хотите увеличить изображение вокруг центра, то решение выглядит следующим образом: (1) перевести изображение таким образом, что его центр находится в (0, 0); (2) масштабировать изображение по X и y факторам; (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);вот оно. Вот Планк, который показывает это в действии. Прокрутите с колесиком мыши на точках, и вы увидите, что они последовательно остаются на месте. (Проверял только в Хроме.) http://plnkr.co/edit/3aqsWHPLlSXJ9JCcJzgH?p=preview
Я хочу поместить здесь некоторую информацию для тех, кто делает отдельно рисунок изображения и перемещение-масштабирование его.
Это может быть полезно, когда вы хотите сохранить масштабирование и положение просмотра.
вот ящик:
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!!! }обратите внимание масштаб должен быть первым.
и вот зумер:
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 }и, конечно же, нам понадобится Драггер:
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(); } })
вот альтернативный способ сделать это, который использует 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
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); }
вот реализация кода ответа @tatarize, используя PIXI.js. У меня есть видовой экран, который смотрит на часть очень большого изображения (например, стиль google maps).
$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));
вам нужно получить точку в мировом пространстве (в отличие от пространства экрана) до и после масштабирования, а затем перевести по дельте.
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
вы можете использовать функцию scrollto(x,y) для обработки положения полосы прокрутки вправо до точки, которую вам нужно показать после масштабирования.для нахождения положения мыши используйте событие.clientX и события.clientY. это поможет вам

Comments