web-dev-qa-db-fra.com

Effectuer un zoom avant sur un point de la molette de la souris (à l'aide de l'échelle et de la translation)

Cette question est similaire à celle-ci: Zoom sur un point (avec l’échelle et la translation) Ou même celle-ci: Zoom sur l’image centré sur la position de la souris Mais je ne veux pas pour le faire sur une toile mais une image normale (ou plutôt le conteneur div de l'image) . Donc, le zoom devrait être comme google maps . Je suis en train de pirater/améliorer le zoom iDangerous Swiper ( http://idangero.us/swiper/ ), et c’est mon point de départ. C’est ce que j’ai eu jusqu’à présent: https://jsfiddle.net/xta2ccdt/3/

Zoom uniquement avec la molette de la souris. La première fois que vous effectuez un zoom avant, il effectue un zoom parfaitement, mais je ne vois pas comment calculer chaque zoom après le premier.

Voici mon code: JS:

$(document).ready(function(){
    $("#slideContainer").on("mousewheel DOMMouseScroll", function (e) {
    e.preventDefault();
    var delta = e.delta || e.originalEvent.wheelDelta;
    var zoomOut;
    if (delta === undefined) {
      //we are on firefox
      delta = e.originalEvent.detail;
      zoomOut = delta ? delta < 0 : e.originalEvent.deltaY > 0;
      zoomOut = !zoomOut;
    } else {
      zoomOut = delta ? delta < 0 : e.originalEvent.deltaY > 0;
    }
    var touchX = e.type === 'touchend' ? e.changedTouches[0].pageX : e.pageX;
    var touchY = e.type === 'touchend' ? e.changedTouches[0].pageY : e.pageY;
    var scale = 1, translateX, translateY;
    if(zoomOut){
        //we are zooming out
      //not interested in this yet
    }else{
        //we are zooming in
      scale = scale + 0.5;
      var dimensionMultiplier = scale - 0.5;//when image is scaled up offsetWidth/offsetHeight doesn't take this into account so we must multiply by scale to get the correct width/height
      var slideWidth = $("#slide")[0].offsetWidth * dimensionMultiplier;
      var slideHeight = $("#slide")[0].offsetHeight * dimensionMultiplier;

      var offsetX = $("#slide").offset().left;//distance from the left of the viewport to the slide
      var offsetY = $("#slide").offset().top;//distance from the top of the viewport to the slide
      var diffX = offsetX + slideWidth / 2 - touchX;//this is distance from the mouse to the center of the image
      var diffY = offsetY + slideHeight / 2 - touchY;//this is distance from the mouse to the center of the image

      //how much to translate by x and y so that poin on image is alway under the mouse
      //we must multiply by 0.5 because the difference between previous and current scale is always 0.5
      translateX = ((diffX) * (0.5));
      translateY = ((diffY) * (0.5));    
    }
    $("#slide").css("transform", 'translate3d(' + translateX + 'px, ' + translateY + 'px,0) scale(' + scale + ')').css('transition-duration', '300ms');
  });


});

HTML:

<div id="slideContainer">
  <div id="slide">
    <img src="http://content.worldcarfans.co/2008/6/medium/9080606.002.1M.jpg"></img>
  </div>
</div>

CSS:

#slideContainer{
  width:500px;
  height:500px;
  overflow:hidden;
}
#slide{
  width:100%;
  height:100%;
}
img{
  width:auto;
  height:auto;
  max-width:100%;
}

J'ai également compris que si je soustrais les valeurs translateX et translateY précédentes aux valeurs actuelles, je peux zoomer autant que je le souhaite et le zoom sera parfait, mais si je zoome sur un point, puis change la position de la souris et le zoom Encore une fois, le zoom ne sera plus comme il est supposé. Exemple: https://jsfiddle.net/xta2ccdt/4/

