Я хочу мати можливість збільшити масштаб точки під мишею на полотні 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 за допомогою CSS3 Transforms). У мене курсор миші трохи працює, все ще намагаюся зрозуміти, як дозволити користувачеві перетягувати полотно навколо, як це можна зробити на Картах Google. Коли я працюю, я опублікую тут код, але перевірте вище за посиланням на частину миші та збільшення.
Я не розумів, що існують методи масштабування та перекладу на 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, або власний масштаб полотна та методи перекладу, вам належить, але перевірте вищенаведене посилання для розрахунків.
Оновлення: Мех! Я просто опублікую код тут, а не змушую вас перейти за посиланням:
$(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: Щойно помічено, я використовую трансформаційне походження разом із перекладом. Мені вдалося реалізувати версію, яка просто використовує масштаб та перекладати самостійно, перегляньте це тут http://www.dominicpettifer.co.uk/Files/Mosaic/MosaicTest.html Зачекайте, коли зображення завантажуватимуться та використовуйте колесо миші для збільшення, також підтримує панорамування, перетягуючи зображення навколо. Він використовує CSS3 Transforms, але ви повинні мати можливість використовувати ті самі розрахунки для свого Canvas.
Я зіткнувся з цією проблемою за допомогою 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;
}
Мені подобається відповідь 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
Більш абстрактно, та ж стратегія працює для будь-якої точки. Наприклад, якщо ви хочете масштабувати зображення в точці 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 () замість шкали () та перекладати (). Все зберігається в одному об’єкті. Полотно вважається рівним 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;
}