简体   繁体   English

将画布缩放到鼠标光标

[英]Zoom Canvas to Mouse Cursor

I'm programming a HTML5 < canvas > project that involves zooming in and out of images using the scroll wheel.我正在编写一个 HTML5 <canvas > 项目,该项目涉及使用滚轮放大和缩小图像。 I want to zoom towards the cursor like google maps does but I'm completely lost on how to calculate the movements.我想像谷歌地图一样放大光标,但我完全不知道如何计算运动。

What I have: image x and y (top-left corner);我所拥有的:图像 x 和 y(左上角); image width and height;图像宽度和高度; cursor x and y relative to the center of the canvas.相对于画布中心的光标 x 和 y。

In short, you want to translate() the canvas context by your offset, scale() it to zoom in or out, and then translate() back by the opposite of the mouse offset. 简而言之,您希望通过偏移量来translate()画布上下文,使用scale()来放大或缩小,然后translate()回鼠标偏移的反面。 Note that you need to transform the cursor position from screen space into the transformed canvas context. 请注意,您需要将光标位置从屏幕空间转换为已转换的画布上下文。

ctx.translate(pt.x,pt.y);
ctx.scale(factor,factor);
ctx.translate(-pt.x,-pt.y);

Demo: http://phrogz.net/tmp/canvas_zoom_to_cursor.html 演示: http//phrogz.net/tmp/canvas_zoom_to_cursor.html

I've put up a full working example on my website for you to examine, supporting dragging, click to zoom in, shift-click to out, or scroll wheel up/down. 我在我的网站上提供了一个完整的工作示例 ,供您检查,支持拖动,单击放大,按住Shift键单击,或向上/向下滚动滚轮。

The only (current) issue is that Safari zooms too fast compared to Chrome or Firefox. 唯一(当前)问题是Safari与Chrome或Firefox相比缩放得太快

I hope, these JS libraries will help you: (HTML5, JS) 我希望,这些JS库可以帮助你:(HTML5,JS)

  1. Loupe 放大镜

http://www.netzgesta.de/loupe/ http://www.netzgesta.de/loupe/

  1. CanvasZoom CanvasZoom

https://github.com/akademy/CanvasZoom https://github.com/akademy/CanvasZoom

  1. Scroller 滚轮

https://github.com/zynga/scroller https://github.com/zynga/scroller

As for me, I'm using loupe. 至于我,我正在使用放大镜。 It's awesome! 这很棒! For you the best case - scroller. 对你来说最好的情况 - 卷轴。

I recently needed to archive same results as Phrogz had already done but instead of using context.scale() , I calculated each object size based on ratio. 我最近需要存档与Phrogz已经完成的相同的结果,但不是使用context.scale() ,而是根据比率计算每个对象的大小。

This is what I came up with. 这就是我提出的。 Logic behind it is very simple. 它背后的逻辑非常简单。 Before scaling, I calculate point distance from edge in percentages and later adjust viewport to correct place. 在缩放之前,我以百分比计算边缘的点距离,然后将视口调整到正确的位置。

