我希望能够放大 HTML 5 画布中鼠标下方的点,例如放大Google Maps。我怎样才能做到这一点?
放大一个点(使用缩放和平移)
终于解决了:
const zoomIntensity = 0.2;
const canvas = document.getElementById("canvas");
let context = canvas.getContext("2d");
const width = 600;
const height = 200;
let scale = 1;
let originx = 0;
let originy = 0;
let visibleWidth = width;
let visibleHeight = height;
function draw(){
// Clear screen to white.
context.fillStyle = "white";
context.fillRect(originx, originy, width/scale, height/scale);
// Draw the black square.
context.fillStyle = "black";
context.fillRect(50, 50, 100, 100);
// Schedule the redraw for the next display refresh.
window.requestAnimationFrame(draw);
}
// Begin the animation loop.
draw();
canvas.onwheel = function (event){
event.preventDefault();
// Get mouse offset.
const mousex = event.clientX - canvas.offsetLeft;
const mousey = event.clientY - canvas.offsetTop;
// Normalize mouse wheel movement to +1 or -1 to avoid unusual jumps.
const wheel = event.deltaY < 0 ? 1 : -1;
// Compute zoom factor.
const 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(HTML/CSS 的 StackOverflow)中发布并得到了回应。查看http://doctype.com/javascript-image-zoom-css3-transforms-calculate-origin-example
我正在构建一个执行此操作的 jQuery 插件(使用 CSS3 转换的 Google 地图样式缩放)。我已经将鼠标光标缩放到位工作正常,仍在尝试弄清楚如何允许用户像在 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 transform-origin 设置为 0, 0 (-moz-transform-origin: 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:刚刚注意到我正在使用 transform-origin 和 translate。我已经设法实现了一个仅使用比例尺和自己翻译的版本,请在此处查看http://www.dominicpettifer.co.uk/Files/Mosaic/MosaicTest.html等待图像下载然后使用您的鼠标滚轮缩放,还支持通过拖动图像来平移。它使用 CSS3 Transforms,但您应该能够对 Canvas 使用相同的计算。
我喜欢Tatarize 的回答,但我会提供一个替代方案。这是一个微不足道的线性代数问题,我提出的方法适用于平移、缩放、倾斜等。也就是说,如果您的图像已经被转换,它就可以很好地工作。
缩放矩阵时,缩放点位于 (0, 0) 点。因此,如果您有一个图像并将其缩放 2 倍,则右下角的点将在 x 和 y 方向上加倍(使用 [0, 0] 是图像左上角的约定)。
相反,如果您想围绕中心缩放图像,那么解决方案如下: (1) 平移图像,使其中心位于 (0, 0);(2) 按 x 和 y 因子缩放图像;(3) 将图像翻译回来。IE
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);
你有它。这是一个 plunk,显示了这一点。用鼠标滚轮在点上滚动,您会看到它们始终保持原状。(仅在 Chrome 中测试。)http://plnkr.co/edit/3aqsWHPLlSXJ9JCcJzgH?p=preview
我使用 c++ 遇到了这个问题,我可能不应该只使用 OpenGL 矩阵开始...像谷歌地图一样,这是布局(使用快板作为我的事件处理程序):
// 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;
}