Si je change la position de la souris et calcule la différence X et Y entre l'ancienne et la nouvelle position de la souris et que je l'ajoute au calcul du diff, le zoom sera correctement effectué une deuxième fois. Mais la troisième fois, on dirait que la différence est encore soustraite du calcul total, ce qui entraînera le déplacement de l’image par la translation, après quoi, si nous maintenons la souris dans la même position, le zoom sera correctement corrigé à nouveau . pense que je vais juste ajouter la différence entre l'ancienne et la nouvelle position de la souris chaque fois que je calcule le nouveau "diff", et ce genre de travaux, il n'y a plus de saut comme ce fut quand j'ai arrêté d'ajouter la différence de position de la souris ne zoomant toujours pas sur la même position, à chaque nouveau zoom, il déplace (décale) l'image d'une petite quantité. Je suppose que c'est parce qu'il y a une nouvelle valeur de zoom à chaque fois, mais le décalage n'est pas linéaire, il est chaque fois plus petit et s'approche de zéro et je ne peux pas comprendre comment compenser le décalage . Voici le nouvel exemple: https://jsfiddle.net/xta2ccdt/5/ Nouvelle image dans l'exemple: l'ancienne image n'est plus disponible: https://jsfiddle.net/xta2ccdt/14/

8
Dario Huzjak

Vous en étiez proche, mais il est préférable de stocker séparément les valeurs x, y et scale et de calculer les transformations en fonction de ces valeurs. Cela rend les choses beaucoup plus faciles + économise des ressources (pas besoin de rechercher les propriétés dom encore et encore),

J'ai mis le code dans un module Nice:

function ScrollZoom(container,max_scale,factor){
    var target = container.children().first()
    var size = {w:target.width(),h:target.height()}
    var pos = {x:0,y:0}
    var zoom_target = {x:0,y:0}
    var zoom_point = {x:0,y:0}
    var scale = 1
    target.css('transform-Origin','0 0')
    target.on("mousewheel DOMMouseScroll",scrolled)

    function scrolled(e){
        var offset = container.offset()
        zoom_point.x = e.pageX - offset.left
        zoom_point.y = e.pageY - offset.top

        e.preventDefault();
        var delta = e.delta || e.originalEvent.wheelDelta;
        if (delta === undefined) {
          //we are on firefox
          delta = e.originalEvent.detail;
        }
        delta = Math.max(-1,Math.min(1,delta)) // cap the delta to [-1,1] for cross browser consistency

        // determine the point on where the slide is zoomed in
        zoom_target.x = (zoom_point.x - pos.x)/scale
        zoom_target.y = (zoom_point.y - pos.y)/scale

        // apply zoom
        scale += delta*factor * scale
        scale = Math.max(1,Math.min(max_scale,scale))

        // calculate x and y based on zoom
        pos.x = -zoom_target.x * scale + zoom_point.x
        pos.y = -zoom_target.y * scale + zoom_point.y


        // Make sure the slide stays in its container area when zooming out
        if(pos.x>0)
            pos.x = 0
        if(pos.x+size.w*scale<size.w)
            pos.x = -size.w*(scale-1)
        if(pos.y>0)
            pos.y = 0
         if(pos.y+size.h*scale<size.h)
            pos.y = -size.h*(scale-1)

        update()
    }

    function update(){
        target.css('transform','translate('+(pos.x)+'px,'+(pos.y)+'px) scale('+scale+','+scale+')')
    }
}

Utilisez-le en appelant

new ScrollZoom($('#container'),4,0.5)

Les paramètres sont:

  1. conteneur: le wrapper de l'élément à zoomer. Le script cherchera le premier enfant du conteneur et lui appliquera les transformations.
  2. max_scale: L'échelle maximale (zoom 4 = 400%)
  3. facteur: la vitesse de zoom (1 = + 100% de zoom par tick de la souris)

JSFiddle ici

7
Manuel Otto

Je pense que cela vous rapprochera un peu plus de ce que vous essayez d’atteindre.

Changements clés

  1. J'ai sorti la balance en dehors du rappel; Je ne pense pas que vous souhaitiez réinitialiser votre balance à chaque événement de roue.
  2. Au lieu de calculer manuellement la traduction, essayez de centrer le transform-Origin sur votre souris (à moins que vous ne souhaitiez le garder centré, valeur par défaut).

var scale = 1;

