Я хочу иметь возможность увеличивать точку под мышью на холсте HTML 5, как масштабирование на Картах Google . Как я могу этого достичь?
Я хочу иметь возможность увеличивать точку под мышью на холсте HTML 5, как масштабирование на Картах Google . Как я могу этого достичь?
Ответы:
Лучшее решение - просто переместить положение области просмотра на основе изменения масштаба. Точка масштабирования - это просто точка в старом и новом масштабах, которую вы хотите оставить прежней. Это означает, что область просмотра предварительно увеличена, а область просмотра после увеличения имеет одинаковую точку увеличения относительно области просмотра. Учитывая, что мы масштабируемся относительно происхождения. Вы можете настроить положение окна просмотра соответственно:
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.onwheel = 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.deltaY < 0 ? 1 : -1;
// 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 *= zoom
Затем оставшийся код должен применить масштабирование и преобразование к контексту рисования, чтобы его происхождение совпадало с углом холста.
На самом деле это очень сложная задача (математически), и я почти работаю над тем же. Я задал аналогичный вопрос о 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 transform-origin установлено значение 0, 0 (-moz-transform-origin: 0 0). Использование CSS3-трансформации позволяет вам увеличивать все, что угодно, просто убедитесь, что контейнер DIV настроен на переполнение: скрытый, чтобы остановить увеличение масштабов по краям.
Независимо от того, используете ли вы CSS3-преобразования или собственные методы canvas и translate, все зависит от вас, но проверьте расчеты по приведенной выше ссылке.
Обновление: Мех! Я просто опубликую код здесь, а не заставлю вас перейти по ссылке:
$(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 Transforms, но вы должны иметь возможность использовать те же вычисления для вашего Canvas.
Я столкнулся с этой проблемой, используя 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;
}
мне нравиться ответ Татаризата , но я предоставлю альтернативу. Это тривиальная задача линейной алгебры, и метод, который я представляю, хорошо работает с панорамированием, масштабированием, перекосом и т. Д. То есть он хорошо работает, если ваше изображение уже трансформировано.
Когда матрица масштабируется, масштаб находится в точке (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
Более абстрактно, та же самая стратегия работает для любой точки. Если, например, вы хотите масштабировать изображение в точке P:
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
Вот мое решение для центрированного изображения:
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>
Вот альтернативный способ сделать это, который использует 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
Я хочу разместить здесь некоторую информацию для тех, кто отдельно рисует рисунок и двигает - масштабирует его.
Это может быть полезно, когда вы хотите сохранить масштаб и положение области просмотра.
Вот ящик:
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();
}
})
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) для обработки положения полосы прокрутки вправо до точки, которую вам нужно показать после масштабирования. Для нахождения положения мыши используйте event.clientX и event.clientY. это поможет тебе
Одна важная вещь ... если у вас есть что-то вроде:
body {
zoom: 0.9;
}
Вы должны сделать эквивалентную вещь на холсте:
canvas {
zoom: 1.1;
}