Saturday, March 27, 2010

jQuery zoom addin with image map support

In the past I fiddled around with the moozoom plugin to add support for image maps and on a new project I needed the same thing. But this new project uses a lot of jQuery stuff and jQuery and mootools don’t play together particularly well. Although there are a lot of image zooming plugins available for jQuery, none of them did exactly what I needed. I just wanted image zooming, panning and support for image maps. Anyway, this is what I came up with. This is my first adventure in the world of jQuery plugins, so it may not be a perfect implementation but it works for me…

Usage is pretty simple

$('.selector').zoomable();

where .selector is the selector for your image. View an example here (that page will also be the place I put all new versions of this code). You can now also programmatically zoom in and out using $('.selector').zoomable('zoomIn'); and $('.selector').zoomable('zoomOut'); which you can hook up to buttons for users without a mouse wheel

Here’s the code (requires jQuery and jQuery UI)

(function ($) {
  $.fn.zoomable = function (method) {
 
    return this.each(function (index, value) {
      // restore data, if there is any for this element
      var zoomData;
      if ($(this).data('zoomData') == null) {
        zoomData = {
          busy: false,
          x_fact: 1.2,
          currentZoom: 1,
          originalMap: null,
          currentX: 0,
          currentY: 0
        };
        $(this).data('zoomData', zoomData);
      }
      else
        zoomData = $(this).data('zoomData');
      
      var init = function() {
        if (value.useMap != "") {
          var tempOriginalMap = document.getElementById(value.useMap.substring(1));
          zoomData.originalMap = tempOriginalMap.cloneNode(true);
          // for IE6, we need to manually copy the areas' coords
          for (var i = 0; i < zoomData.originalMap.areas.length; i++)
            zoomData.originalMap.areas[i].coords = tempOriginalMap.areas[i].coords;
        }

        $(value).css('position', 'relative').css('left', '0').css('top', 0).css('margin', '0');

        $(value).draggable();

        // jquery mousewheel not working in FireFox for some reason
        if ($.browser.mozilla) {
          value.addEventListener('DOMMouseScroll', function (e) {
            e.preventDefault();
            zoomMouse(-e.detail);
          }, false);
          if (value.useMap != "") {
            $(value.useMap)[0].addEventListener('DOMMouseScroll', function (e) {
              e.preventDefault();
              zoomMouse(-e.detail);
            }, false);
          }
        }
        else {
          $(value).bind('mousewheel', function (e) {
            e.preventDefault();
            zoomMouse(e.wheelDelta);
          });
          if (value.useMap != "") {
            $(value.useMap).bind('mousewheel', function (e) {
              e.preventDefault();
              zoomMouse(e.wheelDelta);
            });
          }
        }

        $(value).bind('mousemove', function (e) {
          zoomData.currentX = e.pageX;
          zoomData.currentY = e.pageY;
        });
      };

      var left = function() {
        return parseInt($(value).css('left'));
      };
      
      var top = function() {
        return parseInt($(value).css('top'));
      }
      
      var zoomIn = function() {
        // zoom as if mouse is in centre of image
        var parent = $(value).parent()[0];
        zoom(zoomData.x_fact, left()+parent.offsetLeft+(value.width/2), top()+parent.offsetTop+(value.height/2));
      };
      
      var zoomOut = function() {
        // zoom as if mouse is in centre of image
        var yi = parseInt($(value).css('top'));
        var parent = $(value).parent()[0];
        zoom(1 / zoomData.x_fact, left()+parent.offsetLeft+(value.width/2), top()+parent.offsetTop+(value.height/2));
      };
      
      var zoomMouse = function (delta) {

        // zoom out ---------------
        if (delta < 0) {
          zoom(1 / zoomData.x_fact, zoomData.currentX, zoomData.currentY);
        }

        // zoom in -----------
        else if (delta > 0) {
          zoom(zoomData.x_fact, zoomData.currentX, zoomData.currentY);
        }
      };

      var zoomMap = function () {
        // resize image map
        var map = document.getElementById(value.useMap.substring(1));
        if (map != null) {
          for (var i = 0; i < map.areas.length; i++) {
            var area = map.areas[i];
            var originalArea = zoomData.originalMap.areas[i];
            var coords = originalArea.coords.split(',');
            for (var j = 0; j < coords.length; j++) {
              coords[j] = Math.round(coords[j] * zoomData.currentZoom);
            }
            var coordsString = "";
            for (var k = 0; k < coords.length; k++) {
              if (k > 0)
                coordsString += ",";
              coordsString += coords[k];
            }
            area.coords = coordsString;
          }
        }
      };

      var zoom = function (fact, mouseX, mouseY) {
        if (!zoomData.busy) {
          zoomData.busy = true;

          var xi = left();
          var yi = top();

          var new_h = (value.height * fact);
          var new_w = (value.width * fact);
          zoomData.currentZoom = zoomData.currentZoom * fact;

          // calculate new X and y based on mouse position
          var parent = $(value).parent()[0];
          mouseX = mouseX - parent.offsetLeft
          var newImageX = (mouseX - xi) * fact;
          xi = mouseX - newImageX;

          mouseY = mouseY - parent.offsetTop
          var newImageY = (mouseY - yi) * fact;
          yi = mouseY - newImageY;

          $(value).animate({
            left: xi,
            top: yi,
            height: new_h,
            width: new_w
          }, 100, function () {
            zoomData.busy = false;
          });

          zoomMap();
        }
      };
      
      if (method == "zoomIn")
        zoomIn();
      else if (method == "zoomOut")
        zoomOut();
      else
        init();
    });
  };
})(jQuery);

