165 votes

Zoomer sur un point (en utilisant l'échelle et la translation)

Je souhaite pouvoir zoomer sur le point situé sous la souris dans un canevas HTML 5, comme un zoom sur Google Maps . Comment puis-je y parvenir ?

2 votes

Je l'ai utilisé pour zoomer sur ma toile et cela fonctionne très bien ! La seule chose que je dois ajouter est que le calcul de la quantité de zoom n'est pas comme on pourrait s'y attendre. "var zoom = 1 + wheel/2 ;" c'est-à-dire que cela donne 1.5 pour le zoom avant et 0.5 pour le zoom arrière. J'ai modifié cela dans ma version de sorte que j'ai 1,5 pour le zoom avant et 1/1,5 pour le zoom arrière, ce qui rend la quantité de zoom avant et de zoom arrière égale. Ainsi, si vous faites un zoom avant et un zoom arrière, vous aurez la même image qu'avant le zoom.

2 votes

Notez que ceci ne fonctionne pas sur Firefox, mais la méthode peut facilement être appliquée à Plugin jQuery mousewheel . Merci de partager !

2 votes

Var zoom = Math.pow(1.5f, wheel) ; // Utilisez ceci pour calculer le zoom. L'avantage est qu'un zoom par roue=2 est identique à un zoom double par roue=1. En outre, un zoom avant de +2 et un zoom arrière de +2 rétablissent l'échelle originale.

136voto

Tatarize Points 490

La meilleure solution consiste à déplacer simplement la position de la fenêtre d'affichage en fonction de la modification du zoom. Le point de zoom est simplement le point dans l'ancien zoom et le nouveau zoom que vous voulez garder identique. En d'autres termes, la fenêtre d'affichage pré-zoomée et la fenêtre d'affichage post-zoomée ont le même point de zoom par rapport à la fenêtre d'affichage. Étant donné que nous sommes à l'échelle par rapport à l'origine. Vous pouvez ajuster la position de la fenêtre en conséquence :

scalechange = newscale - oldscale;
offsetX = -(zoomPointX * scalechange);
offsetY = -(zoomPointY * scalechange);

Vous pouvez donc effectuer un panoramique vers le bas et vers la droite lorsque vous effectuez un zoom avant, en fonction de l'ampleur de votre zoom avant par rapport au point où vous avez effectué le zoom.

enter image description here

77voto

csiz Points 914

J'ai finalement résolu le problème :

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>

La clé, comme @Tatarize a souligné consiste à calculer la position de l'axe de telle sorte que le point de zoom (pointeur de la souris) reste au même endroit après le zoom.

A l'origine, la souris est à une distance mouse/scale à partir du coin, nous voulons que le point sous la souris reste au même endroit après le zoom, mais c'est à mouse/new_scale loin du coin. Par conséquent, nous devons déplacer le origin (coordonnées du coin) pour en tenir compte.

originx -= mousex/(scale*zoom) - mousex/scale;
originy -= mousey/(scale*zoom) - mousey/scale;
scale *= zoom

Le code restant doit ensuite appliquer la mise à l'échelle et la translation au contexte de dessin afin que son origine coïncide avec le coin du canevas.

26voto

Sunday Ironfoot Points 5625

Il s'agit en fait d'un problème très difficile (sur le plan mathématique), et je travaille presque sur la même chose. J'ai posé une question similaire sur Stackoverflow mais je n'ai pas obtenu de réponse, mais j'ai posté dans DocType (StackOverflow pour HTML/CSS) et j'ai obtenu une réponse. Consultez le site http://doctype.com/javascript-image-zoom-css3-transforms-calculate-origin-example

Je suis au milieu de la construction d'un plugin jQuery qui fait cela (zoom de style Google Maps utilisant des transformations CSS3). J'ai réussi à faire fonctionner le zoom sur le curseur de la souris, mais j'essaie encore de trouver comment permettre à l'utilisateur de faire glisser la toile comme on peut le faire dans Google Maps. Quand je l'aurai fait fonctionner, je posterai le code ici, mais regardez le lien ci-dessus pour la partie zoom à la souris.

Je n'avais pas réalisé qu'il y avait des méthodes d'échelle et de translation sur le contexte Canvas, vous pouvez réaliser la même chose en utilisant CSS3, par exemple en utilisant 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)');

