放大一个点(使用缩放和平移)

IT技术 javascript html canvas
2021-01-29 13:22:50

我希望能够放大 HTML 5 画布中鼠标下方的点,例如放大Google Maps我怎样才能做到这一点?

6个回答

更好的解决方案是根据缩放的变化简单地移动视口的位置。缩放点只是您希望保持不变的旧缩放和新缩放中的点。也就是说,预先缩放的视口和缩放后的视口相对于视口具有相同的缩放点。鉴于我们相对于原点进行缩放。您可以相应地调整视口位置:

scalechange = newscale - oldscale;
offsetX = -(zoomPointX * scalechange);
offsetY = -(zoomPointY * scalechange);

因此,实际上您可以在放大时向下和向右平移,根据放大的程度,相对于放大的点。

在此处输入图片说明

@C.Finke 第二种方法是使用 ctx 中的翻译。您绘制的所有内容都具有相同的大小和相同的位置。但是,您只需在 javascript 画布中使用矩阵乘法来设置上下文的平移和缩放(缩放)。因此,与其在不同的位置重绘所有形状。您将它们绘制在同一个位置并在 javascript 中移动视口。此方法还需要您获取鼠标事件并将它们向后转换。所以你会减去平移,然后通过缩放反转因子。
2021-03-21 13:22:50
另外,我想为那些寻求实现像 pan-zoom 组件这样的地图添加,鼠标 X, Y 应该是 (mousePosRelativeToContainer - currentTransform)/currentScale 否则它会将当前鼠标位置视为相对于容器。
2021-03-22 13:22:50
比剪切和粘贴代码更有value的是解释最佳解决方案是什么以及为什么它可以在没有包袱的情况下工作,尤其是当它是三行长的时候。
2021-04-05 13:22:50
scalechange = newscale / oldscale?
2021-04-11 13:22:50
是的,这个数学假设缩放和平移在与原点相关的坐标中。如果它们相对于视口,您必须适当地调整它们。虽然我认为正确的数学是 zoomPoint = (mousePosRelativeToContainer + currentTranslation)。该数学还假设原点通常位于字段的左上角。但是,考虑到简单性,调整稍微非典型的情况要容易得多。
2021-04-11 13:22:50

终于解决了:

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>

正如@Tata​​rize 指出的那样关键是计算轴位置,以便缩放点(鼠标指针)在缩放后保持在同一位置。

本来鼠标是在离mouse/scale一定距离的地方,我们希望鼠标下的点在缩放后保持在原来的位置,但是这个是在mouse/new_scale远离角的地方。因此,我们需要移动origin(角的坐标)来解决这个问题。

originx -= mousex/(scale*zoom) - mousex/scale;
originy -= mousey/(scale*zoom) - mousey/scale;
scale *= zoom

然后剩下的代码需要应用缩放并转换到绘制上下文,使其原点与画布角重合。

清除画布时 800 和 600 值代表什么?
2021-03-21 13:22:50
谢谢老兄,差点丢了两天,才找到你的代码
2021-03-24 13:22:50
嘿,我只是在寻找这样的东西,只是想说刺激你破解了它!
2021-03-28 13:22:50
这如何适用于 dom 节点?
2021-03-31 13:22:50
@GeorgianStan 那是我忘记更改的宽度和高度。现在用命名变量替换它们。
2021-04-07 13:22:50

这实际上是一个非常困难的问题(数学上),我几乎在做同样的事情。我在 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 使用相同的计算。

马赛克演示不见了。我通常使用 vanilla js 而不是 jQuery。$(this) 指的是什么?document.body.offsetTop?我真的很想看到马赛克演示,我的foreverscape.com 项目真的可以从中受益。
2021-03-14 13:22:50
马赛克演示页面保存在archive.org:web.archive.org/web/20130126152008/http : //...
2021-03-16 13:22:50
我终于解决了,在做了大约 2 周的其他事情后,我现在花了 3 分钟
2021-03-20 13:22:50
截至今天(2014 年 9 月),MosaicTest.html 的链接已失效。
2021-03-25 13:22:50
@Synday Ironfoot 更新中的链接不起作用。这个链接:dominicpettifer.co.uk/Files/Mosaic/MosaicTest.html 我想要这个impelementation你能把代码贴在这里吗?谢谢
2021-04-01 13:22:50

我喜欢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

我正在使用 Canvas API,但它没有直接的 multiply() API。相反,我一直在做 resetTransform(),然后应用“缩放”平移、缩放、撤消缩放平移,然后应用实际所需的平移。这几乎有效,但有时会导致图像的原点移动。您能否举例说明如何使用 CanvasRenderingContext2D 对象执行上述操作?
2021-03-14 13:22:50
@ScoPi 我写了以下文章,其中提供了更多详细信息并有一个使用画布的示例:medium.com/@benjamin.botto/...
2021-03-17 13:22:50
我必须说,如果您有可用的仿射变换矩阵,请热情地使用它。许多变换矩阵甚至会具有完全相同的 zoom(sx,sy,x,y) 函数。如果没有人使用,几乎值得做一个。
2021-03-21 13:22:50
事实上,我承认在我使用这个解决方案的代码中,已经用矩阵类替换了它。我已经多次完成这个确切的事情并且已经制作了不少于两次的矩阵类。( github.com/EmbroidePy/pyembroidery/blob/master/pyembroidery/... ), ( github.com/EmbroidePy/EmbroidePy/blob/master/embroidepy/... )。如果你想要比这些运算更复杂的东西,矩阵基本上是正确的答案,一旦你掌握了线性代数,你就会意识到这个答案实际上是最好的答案。
2021-03-27 13:22:50

我使用 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; 
}