82 votes

Zoom du canevas sur le curseur de la souris

Je suis en train de programmer un projet HTML5 < canvas > qui implique un zoom avant et arrière des images en utilisant la molette de défilement. Je veux zoomer vers le curseur comme le fait google maps mais je suis complètement perdu sur la façon de calculer les mouvements.

Ce que j'ai : x et y de l'image (coin supérieur gauche) ; largeur et hauteur de l'image ; x et y du curseur par rapport au centre de la toile.

16 votes

Vous devriez accepter cette réponse ou réviser votre question

253voto

Phrogz Points 112337

En bref, vous voulez translate() le contexte de la toile par votre décalage, scale() pour effectuer un zoom avant ou arrière, puis translate() de l'opposé du décalage de la souris. Notez que vous devez transformer la position du curseur de l'espace écran dans le contexte du canevas transformé.

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

Démonstration : http://phrogz.net/tmp/canvas_zoom_to_cursor.html

J'ai mis en place un exemple complet de fonctionnement sur mon site web pour que vous puissiez les examiner, en soutenant le glissement, le clic pour zoomer, le shift-clic pour sortir, ou la molette de défilement vers le haut/bas.

Le seul problème (actuel) est que Safari effectue des zooms trop rapides par rapport à Chrome ou Firefox.

3 votes

Bel effort avec l'exemple. Merci !

4 votes

Wow, @phrogz, vous vous êtes surpassé !

1 votes

Hey @Phrogz c'est génial ! Je me demande juste si l'on peut limiter le déplacement de manière à ce que l'on ne puisse pas déplacer l'image en dehors des limites. S'il n'y a plus d'image à déplacer, le déplacement devrait s'arrêter là au lieu de permettre de déplacer indéfiniment. J'ai tenté de le faire, mais il semble que je n'arrive pas à faire les bons calculs :-(

14voto

Alexey Points 386

J'espère que ces bibliothèques JS vous aideront : (HTML5, JS)

  1. Loupe

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

  1. CanvasZoom

https://github.com/akademy/CanvasZoom

  1. Déflecteur

https://github.com/zynga/scroller

Pour ma part, j'utilise une loupe. C'est génial ! Pour vous le meilleur cas - scroller.

12voto

Kristian Points 643

J'ai récemment eu besoin d'archiver les mêmes résultats que Phrogz mais au lieu d'utiliser context.scale() J'ai calculé la taille de chaque objet en fonction du ratio.

C'est ce que j'ai trouvé. La logique derrière tout ça est très simple. Avant de mettre à l'échelle, je calcule la distance du point par rapport au bord en pourcentage et j'ajuste ensuite la fenêtre à l'endroit correct.

J'ai mis du temps à trouver cette idée, j'espère que cela fera gagner du temps à quelqu'un.

$(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>

7voto

vogdb Points 670

J'ai pris la réponse de @Phrogz comme base et j'ai fait une petite bibliothèque qui permet le canvas avec le déplacement, le zoom et la rotation. Voici l'exemple.

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

Vous trouverez des exemples et une documentation plus détaillés sur le site du projet page

1voto

user3877726 Points 11

Plus rapide

Utilisation de ctx.setTransform vous donne plus de performance que de multiples appels de matrice ctx.translate , ctx.scale , ctx.translate .

Pas besoin d'inversions de transformation complexes ni d'appels coûteux à la matrice DOM pour convertir un point entre les systèmes de coordonnées du zoom et de l'écran.

Flexible

Flexibilité car vous n'avez pas besoin d'utiliser ctx.save y ctx.restore si vous rendez le contenu à l'aide de différentes transformations. Le retour à la transformation avec ctx.setTransform plutôt que de détruire la fréquence d'images ctx.restore appelez

Il est facile d'inverser la transformation et d'obtenir les coordonnées mondiales d'une position de pixel (à l'écran) et l'inverse.

Exemples

Utilisation de la souris et de la molette de la souris pour effectuer un zoom avant et arrière à la position de la souris

Un exemple d'utilisation de cette méthode pour mettre à l'échelle le contenu d'une page en un point (souris) via une transformation CSS La démo CSS au bas de la réponse contient également une copie de la démo de l'exemple suivant.

Et un exemple de cette méthode utilisée pour mettre à l'échelle le contenu du canevas en un point à l'aide de setTransform

Comment

Étant donné une échelle et une position de pixel, vous pouvez obtenir la nouvelle échelle comme suit...

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

Pour positionner le canevas et dessiner le contenu

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

A utiliser si vous avez les coordonnées de la souris

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

Pour restaurer la transformation par défaut

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

Les inversions

Pour obtenir les coordonnées d'un point dans le système de coordonnées zoomé et la position à l'écran d'un point dans le système de coordonnées zoomé

De l'écran au monde

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

Du monde à l'écran

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X