Veillez à définir le transform-origin CSS3 sur 0, 0 (-moz-transform-origin : 0 0). L'utilisation de la transformation CSS3 vous permet de zoomer sur n'importe quoi, assurez-vous simplement que le DIV conteneur est réglé sur overflow : hidden pour empêcher les bords zoomés de déborder sur les côtés.

C'est à vous de décider si vous utilisez les transformations CSS3 ou les méthodes d'échelle et de translation propres à canvas, mais consultez le lien ci-dessus pour les calculs.


Mise à jour : Meh ! Je vais simplement poster le code ici plutôt que de vous faire suivre un lien :

$(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;
    });
});

Vous devrez bien sûr l'adapter pour utiliser les méthodes d'échelle et de translation du canevas.


Mise à jour 2 : Je viens de remarquer que j'utilise transform-origin en même temps que translate. J'ai réussi à mettre en place une version qui n'utilise que scale et translate de manière indépendante, regardez-la ici. http://www.dominicpettifer.co.uk/Files/Mosaic/MosaicTest.html Attendez que les images soient téléchargées, puis utilisez la molette de votre souris pour zoomer. Vous pouvez également effectuer un panoramique en déplaçant l'image. Il utilise les transformations CSS3, mais vous devriez pouvoir utiliser les mêmes calculs pour votre Canvas.

1 votes

J'ai finalement résolu le problème, cela m'a pris 3 minutes après avoir fait autre chose pendant 2 semaines.

0 votes

Le lien de @Synday Ironfoot sur sa mise à jour ne fonctionne pas. Ce lien : dominicpettifer.co.uk/Files/Mosaique/MosaiqueTest.html Je veux cette implémentation. Pouvez-vous poster ici le code ?

2 votes

À partir d'aujourd'hui (sept.2014), le lien vers MosaicTest.html est mort.

13voto

avejidah Points 547

J'aime La réponse de Tatarize mais je vais proposer une alternative. Il s'agit d'un problème trivial d'algèbre linéaire, et la méthode que je présente fonctionne bien avec le pan, le zoom, l'inclinaison, etc. C'est-à-dire qu'elle fonctionne bien si votre image est déjà transformée.

Lorsqu'une matrice est mise à l'échelle, l'échelle se situe au point (0, 0). Ainsi, si vous avez une image et que vous la mettez à l'échelle par un facteur 2, le point en bas à droite doublera dans les directions x et y (en utilisant la convention selon laquelle [0, 0] est le haut à gauche de l'image).

Si vous souhaitez plutôt zoomer l'image autour du centre, la solution est la suivante : (1) translater l'image de sorte que son centre soit à (0, 0) ; (2) mettre l'image à l'échelle par des facteurs x et y ; (3) translater l'image en retour, c'est à dire

myMatrix
  .translate(image.width / 2, image.height / 2)    // 3
  .scale(xFactor, yFactor)                         // 2
  .translate(-image.width / 2, -image.height / 2); // 1

De manière plus abstraite, la même stratégie fonctionne pour n'importe quel point. Si, par exemple, vous voulez mettre l'image à l'échelle en un point P :

myMatrix
  .translate(P.x, P.y)
  .scale(xFactor, yFactor)
  .translate(-P.x, -P.y);

Enfin, si l'image est déjà transformée d'une manière ou d'une autre (par exemple, si elle a subi une rotation, une inclinaison, une translation ou une mise à l'échelle), la transformation actuelle doit être préservée. Plus précisément, la transformation définie ci-dessus doit être post-multipliée (ou multipliée à droite) par la transformation actuelle.

myMatrix
  .translate(P.x, P.y)
  .scale(xFactor, yFactor)
  .translate(-P.x, -P.y)
  .multiply(myMatrix);

Voilà, c'est fait. Voici un plunk qui montre cela en action. Faites défiler les points avec la molette de la souris et vous verrez qu'ils restent toujours en place. (Testé dans Chrome uniquement.) http://plnkr.co/edit/3aqsWHPLlSXJ9JCcJzgH?p=preview

9voto

J'ai rencontré ce problème en utilisant c++, ce que je n'aurais probablement pas dû faire si j'avais utilisé les matrices OpenGL pour commencer... Quoi qu'il en soit, si vous utilisez un contrôle dont l'origine est le coin supérieur gauche, et que vous voulez un pan/zoom comme google maps, voici la disposition (en utilisant allegro comme gestionnaire d'événement) :

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

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