Insights and discoveries
from deep in the weeds
Outsharked

Thursday, February 16, 2012

Dragging and Dropping onto HTML Image Maps

A user of my jQuery plugin ImageMapster, while working through a problem, asked if it was possible to set up an imagemap that you could drop things onto. I had never done this before, but it seemed like an interesting problem with lots of potential applications, so I set about trying to solve it. A the bottom of this page is a simple example using Marvin the Martian that works, and I'll explain how before we get there.

Using jQueryUI's draggable method, it's easy to create things that can be dragged. Things get a little tricky because of z-indexes though. When you drag something, it needs to have a highest z-index on the page, or it will disappear behind things when you drag it over them. But at the same time, an imagemap will not activate unless it has the highest z-index. A paradox!

Luckily, when using ImageMapster, things are already set up so you can have your dragging element on top, and still have an active imagemap. This is the very nature of how it works: when binding an image, ImageMapster makes a copy of the image to use as a backdrop, and then makes your original image invisible though CSS using opacity:0. So all you need to do is make sure the z-index of your drag element is between those two things. A little understanding of ImageMapster's element scheme is all that's needed:

The topmost layer, the HTML image map itself, isn't really a layer in that you don't need to set the z-index of the elements explicitly. However it does act like a layer in that mouse events over the areas will supercede events over the image itself. But basically, the original image is the topmost element.

Finally, all this stuff is wrapped up in a div. This is useful to know because it means you can use jQuery's siblings method to easily change the z-index of everything that's important except your original image.

The code below is the basic logic you'll need to make something draggable over a live imagemap.

    
    var img = $('#my-image-map'), item = $('#draggable-object');

    img.mapster();
    ...

    // after binding with imageMapster, set the z-index of the image's siblings 
    // to zero (the lowest). The image copy that is created, as well as the canvases 
    // that render the highlight effects, are all siblings of the original image 
    // after binding ImageMapster.
    
    // IMPORTANT: This must be done *after* mapster has finished binding -- see the
    // actual code below for the use of "onConfigured" to do this

    img.siblings().css('z-index',0);

    // set the image itself to something higher. 
    
    img.css('z-index',10);

    // the draggable element should have a z-index inbetween the visible
    // background and effects images (which are all set to 0 now) and the
    // original image+imagemap (now set to 10).

    item.css('z-index',5);


That's almost all you need to do. What happens when you drop the martian somewhere? It now has a z-index that is lower than the original image. Even though the imagemap is not visible, and the martian is, you won't be able to grab the martian again, because once you drop it, it's old z-index takes effect.

To address this, you need a little more sleight of hand. When someone first grabs the draggable element, change its z-index to be a value between the two image layers. When they drop it, though, change it to something that's higher than the original image, so it will be on top and can be picked up again.

In Action!

Here's the functioning example, all the code follows (or just use your browser to look at it). Drag the martian onto Mars to "win". Any other planets will give you a negative response, and nothing happens if you drop him in space. I've also set it up on jsfiddle.net. Enjoy!

Help me get home!

Code:


Internet Explorer notes: This doesn't seem to work in IE < 9 -- in that the imagemap areas are not activated on mouseover while dragging. I'll address this, and present a solution, in my next post.

    var img = $('#planets'), martian=$('#martian');
    
    img.mapster({ 
        mapKey: 'alt',
        fillOpacity: 0.8, 
        fillColor: 'ff0000', 
        stroke: true,
        strokeWeight: 2, 
        strokeColor: '00ff00',
        onConfigured: function() {
            // this will be called only after ImageMapster is done setting up
            img.siblings().css('z-index',0);
            img.css('z-index',10);
        }
    });   

    martian.css('z-index',5)
        .draggable({
            drag: function() {
                $(this).css('z-index', 5)
            }
        });

    img.droppable({
        drop: function(e,ui) {
            // set z-index to the highest, so it can be dragged again
            $(ui.draggable).css('z-index',20);
 
            // returns the mapKey value of the currently highlighted item
            var landing=img.mapster('highlight');

            if (landing=='Mars') {
                alert("Thanks for bringing me home!!!");
                martian.draggable('disable').hide('slow');
            } else if (landing) {
                 alert("I don't live on " +  landing);       
            }
        }
    });