$(document).ready(function(){
    $("#slideContainer").on("mousewheel DOMMouseScroll", function (e) {
    e.preventDefault();
    var delta = e.delta || e.originalEvent.wheelDelta;
    var zoomOut;
    if (delta === undefined) {
      //we are on firefox
      delta = e.originalEvent.detail;
      zoomOut = delta ? delta < 0 : e.originalEvent.deltaY > 0;
      zoomOut = !zoomOut;
    } else {
      zoomOut = delta ? delta < 0 : e.originalEvent.deltaY > 0;
    }
    var touchX = e.type === 'touchend' ? e.changedTouches[0].pageX : e.pageX;
    var touchY = e.type === 'touchend' ? e.changedTouches[0].pageY : e.pageY;
    var translateX, translateY;
    if(zoomOut){
      // we are zooming out
      scale = scale - 0.01;
      
      var offsetWidth = $("#slide")[0].offsetWidth;
      var offsetHeight = $("#slide")[0].offsetHeight;

      $("#slide")
        .css("transform-Origin", touchX + 'px ' + touchY + 'px')
        .css("transform", 'scale(' + scale + ')');
      
    }else{
      // we are zooming in
      scale = scale + 0.01;
      
      var offsetWidth = $("#slide")[0].offsetWidth;
      var offsetHeight = $("#slide")[0].offsetHeight;

      $("#slide")
        .css("transform-Origin", touchX + 'px ' + touchY + 'px')
        .css("transform", 'scale(' + scale + ')');
    }
    
  });


});
#slideContainer{
  width:200px;
  height:200px;
  overflow:hidden;
}
#slide{
  width:100%;
  height:100%;
}
img{
  width:auto;
  height:auto;
  max-width:100%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="slideContainer">
  <div id="slide">
    <img src="https://via.placeholder.com/200x200"></img>
  </div>
</div>

1
Steve Ladavich

Le code que j'utilise pour effectuer un zoom avant sur la position de la souris est ci-dessous. Il n'utilise pas transform/translate3d mais réajuste la position de l'image dans le div et ajuste ses height et width.

var zoom = 1;
var img, div;
window.onload = function() {
  window.addEventListener('DOMMouseScroll', wheel, false)
  img = document.getElementById("img");
  div = document.getElementById("div");
}

function wheel(event) {
  event.preventDefault();
  var delta = 0;
  if (!event) /* For IE. */
    event = window.event;
  if (event.wheelDelta) { /* IE/Opera. */
    delta = event.wheelDelta / 120;
  } else if (event.detail) { /** Mozilla case. */
    /** In Mozilla, sign of delta is different than in IE.
     * Also, delta is multiple of 3.
     */
    delta = -event.detail / 3;
  }
  /** If delta is nonzero, handle it.
   * Positive Delta = wheel scrolled up,
   * Negative Delte = wheel scrolled down.
   */
  if (delta) {
    // will pass 1 to zoom in and -1 to zoom out	    
    delta = delta / Math.abs(delta)
    zoomImage(delta == 1, event);
  }
}

function zoomImage(zoomIn, e) {
  var oldZoom = zoom;
  var direction = 1 * (zoomIn ? 1 : -1);
  zoom += direction * .2;
  // range = 50% => 600%
  zoom = round(Math.min(6, Math.max(.5, zoom)), 1);
  
  if (zoom == 1) {
    // For a zoom = 1, we reset
    resetZoom(div, img);
    return;
  }

  // make the position of the mouse the center, 
  // or as close as can with keeping maximum image viewable
  // e == div[this.slide]
  // gets the top and left of the div
  var divOffset = getOffset(div);
  var imgStyles = getComputedStyle(img);
  var divStyles = getComputedStyle(div);
  var imgOffset = {
    x: parseInt(imgStyles.left),
    y: parseInt(imgStyles.top)
  };

  // where clicked relative in div
  var yTravel = e.pageY - divOffset.y;
  var xTravel = e.pageX - divOffset.x;

  // where clicked
  var xOldImg = -imgOffset.x + xTravel;
  var yOldImg = -imgOffset.y + yTravel;

  // the clicked position relative to the image 0,0
  // clicked position will remain at the cursor position while image zoom changes

  // calc the same position at the new zoom level
  var ratio = zoom / oldZoom;
  var xNewImg = xOldImg * ratio;
  var yNewImg = yOldImg * ratio;

  // calc new top / left
  var xStart = -(xNewImg - xTravel);
  var yStart = -(yNewImg - yTravel);

  img.style.height = parseInt(divStyles.height) * (zoom) + "px";
  img.style.width = parseInt(divStyles.width) * (zoom) + "px";

  img.style.top = yStart + "px";
  img.style.left = xStart + "px";
  img.style.cursor = "grab";
}

function resetZoom(div, img) {
  img.style.top = "0px";
  img.style.left = "0px";
  img.style.height = div.style.height;
  img.style.width = div.style.width;
  img.style.cursor = "default";
  zoom = 1;
}

function getOffset(element) {
  var rect = element.getBoundingClientRect();
  var posX = rect.left + window.pageXOffset; // alias for window.scrollX; 
  var posY = rect.top + window.pageYOffset; // alias for window.scrollY;	

  return {
    x: posX,
    y: posY,
    left: posX,
    top: posY,
    width: rect.width,
    height: rect.height
  };
}

function round(number, precision) {
  precision = precision ? +precision : 0;

  var sNumber = number + '',
    periodIndex = sNumber.indexOf('.'),
    factor = Math.pow(10, precision);

  if (periodIndex === -1 || precision < 0) {
    return Math.round(number * factor) / factor;
  }

  number = +number;

  // sNumber[periodIndex + precision + 1] is the last digit
  if (sNumber[periodIndex + precision + 1] >= 5) {
    // Correcting float error
    // factor * 10 to use one decimal place beyond the precision
    number += (number < 0 ? -1 : 1) / (factor * 10);
  }

  return +number.toFixed(precision);
}
#div {
  width: 350px;
  height: 262px;
  border: 1px solid black;
  overflow: hidden;
}

