我希望能够放大 HTML 5 画布中鼠标下的点,例如放大 Google Maps。我怎样才能做到这一点?
更好的解决方案是根据缩放的变化简单地移动视口的位置。缩放点只是旧缩放和新缩放中要保持不变的点。也就是说,预缩放的视口和缩放后的视口相对于视口具有相同的缩放点。鉴于我们正在相对于原点进行缩放。您可以相应地调整视口位置:
scalechange = newscale - oldscale;
offsetX = -(zoomPointX * scalechange);
offsetY = -(zoomPointY * scalechange);
所以实际上你可以在放大时向下和向右平移,相对于你放大的点,你放大了多少。
https://i.stack.imgur.com/oDTlQ.png
终于解决了:
常量缩放强度 = 0.2; const canvas = document.getElementById("canvas");让上下文 = canvas.getContext("2d");常量宽度 = 600;常量高度 = 200;让比例 = 1;让 originx = 0;让原始= 0;让可见宽度 = 宽度;让可见高度 = 高度; function draw(){ // 清屏为白色。 context.fillStyle = "白色"; context.fillRect(originx, originy, 宽度/比例, 高度/比例); // 绘制黑色方块。 context.fillStyle = "黑色"; context.fillRect(50, 50, 100, 100); // 为下一次显示刷新安排重绘。 window.requestAnimationFrame(draw); } // 开始动画循环。画(); canvas.onwheel = 函数(事件){ event.preventDefault(); // 获取鼠标偏移量。 const mousex = event.clientX - canvas.offsetLeft; const mousey = event.clientY - canvas.offsetTop; // 将鼠标滚轮移动标准化为 +1 或 -1 以避免异常跳跃。常量轮 = event.deltaY < 0 ? 1:-1; // 计算缩放系数。 const zoom = Math.exp(wheel * zoomIntensity); // 翻译使可见原点位于上下文的原点。 context.translate(originx, originy); // 计算新的可见原点。最初鼠标与拐角处 // 距离 mouse/scale 处,我们希望鼠标下方的点在缩放后保持在同一位置,但这 // 位于距拐角处的 mouse/new_scale 处。因此我们需要 // 移动原点(角的坐标)来解决这个问题。 originx -= mousex/(scale*zoom) - mousex/scale; originy -= mousey/(scale*zoom) - mousey/scale; // 缩放它(由于上面的平移,以原点为中心)。 context.scale(缩放,缩放); // 将可见原点偏移到正确的位置。 context.translate(-originx, -originy); // 更新比例和其他。缩放 *= 缩放;可见宽度=宽度/比例;可见高度 = 高度 / 比例; }
关键,如@Tatarize pointed out,是计算轴位置,使缩放点(鼠标指针)在缩放后保持在同一位置。
最初鼠标在距离角 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 插件(Google Maps style zoom using CSS3 Transforms)。我的鼠标光标缩放位工作正常,仍在试图弄清楚如何允许用户像在谷歌地图中那样拖动画布。当我让它工作时,我会在这里发布代码,但请查看上面的链接以获取鼠标缩放到点部分。
我没有意识到 Canvas 上下文中有 scale 和 translate 方法,您可以使用 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 变换,但您应该能够为您的画布使用相同的计算。
我喜欢 Tatarize's answer,但我会提供一个替代方案。这是一个微不足道的线性代数问题,我提出的方法适用于平移、缩放、倾斜等。也就是说,如果您的图像已经转换,它也很有效。
当矩阵被缩放时,比例在 (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);
你有它。 Here's a plunk 展示了这一点。在点上滚动鼠标滚轮,您会看到它们始终保持不变。 (仅在 Chrome 中测试。)http://plnkr.co/edit/3aqsWHPLlSXJ9JCcJzgH?p=preview
我使用 c++ 遇到了这个问题,我可能不应该只是使用 OpenGL 矩阵开始...无论如何,如果您使用的控件的原点是左上角,并且您想要平移/缩放像谷歌地图一样,这是布局(使用 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;
}
这是我针对中心图像的解决方案:
变量 MIN_SCALE = 1;变量 MAX_SCALE = 5;变比例 = 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 =下一个偏移X; offsetY = nextOffsetY;规模=下一个规模; });身体{背景颜色:橙色; } #container { 边距:30px;宽度:500px;高度:500px;背景颜色:白色;位置:相对;溢出:隐藏; } img { 位置:绝对;顶部:0;底部:0;左:0;右:0;最大宽度:100%;最大高度:100%;边距:自动; }
这是使用 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);
}
mouseX
和 mouseY
的参考会很有帮助。
我想在这里为那些单独绘制图片和移动缩放的人提供一些信息。
当您想要存储视口的缩放和位置时,这可能很有用。
这是抽屉:
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();
}
})
这是@tatarize 答案的代码实现,使用PIXI.js。我有一个查看非常大图像(例如谷歌地图样式)的一部分的视口。
$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
一件重要的事情......如果你有类似的东西:
body {
zoom: 0.9;
}
您需要在画布中制作等效的东西:
canvas {
zoom: 1.1;
}
您可以使用 scrollto(x,y) 函数将滚动条的位置处理到缩放后需要显示的位置。查找鼠标的位置使用 event.clientX 和 event.clientY。 this will help you
这是我用来更严格控制绘制方式的方法
var canvas = document.getItemById("canvas"); var ctx = canvas.getContext("2d");变量比例 = 1;变量 xO = 0;变量 yO = 0;画 ( ) ; function draw(){ // 清屏 ctx.clearRect(0, 0, canvas.offsetWidth, canvas.offsetHeight); // 原始坐标 const xData = 50, yData = 50, wData = 100, hData = 100; // 转换后的坐标 const x = xData * scale + xO , y = yData * scale + yO , w = wData * scale , h = hData * scale ; // 绘制变换位置 ctx.fillStyle = "black"; ctx.fillRect(x,y,w,h); } canvas.onwheel = function(e){ e.preventDefault(); const r = canvas.getBoundingClientRect(), xNode = e.pageX - row.left, yNode = e.pageY - row.top; const newScale = scale * Math.exp (-Math.sign (e.deltaY) * 0.2), scaleFactor = new Scale / scale; xO = xNode - scaleFactor *(xNode - xO); yO = yNode - scaleFactor *(yNode - yO);规模=新规模;画 ( ) ; } <画布id =“画布”宽度=“600”高度=“200”>
这是我的解决方案:
// helpers const diffPoints = (p1, p2) => { return { x: p1.x - p2.x, y: p1.y - p2.y, }; }; const addPoints = (p1, p2) => { return { x: p1.x + p2.x, y: p1.y + p2.y, }; };函数 scalePoint(p1, scale) { return { x: p1.x / scale, y: p1.y / scale }; } // 常量 const ORIGIN = Object.freeze({ x: 0, y: 0 });常量 SQUARE_SIZE = 20;常量 ZOOM_SENSITIVITY = 500; // 更大,每次滚动的缩放更低 const MAX_SCALE = 50;常量 MIN_SCALE = 0.1; // dom const canvas = document.getElementById("canvas");常量上下文 = canvas.getContext("2d"); const debugDiv = document.getElementById("debug"); // “道具” const initialScale = 0.75; const initialOffset = { x: 10, y: 20 }; // “状态” let mousePos = ORIGIN;让 lastMousePos = ORIGIN;让偏移量 = 初始偏移量;让比例 = 初始比例; // 设置画布时,将宽度/高度设置为 devicePixelRation 乘以正常 const { devicePixelRatio = 1 } = window; context.canvas.width = context.canvas.width * devicePixelRatio; context.canvas.height = context.canvas.height * devicePixelRatio;函数绘制(){ window.requestAnimationFrame(绘制); // 清除画布 context.canvas.width = context.canvas.width; // 变换坐标 - 比例乘以 devicePixelRatio context.scale(scale * devicePixelRatio, scale * devicePixelRatio); context.translate(offset.x, offset.y); // 绘制 context.fillRect(200 + -SQUARE_SIZE / 2, 50 + -SQUARE_SIZE / 2, SQUARE_SIZE, SQUARE_SIZE); // 调试 context.beginPath(); context.moveTo(0, 0); context.lineTo(0, 50); context.moveTo(0, 0); context.lineTo(50, 0); context.stroke(); // debugDiv.innerText = `scale: ${scale} // 鼠标:${JSON.stringify(mousePos)} // 偏移量:${JSON.stringify(offset)} // `; } // 计算画布上鼠标相对于页面左上画布点的位置 function calculateMouse(event, canvas) { const viewportMousePos = { x: event.pageX, y: event.pageY }; const boundingRect = canvas.getBoundingClientRect(); const topLeftCanvasPos = { x: boundingRect.left, y: boundingRect.top };返回差异点(viewportMousePos,topLeftCanvasPos); } // 缩放函数 handleWheel(event) { event.preventDefault(); // 更新鼠标位置 const newMousePos = calculateMouse(event, canvas);最后鼠标位置 = 鼠标位置;鼠标位置 = 新鼠标位置; // 计算新的缩放比例 const zoom = 1 - event.deltaY / ZOOM_SENSITIVITY;常量 newScale = 缩放 * 缩放; if (MIN_SCALE > newScale || newScale > MAX_SCALE) { return; } // 偏移画布,使鼠标下方的点不移动 const lastMouse = scalePoint(mousePos, scale);常量 newMouse = scalePoint(mousePos, newScale); const mouseOffset = diffPoints(lastMouse, newMouse); offset = diffPoints(offset, mouseOffset);规模=新规模; } canvas.addEventListener("wheel", handleWheel); // 平移 const mouseMove = (event) => { // 更新鼠标位置 const newMousePos = calculateMouse(event, canvas);最后鼠标位置 = 鼠标位置;鼠标位置 = 新鼠标位置; const mouseDiff = scalePoint(diffPoints(mousePos, lastMousePos), scale); offset = addPoints(offset, mouseDiff); }; const mouseUp = () => { document.removeEventListener("mousemove", mouseMove); document.removeEventListener("mouseup", mouseUp); }; const startPan = (event) => { document.addEventListener("mousemove", mouseMove); document.addEventListener("mouseup", mouseUp); // 设置初始鼠标位置以防用户尚未移动鼠标 mousePos = calculateMouse(event, canvas); }; canvas.addEventListener("mousedown", startPan); // 重复重绘 window.requestAnimationFrame(draw); #canvas { /*为你在css中真正想要的设置固定宽度和高度!*/ /*应该与传递给画布元素的内容相同*/ width: 500px;高度:150px;位置:固定;边框:2px纯黑色;最高:50%;左:50%;变换:翻译(-50%,-50%); }
< /html>
添加一个在 C# & WPF 中对我有用的答案:
double zoom = scroll > 0 ? 1.2 : (1/1.2);
var CursorPosCanvas = e.GetPosition(Canvas);
pan.X += -(CursorPosCanvas.X - Canvas.RenderSize.Width / 2.0 - pan.X) * (zoom - 1.0);
pan.Y += -(CursorPosCanvas.Y - Canvas.RenderSize.Height / 2.0 - pan.Y) * (zoom - 1.0);
transform.ScaleX *= zoom;
transform.ScaleY *= zoom;