13 comments:

socrtwo said...

This works for my image if I have an ID for the image and set the Javascript usage statement in the body to: $('#world_map').zoomable(); (world_map is the ID of my image). I could not get it to work using '.selector'

However what it doesn't do is to preserve the image map. I can also try to refer to the image map's ID as such, $('#world_image_map').zoomable(); where my image map ID is "world_image_map" however the image map does not zoom with the image, so there doesn't seem to be image map support for me.

I'm using jQuery 1.42 minimum and referred to the plugin in its own file called "zoomable.js" which I loaded as src along with jquery.min.js in two separate script statements.

Any suggestions?

seanmk said...

This plugin seems to zoom the image map pretty well, but I can't seem to get the dragging part to work. Instead of dragging the image, it applies the browser default and gives me a little image icon that I can drag around.

I tried just applying draggable() instead of zoomable() to my image and discovered that it just doesn't work. Draggable doesn't seem to do very well with image maps. I looked around and found a couple people with similar problems but no good solutions.

It seems like others must have run into this with this plugin. Is this so, and how are you solving it?

Doogal said...

Yeh, I've never got dragging on the actual image map to work, but generally clicking on the image map does something else anyway so I don't mind the fact that dragging doesn't work, it might be confusing for the end user anyway.

Having said that, dragging should work on the parts of the image that don't have an image map area over them.

Anonymous said...

Hey nice post its exactly what i want. but just struggling to make it work. Is there a way you can post the example thanks in advance

Saswat said...

I am getting the cords as 0,0,0,0 for this. any suggestions ?

var originalArea = originalMap.areas[i];
var coords = originalArea.coords.split(',');

Doogal said...

Saswat

Could be you'll get 0,0,0,0, depends what your image map coordinates are

Saswat said...

here is the image map






here is the function i am calling
$(document).ready(function() {
$('#x').zoomable();
});
Problem is the image map resizing always gives cords 0,0,0,0 so imagemap is not getting created.

Doogal said...

Hi Saswat

I think the commenting system stripped out the image map. Drop me an email on doogal[at]doogal[dot]co[dot]uk and I'll have a look

Doogal said...

Anonymous

There's an example available at
http://www.doogal.co.uk/zoomable.php

Anonymous said...

Hi, good script, but, what can you do for people that uses laptop and dont have the wheel to zoom in and out? can this be modified to have a pair of buttons to zoom out and in? thanks

Doogal said...

good point, I'll add it to my to Do list

jonathan said...

scortwo:"However what it doesn't do is to preserve the image map. "

I had the same issue, but after some digging found that if I removed the width and height attributes from the img tag, this solved the problem.

Unknown said...

Wonderful. This is what i really need.