Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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 position but 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/

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:

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

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/

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/

like image 545
dari0h Avatar asked Oct 09 '17 13:10

dari0h


1 Answers

Thanks for all the answers, helped me a lot. I have modified it to allow pan to.

$(document).ready(function (){
    var scroll_zoom = new ScrollZoom($('#container'),5,0.5)
})

//The parameters are:
//
//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.
//max_scale: The maximum scale (4 = 400% zoom)
//factor: The zoom-speed (1 = +100% zoom per mouse wheel tick)

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 scale = 1
    var zoom_target = {x:0,y:0}
    var zoom_point = {x:0,y:0}
    var curr_tranform = target.css('transition')
    var last_mouse_position = { x:0, y:0 }
    var drag_started = 0

    target.css('transform-origin','0 0')
    target.on("mousewheel DOMMouseScroll",scrolled)
    target.on('mousemove', moved)
    target.on('mousedown', function() {
        drag_started = 1;
        target.css({'cursor':'move', 'transition': 'transform 0s'});
        /* Save mouse position */
        last_mouse_position = { x: event.pageX, y: event.pageY};
    });

    target.on('mouseup mouseout', function() {
        drag_started = 0;
        target.css({'cursor':'default', 'transition': curr_tranform});
    });

    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

        update()
    }

    function moved(event){
        if(drag_started == 1) {
            var current_mouse_position = { x: event.pageX, y: event.pageY};
            var change_x = current_mouse_position.x - last_mouse_position.x;
            var change_y = current_mouse_position.y - last_mouse_position.y;

            /* Save mouse position */
            last_mouse_position = current_mouse_position;
            //Add the position change
            pos.x += change_x;
            pos.y += change_y;

        update()
        }
    }

    function update(){
        // 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)

        target.css('transform','translate('+(pos.x)+'px,'+(pos.y)+'px) scale('+scale+','+scale+')')
    }
}
#container{
  width:500px;
  height:500px;
  overflow:hidden;
}
#slide{
  width:100%;
  height:100%;
  transition: transform .3s;
}
img{
  width:auto;
  height:auto;
  max-width:100%;
  pointer-events: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="container">
  <div id="slide">
    <img src="https://iso.500px.com/wp-content/uploads/2014/07/big-one.jpg">
  </div>
</div>

Test it here to if you like: https://jsfiddle.net/Pimigo/sy9c8z5q/8/

like image 56
Pimigo Avatar answered Sep 19 '22 13:09

Pimigo