I'm trying to implement an image zoom effect, a bit like how the zoom works with Google Maps, but with a grid of fix position images.

I've uploaded an example of what I have so far here:


(uses CSS3 transforms so only works with Firefox, Opera, Chrome or Safari)

Use your mouse wheel to zoom in/out. The HTML source is basically an outer div with an inner-div, and that inner-div contains 16 images arranged using absolute position. It's going to be a Photo Mosaic basically.

I've got the zoom bit working using CSS3 transforms:

$(this).find('div').css('-moz-transform', 'scale(' + scale + ')');

...however, I'm relying on the mouse X/Y position on the outer div to zoom in on where the mouse cursor is, similar to how Google Maps functions. The problem is that if you zoom right in on an image, move the cursor to the bottom/left corner and zoom again, instead of zooming to the bottom/left corner of the image, it zooms to the bottom/left of the entire mosaic. This has the effect of appearing to jump about the mosaic as you zoom in closer while moving the mouse around, even slightly.

That's basically the problem, I want the zoom to work exactly like Google Maps where it zooms exactly to where your mouse cursor position is, but I can't get my head around the Maths to calculate the transform-origin: X/Y values correctly. Please help, been stuck on this for 3 days now.

Here is the full code listing for the mouse wheel event:

var scale = 1;

$("#mosaicContainer").mousewheel(function(e, delta)
    if (delta > 0)
        scale += 1;
        scale -= 1;
    scale = scale < 1 ? 1 : (scale > 40 ? 40 : scale);

    var x = e.pageX - $(this).offset().left;
    var y = e.pageY - $(this).offset().top;

    $(this).find('div').css('-moz-transform', 'scale(' + scale + ')')
        .css('-moz-transform-origin', x + 'px ' + y + 'px');

    return false;

2 answers

This was chosen as the best answer

The problem occurs because the location of the point on the image is not the same as the location of the pointer on the screen if the scaling factor is something other than 1. You have to determine the location on the image represented by the current screen location, scale the image around that point, then determine the location on the screen of the scaled image so that it doesn't shift.

I also changed it so scaling factor doubles each time. The scaling grew too slowly for my taste when the magnification was large. If you find this too fast, change the scaling by 2 to something smaller like 1.5, or just go back to your code.

There's one problem left. When you zoom out after shifting locations the image won't be centred on the same place. This results in the image being shifted when you get back to magnification 1. This isn't a problem for Google Maps because they are showing an image from the surface of a sphere. There's no edge to that surface. There's always something more to fill in when zooming out. I suppose you could snap the image back to the origin when the scale returns to 1. Or, you could allow the user to drag the image.

<script type="text/javascript">

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

Answered over 9 years ago by Wayne Johnston
  • OMG! It's a good job I don't know where you live, coz I would have come over to your house and kissed & hugged you to death (eww, too gross?). Seriously, thank you soooo much!! Sunday Ironfoot over 9 years ago
  • BTW: I was going to turn this into a jQuery plugin at some point, the least I can do is give you credit with helping me with the Math, do you have a blog or website you want me to plug? Sunday Ironfoot over 9 years ago
  • No trouble. It was an interesting problem to solve and it gave me an excuse to learn something about transforms. Go ahead and release this. As far as I'm concerned it's in the public domain and I don't need credit. I'm happy if it helped. Wayne Johnston over 9 years ago
  • Managed to implement this into a full example, have a look at a Mosaic of some penguins http://www.dominicpettifer.co.uk/Files/Mosaic/MosaicTest.html Also supports dragging the image around (left click hold). Still a little buggy, also currently only works with latest Safari, Opera and Firefox (images are blurry on Chrome for some reason). Sunday Ironfoot about 9 years ago

I don't have a mouse wheel so there's no way for me to test it, but you should probably save off the mouse coordinates before beginning the zoom operation and just forget about whether or not it moved during the transition.

Answered over 9 years ago by Nathan Duran