Zoom in on the mouse point (using scale and translate)

This question is similar to this one: Zooming in (using scale and translation) or even this: Scaling an image to fit the position of the mouse but I don’t want to do this on the canvas, but a normal image (or rather, a div container). Therefore, scaling should be like Google maps. I actually hack / zoom iDangerous Swiper ( http://idangero.us/swiper/ ), and this is my starting point, and this is what I got so far: https://jsfiddle.net/xta2ccdt/3/

Enlarge only the mouse wheel. The first time you zoom in, it scales very well, but I can't figure out how to calculate each scale after the first.

Here is 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 realized that if you subtract the previous translateX and translateY values ​​from the current ones, I can increase the same point as I want, and it will scale perfectly, but if I increase by one point, then change the mouse position and zoom in, it will no longer scale as intended. Example: https://jsfiddle.net/xta2ccdt/4/

If I change the position of the mouse and calculate the difference between X and Y between the old and the new position of the mouse and add this to the diffusion calculation, it will scale correctly the second time. But for the third time, it seems that this difference is still subtracted from the general calculation, and this will force the translation to move the image again, after which if we hold the mouse in the same position, it will scale correctly again. So I decided that I would just add the difference between the old and the new mouse position every time I calculate a new “diff”, and such works, there’s no longer a jump like it was when I stopped adding the difference in the mouse position, but it’s all still not scaled in the same position, with each new zoom it moves (shifts) the image by a small amount. I believe that this is due to the fact that each time a new scaling value appears, but the offset is not linear, it approaches zero less each time, and I cannot figure out how to compensate for the offset. Here is a new example: https://jsfiddle.net/xta2ccdt/5/ New image in the example: the old one is no longer available: https://jsfiddle.net/xta2ccdt/14/

+5
source share
4 answers

You were close to it, however it is better to store x, y and scale separately and calculate the transforms based on these values. It makes things a lot easier + saves resources (no need to search for dom properties over and over again),

I put the code in 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 calling

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

Options:

  • container: the wrapper of the item to be scaled. The script will find the first child of the container and apply the transformations to it.
  • max_scale: maximum scale (4 = 400% scaling)
  • : Scaling speed (1 = + 100% scaling for each mouse wheel)

Jsfiddle here

+1
source

I think this will bring you closer to what you are trying to achieve.

Major changes

  • I pulled the scale beyond the callback; I don't think you want to zoom out on every wheel event.
  • Instead of calculating the translation manually, try setting transform-origin to the center of the mouse (if you don't want it to be centered by default)

 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
source

The code I'm using to zoom in on the mouse position is below. It does not use transform / translate3d , but adjusts the position of the image in the div and adjusts its height and 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
source

How to use translate3d and perspective to handle 3D transforms instead of using scale ? In addition, decoupling the scale from translation makes it easy.

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

Source: https://habr.com/ru/post/1272452/


All Articles