我希望能够放大 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; 
}
