【问题标题】:Zoom Canvas to Mouse Cursor缩放画布到鼠标光标
【发布时间】:2011-07-08 13:54:05
【问题描述】:

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

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

【问题讨论】:

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

标签: javascript html canvas zooming scrollwheel


【解决方案1】:

简而言之,您希望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-点击缩小、滚轮上/下。

唯一的(当前)问题是 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 0 || curY + deltaY > 0)
  • 这在移动设备上是否难以通过手势实现?比如,允许两指捏合只放大图像而不放大整个网站?
【解决方案2】:

我希望,这些 JS 库对你有所帮助: (HTML5, JS)

  1. 放大镜

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

  1. 画布缩放

https://github.com/akademy/CanvasZoom

  1. 滚动条

https://github.com/zynga/scroller

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

【讨论】:

    【解决方案3】:

    我最近需要存档与 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 // 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>

    【讨论】:

      【解决方案4】:

      我以@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 上找到更详细的示例和文档

      【讨论】:

        【解决方案5】:

        更快

        使用ctx.setTransform 比多个矩阵调用ctx.translatectx.scalectx.translate 提供更高的性能。

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

        灵活

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

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

        示例

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

        答案底部使用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};
        }
        

        【讨论】:

          猜你喜欢
          • 2014-08-27
          • 2016-01-01
          • 2014-04-16
          • 2012-07-15
          • 1970-01-01
          • 2021-11-18
          • 2014-08-18
          • 2017-10-24
          相关资源
          最近更新 更多