ChatGPT解决这个技术问题 Extra ChatGPT

缩放画布到鼠标光标

我正在编写一个 HTML5 项目,该项目涉及使用滚轮放大和缩小图像。我想像谷歌地图一样放大光标,但我完全不知道如何计算运动。

我所拥有的:图像 x 和 y(左上角);图像宽度和高度;光标 x 和 y 相对于画布的中心。

你应该接受这个答案或修改你的问题

C
Community

简而言之,您希望 translate() 画布上下文按您的偏移量,scale() 放大或缩小,然后 translate() 按鼠标偏移量的相反方向返回。请注意,您需要将光标位置从屏幕空间转换为转换后的画布上下文。

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

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

我在我的网站上放了一个 full working example 供您查看,支持拖动、单击放大、shift-click 缩小或上/下滚轮。

唯一的(当前)问题是Safari zooms too fast与 Chrome 或 Firefox 相比。


很好的例子。谢谢!
哇,@phrogz,你超越了!
嘿@Phrogz 这太棒了!我只是想知道我们是否可以限制拖动,以便不能将图像拖出边界。如果没有更多图像可以拖动,则拖动应该停止在那里,而不是允许无限拖动。我对它进行了尝试,但似乎我无法正确计算:-(
@Christoph 这很容易。获取比例 - 您可以从以下位置获取比例: var scale = ctx.getTransform().a;然后取图像的当前左上角位置: var curX = ctx.getTransform().e; var curY = ctx.getTransform().f;估计位置变化:var deltaX = pt.x - dragStart.x; var deltaY = pt.y - dragStart.y;然后是原始图像大小,我们以宽度为例(当 scale=1): imageW 并且有画布宽度:canvasW 那么条件应该为假以允许拖动:curX + deltaX + imageW * scale < canvasW 和一个更多( curX + deltaX > 0 || curY + deltaY > 0)
这会很难在移动设备上用手势实现吗?比如,允许两指捏合只放大图像而不放大整个网站?
A
Alexey

我希望,这些 JS 库会帮助你:(HTML5,JS)

放大镜

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

画布缩放

https://github.com/akademy/CanvasZoom

滚动条

https://github.com/zynga/scroller

至于我,我用的是放大镜。这很棒!为您提供最好的案例 - 滚动条。


K
Kristian

我最近需要存档与 Phrogz 已经完成的结果相同的结果,但我没有使用 context.scale(),而是根据比率计算每个对象的大小。

这就是我想出的。其背后的逻辑非常简单。在缩放之前,我以百分比计算点到边缘的距离,然后将视口调整到正确的位置。

我花了很长时间才想出它,希望它能节省别人的时间。

$(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 // 是的.. 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 } // 缩放中心点 var targetX = ev.originalEvent.clientX || (vpw / 2) var tar getY = ev.originalEvent.clientY || (vph / 2) // 侧面百分比 var pX = ((vpx * -1) + targetX) * 100 / width var pY = ((vpy * -1) + targetY) * 100 / height // 更新比例和尺寸ratio = new_ratio width = orig_width * new_ratio height = orig_height * new_ratio // 将视图转换回中心点 var x = ((width * pX / 100) - targetX) var y = ((height * pY / 100) - targetY) / / 不要让视口越过边缘 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 + 高度) { vpy = y } last_drag = { x: ev.clientX, y: ev.clientY } } }) $(可以vas).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',顶部:0,左侧: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 < 宽度 + 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() })


v
vogdb

我以@Phrogz 的回答为基础,制作了一个小型库,使画布可以拖动、缩放和旋转。这是示例。

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

您可以在项目的 page 上找到更详细的示例和文档


B
Blindman67

快点

与多个矩阵调用 ctx.translatectx.scalectx.translate 相比,使用 ctx.setTransform 可以获得更高的性能。

不需要复杂的转换反转,因为昂贵的 DOM 矩阵调用 tp 在缩放坐标系和屏幕坐标系之间转换点。

灵活的

灵活性,因为如果您使用不同的转换来呈现内容,则无需使用 ctx.savectx.restore。使用 ctx.setTransform 返回转换,而不是潜在的帧速率破坏 ctx.restore 调用

易于反转变换并获得(屏幕)像素位置的世界坐标,反之亦然。

例子

使用鼠标和鼠标滚轮在鼠标位置放大和缩小

在答案底部使用此方法 scale page content at a point (mouse) via CSS transform CSS Demo 的示例也有来自下一个示例的演示副本。

此方法的示例用于scale canvas content at a point using setTransform

如何

给定比例和像素位置,您可以获得新比例,如下所示...

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;
}

定位画布并绘制内容

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

如果您有鼠标坐标,请使用

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

恢复默认变换

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

倒置

获取缩放坐标系中点的坐标和缩放坐标系中点的屏幕位置

屏幕到世界

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

屏幕上的世界

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