It took me quite a while to come up with it, hope it saves someones time. 我花了很长时间来提出它,希望它节省了一些人的时间。

 $(function () { var canvas = $('canvas.main').get(0) var canvasContext = canvas.getContext('2d') var ratio = 1 var vpx = 0 var vpy = 0 var vpw = window.innerWidth var vph = window.innerHeight var orig_width = 4000 var orig_height = 4000 var width = 4000 var height = 4000 $(window).on('resize', function () { $(canvas).prop({ width: window.innerWidth, height: window.innerHeight, }) }).trigger('resize') $(canvas).on('wheel', function (ev) { ev.preventDefault() // for stackoverflow var step if (ev.originalEvent.wheelDelta) { step = (ev.originalEvent.wheelDelta > 0) ? 0.05 : -0.05 } if (ev.originalEvent.deltaY) { step = (ev.originalEvent.deltaY > 0) ? 0.05 : -0.05 } if (!step) return false // yea.. var new_ratio = ratio + step var min_ratio = Math.max(vpw / orig_width, vph / orig_height) var max_ratio = 3.0 if (new_ratio < min_ratio) { new_ratio = min_ratio } if (new_ratio > max_ratio) { new_ratio = max_ratio } // zoom center point var targetX = ev.originalEvent.clientX || (vpw / 2) var targetY = ev.originalEvent.clientY || (vph / 2) // percentages from side var pX = ((vpx * -1) + targetX) * 100 / width var pY = ((vpy * -1) + targetY) * 100 / height // update ratio and dimentsions ratio = new_ratio width = orig_width * new_ratio height = orig_height * new_ratio // translate view back to center point var x = ((width * pX / 100) - targetX) var y = ((height * pY / 100) - targetY) // don't let viewport go over edges if (x < 0) { x = 0 } if (x + vpw > width) { x = width - vpw } if (y < 0) { y = 0 } if (y + vph > height) { y = height - vph } vpx = x * -1 vpy = y * -1 }) var is_down, is_drag, last_drag $(canvas).on('mousedown', function (ev) { is_down = true is_drag = false last_drag = { x: ev.clientX, y: ev.clientY } }) $(canvas).on('mousemove', function (ev) { is_drag = true if (is_down) { var x = vpx - (last_drag.x - ev.clientX) var y = vpy - (last_drag.y - ev.clientY) if (x <= 0 && vpw < x + width) { vpx = x } if (y <= 0 && vph < y + height) { vpy = y } last_drag = { x: ev.clientX, y: ev.clientY } } }) $(canvas).on('mouseup', function (ev) { is_down = false last_drag = null var was_click = !is_drag is_drag = false if (was_click) { } }) $(canvas).css({ position: 'absolute', top: 0, left: 0 }).appendTo(document.body) function animate () { window.requestAnimationFrame(animate) canvasContext.clearRect(0, 0, canvas.width, canvas.height) canvasContext.lineWidth = 1 canvasContext.strokeStyle = '#ccc' var step = 100 * ratio for (var x = vpx; x < width + vpx; x += step) { canvasContext.beginPath() canvasContext.moveTo(x, vpy) canvasContext.lineTo(x, vpy + height) canvasContext.stroke() } for (var y = vpy; y < height + vpy; y += step) { canvasContext.beginPath() canvasContext.moveTo(vpx, y) canvasContext.lineTo(vpx + width, y) canvasContext.stroke() } canvasContext.strokeRect(vpx, vpy, width, height) canvasContext.beginPath() canvasContext.moveTo(vpx, vpy) canvasContext.lineTo(vpx + width, vpy + height) canvasContext.stroke() canvasContext.beginPath() canvasContext.moveTo(vpx + width, vpy) canvasContext.lineTo(vpx, vpy + height) canvasContext.stroke() canvasContext.restore() } animate() }) 
 <!DOCTYPE html> <html> <head> <title></title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> </head> <body> <canvas class="main"></canvas> </body> </html> 

I took @Phrogz's answer as a basis and made a small library that enables canvas with dragging, zooming and rotating. 我以@ Phrogz的答案为基础,制作了一个小型库,可以通过拖动,缩放和旋转来实现画布。 Here is the example. 这是一个例子。

var canvas = document.getElementById('canvas')
//assuming that @param draw is a function where you do your main drawing.
var control = new CanvasManipulation(canvas, draw)
control.init()
control.layout()
//now you can drag, zoom and rotate in canvas

You can find more detailed examples and documentation on the project's page 您可以在项目页面上找到更详细的示例和文档

Faster快点

Using ctx.setTransform gives you more performance than multiple matrix calls ctx.translate , ctx.scale , ctx.translate .与多个矩阵调用ctx.translatectx.scalectx.translate ctx.translate ,使用ctx.setTransform更高的性能。

No need for complex transformation inversions as and expensive DOM matrix calls tp converts point between zoomed and screen coordinate systems.不需要复杂的转换反转,因为昂贵的 DOM 矩阵调用 tp 在缩放和屏幕坐标系之间转换点。

Flexible灵活的

Flexibility as you don't need to use ctx.save and ctx.restore if you are rendering content at using different transforms.灵活性,因为如果您使用不同的转换渲染内容,则不需要使用ctx.savectx.restore Returning to the transform with ctx.setTransform rather than the potentially frame rate wreaking ctx.restore call使用ctx.setTransform而不是潜在的帧率ctx.restore调用返回到转换

Easy to invert the transform and get the world coordinates of a (screen) pixel position and the other way round.易于反转变换并获得(屏幕)像素位置的世界坐标,反之亦然。

Examples例子

Using mouse and mouse wheel to zoom in and out at mouse position使用鼠标和鼠标滚轮在鼠标位置放大和缩小

An example using this method to scale page content at a point (mouse) via CSS transform CSS Demo at bottom of answer also has a copy of the demo from the next example.使用此方法通过 CSS 转换在答案底部的 CSS 演示点(鼠标)缩放页面内容的示例也有下一个示例中演示的副本。

And an example of this method used to scale canvas content at a point using setTransform以及使用 setTransform 在某个点缩放画布内容的此方法的示例

How如何

Given a scale and pixel position you can get the new scale as follow...给定比例和像素位置,您可以按如下方式获得新比例...

const origin = {x:0, y:0};         // canvas origin
var scale = 1;                     // current scale
function scaleAt(x, y, scaleBy) {  // at pixel coords x, y scale by scaleBy
    scale *= scaleBy;
    origin.x = x - (x - origin.x) * scaleBy;
    origin.y = y - (y - origin.y) * scaleBy;
}

To position the canvas and draw content定位画布并绘制内容

ctx.setTransform(scale, 0, 0, scale, origin.x, origin.y);
ctx.drawImage(img, 0, 0);

To use if you have the mouse coordinates如果您有鼠标坐标,则使用

const zoomBy = 1.1;                    // zoom in amount
scaleAt(mouse.x, mouse.y, zoomBy);     // will zoom in at mouse x, y
scaleAt(mouse.x, mouse.y, 1 / zoomBy); // will zoom out by same amount at mouse x,y

To restore the default transform恢复默认转换

ctx.setTransform(1,0,0,1,0,0);

The inversions反转

To get the coordinates of a point in the zoomed coordinate system and the screen position of a point in the zoomed coordinate system获取缩放坐标系中某个点的坐标和缩放坐标系中某个点的屏幕位置

Screen to world屏幕到世界

function toWorld(x, y) {  // convert to world coordinates
    x = (x - origin.x) / scale;
    y = (y - origin.y) / scale;
    return {x, y};
}

World to screen屏幕上的世界

function toScreen(x, y) {
    x = x * scale + origin.x;
    y = y * scale + origin.y;
    return {x, y};
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM