Tôi muốn có thể phóng to điểm dưới chuột trong khung vẽ HTML 5, như phóng to Google Maps . Làm thế nào tôi có thể đạt được điều đó?
Tôi muốn có thể phóng to điểm dưới chuột trong khung vẽ HTML 5, như phóng to Google Maps . Làm thế nào tôi có thể đạt được điều đó?
Câu trả lời:
Giải pháp tốt hơn là chỉ cần di chuyển vị trí của khung nhìn dựa trên sự thay đổi trong thu phóng. Điểm thu phóng chỉ đơn giản là điểm trong thu phóng cũ và mức thu phóng mới mà bạn muốn giữ nguyên. Nghĩa là chế độ xem được phóng to trước và chế độ xem sau thu phóng có cùng số zoompoint so với chế độ xem. Cho rằng chúng tôi mở rộng liên quan đến nguồn gốc. Bạn có thể điều chỉnh vị trí khung nhìn phù hợp:
scalechange = newscale - oldscale;
offsetX = -(zoomPointX * scalechange);
offsetY = -(zoomPointY * scalechange);
Vì vậy, thực sự bạn chỉ có thể xoay xuống và sang phải khi bạn phóng to, theo hệ số bạn đã phóng to bao nhiêu, so với điểm bạn đã phóng to.
Cuối cùng đã giải quyết nó:
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>
Chìa khóa, như @Tatarize đã chỉ ra , là tính toán vị trí trục sao cho điểm thu phóng (con trỏ chuột) vẫn ở cùng một vị trí sau khi thu phóng.
Ban đầu con chuột ở một khoảng cách mouse/scale
từ góc, chúng tôi muốn điểm dưới con chuột vẫn ở cùng một vị trí sau khi phóng to, nhưng điều này nằm ở mouse/new_scale
góc xa. Do đó, chúng ta cần chuyển origin
(tọa độ của góc) để giải thích cho việc này.
originx -= mousex/(scale*zoom) - mousex/scale;
originy -= mousey/(scale*zoom) - mousey/scale;
scale *= zoom
Đoạn mã còn lại sau đó cần áp dụng tỷ lệ và dịch sang ngữ cảnh vẽ sao cho nguồn gốc của nó trùng với góc canvas.
Đây thực sự là một vấn đề rất khó khăn (về mặt toán học) và tôi đang làm việc với điều tương tự. Tôi đã hỏi một câu hỏi tương tự trên Stackoverflow nhưng không có phản hồi, nhưng được đăng trong DocType (StackOverflow cho HTML / CSS) và nhận được phản hồi. Hãy xem http://doctype.com/javascript-image-zoom-css3-transforms-calculate-origin-example
Tôi đang trong quá trình xây dựng một plugin jQuery thực hiện việc này (thu phóng kiểu Google Maps bằng CSS3 Transforms). Tôi đã thu phóng bit để con trỏ chuột hoạt động tốt, vẫn cố gắng tìm ra cách cho phép người dùng kéo khung vẽ xung quanh như bạn có thể làm trong Google Maps. Khi tôi làm việc, tôi sẽ đăng mã ở đây, nhưng hãy kiểm tra liên kết ở trên để biết phần thu phóng chuột đến điểm.
Tôi không nhận ra có các phương pháp tỷ lệ và dịch trên ngữ cảnh Canvas, bạn có thể đạt được điều tương tự bằng CSS3, vd. sử dụng 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)');
Đảm bảo rằng bạn đặt biến đổi gốc CSS3 thành 0, 0 (-moz-Transform-origin: 0 0). Sử dụng biến đổi CSS3 cho phép bạn phóng to bất cứ thứ gì, chỉ cần đảm bảo rằng DIV của container được đặt thành tràn: ẩn để ngăn các cạnh được phóng to tràn ra khỏi các cạnh.
Cho dù bạn sử dụng các phép biến đổi CSS3, hay các phương thức dịch và tỷ lệ riêng của canvas đều tùy thuộc vào bạn, nhưng hãy kiểm tra liên kết ở trên để biết các phép tính.
Cập nhật: Meh! Tôi sẽ chỉ đăng mã ở đây chứ không phải để bạn theo liên kết:
$(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;
});
});
Tất nhiên bạn sẽ cần phải điều chỉnh nó để sử dụng quy mô canvas và phương thức dịch.
Cập nhật 2: Chỉ cần lưu ý rằng tôi đang sử dụng biến đổi gốc cùng với dịch. Tôi đã quản lý để triển khai một phiên bản chỉ sử dụng tỷ lệ và tự dịch, hãy kiểm tra nó tại đây http://www.dominicpettifer.co.uk/Files/Mosaic/MosaicTest.html Đợi hình ảnh tải xuống rồi sử dụng bánh xe chuột để phóng to, cũng hỗ trợ di chuyển bằng cách kéo hình ảnh xung quanh. Đó là sử dụng Chuyển đổi CSS3 nhưng bạn sẽ có thể sử dụng các phép tính tương tự cho Canvas của mình.
Tôi gặp phải vấn đề này khi sử dụng c ++, điều mà có lẽ tôi không nên sử dụng ma trận OpenGL để bắt đầu với ... dù sao đi nữa, nếu bạn đang sử dụng một điều khiển có nguồn gốc là góc trên cùng bên trái và bạn muốn xoay / thu phóng như google maps, đây là cách bố trí (sử dụng allegro làm trình xử lý sự kiện của tôi):
// 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;
}
Tôi thích câu trả lời của Tatarize , nhưng tôi sẽ cung cấp một giải pháp thay thế. Đây là một vấn đề đại số tuyến tính tầm thường và phương pháp tôi trình bày hoạt động tốt với pan, zoom, skew, v.v. Đó là, nó hoạt động tốt nếu hình ảnh của bạn đã được chuyển đổi.
Khi một ma trận được chia tỷ lệ, thang đo nằm ở điểm (0, 0). Vì vậy, nếu bạn có một hình ảnh và chia tỷ lệ của nó theo hệ số 2, điểm dưới cùng bên phải sẽ tăng gấp đôi theo cả hai hướng x và y (sử dụng quy ước rằng [0, 0] là trên cùng bên trái của hình ảnh).
Nếu thay vào đó, bạn muốn phóng to hình ảnh về trung tâm, thì một giải pháp như sau: (1) dịch hình ảnh sao cho tâm của nó ở (0, 0); (2) chia tỷ lệ hình ảnh theo các yếu tố x và y; (3) dịch lại hình ảnh. I E
myMatrix
.translate(image.width / 2, image.height / 2) // 3
.scale(xFactor, yFactor) // 2
.translate(-image.width / 2, -image.height / 2); // 1
Trừu tượng hơn, chiến lược tương tự làm việc cho bất kỳ điểm nào. Ví dụ, nếu bạn muốn chia tỷ lệ hình ảnh tại một điểm P:
myMatrix
.translate(P.x, P.y)
.scale(xFactor, yFactor)
.translate(-P.x, -P.y);
Và cuối cùng, nếu hình ảnh đã được chuyển đổi theo một cách nào đó (ví dụ: nếu nó bị xoay, lệch, dịch hoặc thu nhỏ), thì việc chuyển đổi hiện tại cần được giữ nguyên. Cụ thể, biến đổi được xác định ở trên cần được nhân sau (hoặc nhân phải) bởi biến đổi hiện tại.
myMatrix
.translate(P.x, P.y)
.scale(xFactor, yFactor)
.translate(-P.x, -P.y)
.multiply(myMatrix);
Có bạn có nó. Đây là một plunk cho thấy điều này trong hành động. Di chuyển bằng con lăn trên các chấm và bạn sẽ thấy rằng chúng luôn được đặt. (Chỉ được thử nghiệm trong Chrome.) Http://plnkr.co/edit/3aqsWHPLlSXJ9JCcJzgH?p=preview
Đây là giải pháp của tôi cho hình ảnh hướng trung tâm:
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>
Đây là một cách khác để làm điều đó sử dụng setTransform () thay vì scale () và dịch (). Tất cả mọi thứ được lưu trữ trong cùng một đối tượng. Canvas được giả định là 0,0 trên trang, nếu không, bạn sẽ cần phải trừ vị trí của nó khỏi các cuộn trang.
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);
};
Mã kèm theo để xử lý panning:
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
};
};
Để tự mình rút ra câu trả lời, hãy xem xét rằng các tọa độ trang giống nhau cần khớp với cùng tọa độ canvas trước và sau khi thu phóng. Sau đó, bạn có thể làm một số đại số bắt đầu từ phương trình này:
(pageCoords - dịch) / scale = canvasCoords
Tôi muốn đặt ở đây một số thông tin cho những người, những người thực hiện vẽ riêng hình ảnh và di chuyển - hiển thị nó.
Điều này có thể hữu ích khi bạn muốn lưu trữ thu phóng và vị trí của chế độ xem.
Đây là ngăn kéo:
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!!!
}
Thông báo quy mô PHẢI là đầu tiên .
Và đây là 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
}
và, tất nhiên, chúng ta sẽ cần một trình kéo:
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();
}
})
Đây là một triển khai mã của câu trả lời của @ tatarize, sử dụng PIXI.js. Tôi có một khung nhìn nhìn vào một phần của một hình ảnh rất lớn (ví dụ: phong cách 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
là thùng chứa html của tôi.imageContainer
là thùng chứa PIXI của tôi có hình ảnh trong đó.mousePosOnImage
là vị trí chuột liên quan đến toàn bộ hình ảnh (không chỉ cổng xem).Đây là cách tôi có được vị trí chuột:
imageContainer.on('mousemove', _.bind(function(ev) {
mousePosOnImage = ev.data.getLocalPosition(imageContainer);
mousePosOnViewport.x = ev.data.originalEvent.offsetX;
mousePosOnViewport.y = ev.data.originalEvent.offsetY;
},self));
Bạn cần lấy điểm trong không gian thế giới (trái ngược với không gian màn hình) trước và sau khi phóng to, sau đó dịch theo delta.
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;
Vị trí chuột nằm trong không gian màn hình, vì vậy bạn phải biến đổi nó thành không gian thế giới. Biến đổi đơn giản nên tương tự như thế này:
world_position = screen_position / scale - translation
bạn có thể sử dụng chức năng scrollto (x, y) để xử lý vị trí của thanh cuộn ngay đến điểm bạn cần hiển thị sau khi phóng to. Để tìm vị trí của chuột sử dụng event.clientX và event.clientY. Điều này sẽ giúp bạn
Một điều quan trọng ... nếu bạn có một cái gì đó như:
body {
zoom: 0.9;
}
Bạn cần làm cho điều tương đương trong vải:
canvas {
zoom: 1.1;
}