#img {
  width: 350px;
  height: 262px;
  position: relative;
}
<div id='div'>
  <img id='img' src="https://www.design.mseifert.com/git-slideshow/img-demo/images01.jpg">
</div>

0
mseifert

Qu'en est-il de l'utilisation de translate3d et perspective, pour gérer les transformations 3D, au lieu d'utiliser scale? En outre, le découplage du zoom de la traduction le simplifie.

$(document).ready(function() {

  var translateX = 0,
    translateY = 0,
    translateZ = 0,
    stepZ = 10,
    initial_obj_X = 0,
    initial_obj_Y = 0,
    initial_mouse_X = 0,
    initial_mouse_Y = 0;

  function apply_coords() {
    $("#slide").css("transform", 'perspective(100px) translate3d(' + translateX + 'px, ' + translateY + 'px, ' + translateZ + 'px)');
  }


  $("#slideContainer").on("mousewheel DOMMouseScroll", function(e) {

    e.preventDefault();
    var delta = e.delta || e.originalEvent.wheelDelta;
    var zoomOut;
    if (delta === undefined) {
      delta = e.originalEvent.detail;
      zoomOut = delta ? delta < 0 : e.originalEvent.deltaY > 0;
      zoomOut = !zoomOut;
    } else {
      zoomOut = delta ? delta < 0 : e.originalEvent.deltaY > 0;
    }
    if (zoomOut) {
      translateZ = translateZ - stepZ;
    } else {
      translateZ = translateZ + stepZ;
    }
    apply_coords();

  });


  var is_dragging = false;
  $("#slideContainer")
    .mousedown(function(e) {
      is_dragging = true;
    })
    .mousemove(function(e) {
      if (is_dragging) {
        e.preventDefault();
        var currentX = e.type === 'touchend' ? e.changedTouches[0].pageX : e.pageX;
        var currentY = e.type === 'touchend' ? e.changedTouches[0].pageY : e.pageY;
        translateX = initial_obj_X + (currentX - initial_mouse_X);
        translateY = initial_obj_Y + (currentY - initial_mouse_Y);
        apply_coords();
      } else {
        initial_mouse_X = e.type === 'touchend' ? e.changedTouches[0].pageX : e.pageX;
        initial_mouse_Y = e.type === 'touchend' ? e.changedTouches[0].pageY : e.pageY;
        initial_obj_X = translateX;
        initial_obj_Y = translateY;
      }
    })
    .mouseup(function() {
      is_dragging = false;
    });


});
#slideContainer {
  width: 200px;
  height: 200px;
  overflow: hidden;
  position: relative;
}

#slide {
  width: 100%;
  height: 100%;
  background: red;
}

img {
  width: auto;
  height: auto;
  max-width: 100%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="slideContainer">
  <div id="slide">
  </div>
</div>

0
Ivan Chaer