javascript 放大鼠标滚轮点(使用缩放和平移)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/46647138/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 06:50:28  来源:igfitidea点击:

Zoom in on a mousewheel point (using scale and translate)

javascripthtmlimagezoom

提问by dari0h

This question is similar to this one: Zoom in on a point (using scale and translate)or even this one: Image zoom centered on mouse positionbut I don't want to do it on a canvas but a normal image (or rather the container div of the image). So zooming should be as google maps. I am actually hacking/enhancing iDangerous Swiper zoom (http://idangero.us/swiper/), and that is my starting point, and this is is what I got so far: https://jsfiddle.net/xta2ccdt/3/

这个问题类似于这个问题:放大一个点(使用缩放和平移)甚至这个问题:图像缩放以鼠标位置为中心,但我不想在画布上进行,而是在普通图像上(或者更确切地说是图像的容器 div)。所以缩放应该像谷歌地图一样。我实际上正在破解/增强 iDangerous Swiper 缩放 ( http://idangero.us/swiper/),这是我的起点,这就是我到目前为止所得到的:https: //jsfiddle.net/xta2ccdt/3 /

Zoom only with the mouse wheel. The first time you zoom in it zooms perfectly, but I can't figure out how to calculate every zoom after first one.

仅使用鼠标滚轮缩放。第一次放大时可以完美放大,但我不知道如何计算第一个缩放后的每个缩放。

Here's my code: JS:

这是我的代码: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:

HTML:

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

CSS:

CSS:

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

I also figured out if I subtract previous translateX and translateY values from the current ones, I can zoom on the same point as much as I want and it will zoom perfectly, but if I zoom on one point and then change the mouse position and zoom in again, it will no longer zoom as it's supposed to. Example: https://jsfiddle.net/xta2ccdt/4/

我还想出如果我从当前值中减去之前的 translateX 和 translateY 值,我可以根据需要缩放同一点,它会完美缩放,但是如果我缩放一个点,然后更改鼠标位置并缩放再一次,它将不再像预期的那样缩放。示例:https: //jsfiddle.net/xta2ccdt/4/

If I change mouse position, and calculate the X and Y difference between old and new mouse position and add that into the diff calculation it will zoom correctly the second time. But the third time looks like that difference still gets subtracted from the total calculation and this will cause the translate to move the image away again, after that if we hold the mouse in the same position it will zoom correctly again. So I figured I'll just add the difference between old and new mouse position every time I calculate the new "diff", and this kind of works, there is no longer a jump like it was when I stopped adding the mouse position difference, but it's still not zooming on the same position, with each new zoom it moves (offsets) the image by a small amount. I figure this is because there is a new zoom value each time, but the offset is not linear, it's everytime smaller approaching zero, and I can't figure out how to offset the offset. Here is the new example: https://jsfiddle.net/xta2ccdt/5/New image in the example: old one is no longer available: https://jsfiddle.net/xta2ccdt/14/

如果我更改鼠标位置,并计算新旧鼠标位置之间的 X 和 Y 差异并将其添加到差异计算中,它将第二次正确缩放。但是第三次​​看起来差异仍然会从总计算中减去,这将导致平移再次将图像移开,之后如果我们将鼠标保持在同一位置,它将再次正确缩放。所以我想每次计算新的“差异”时我都会添加新旧鼠标位置之间的差异,这种工作,不再像我停止添加鼠标位置差异时那样跳跃,但它仍然没有在相同的位置进行缩放,每次新的缩放都会移动(偏移)图像少量。我想这是因为每次都有一个新的缩放值,但偏移量不是线性的,每次都更小接近零,我不知道如何抵消偏移量。这是新示例:https://jsfiddle.net/xta2ccdt/5/示例中的新图像:旧图像不再可用:https: //jsfiddle.net/xta2ccdt/14/

回答by Manuel Otto

You were close to it, however it's better to store the x,y and scale separately and calculate the transforms based on those values. It makes things alot easier + saves resources (no need to lookup the dom properties over and over),

您已经接近它了,但是最好单独存储 x、y 和缩放并根据这些值计算变换。它使事情变得更容易+节省资源(无需一遍又一遍地查找 dom 属性),

I've put the code into a nice module:

我已将代码放入一个不错的模块中:

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+')')
    }
}

Use it by calling

通过调用使用它

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

The parameters are:

参数是:

  1. container: The wrapper of the element to be zoomed. The script will look for the first child of the container and apply the transforms to it.
  2. max_scale: The maximum scale (4 = 400% zoom)
  3. factor: The zoom-speed (1 = +100% zoom per mouse wheel tick)
  1. 容器:要缩放的元素的包装器。该脚本将查找容器的第一个子项并对其应用转换。
  2. max_scale:最大比例(4 = 400% 缩放)
  3. 因素:缩放速度(1 = 每个鼠标滚轮刻度 +100% 缩放)

JSFiddle here

JSFiddle在这里

回答by Steve Ladavich

I think this will get you a little closer to what you are trying to achieve.

我认为这会让你更接近你想要实现的目标。

Key Changes

主要变化

  1. I pulled scale outside of the callback; I don't think you want to be resetting your scale on each wheel event.
  2. Instead of calculating the translation manually, try setting the transform-originto be centered on your mouse (unless you want to keep it centered which is the default)
  1. 我在回调之外拉了比例;我不认为你想在每个轮子事件上重置你的比例。
  2. 不要手动计算翻译,而是尝试将transform-origin鼠标设置为居中(除非您想将其居中,这是默认设置)

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>

回答by mseifert

The code I use to zoom in at the mouse position is below. It does not use transform/ translate3dbut readjusts the position of the image within the div and adjusts its heightand width.

我用来放大鼠标位置的代码如下。它不使用transform/translate3d而是重新调整图像在 div 中的位置并调整其heightwidth

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>

回答by Ivan Chaer

What about using translate3dand perspective, to handle the 3d transformations, instead of using scale? Also, decoupling zoom from translation makes it simpler.

如何使用translate3dandperspective来处理 3d 转换,而不是使用scale?此外,将缩放与平移分离使其更简单。

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