42

I'm currently trying to make an Image-Map on my site that will resize depending on the size of the window... I was wondering if there was anyway to do this with HTML or will I have to do this with Javascript or another language.

<div style="text-align:center; width:1920px; margin-left:auto; margin-right:auto;">
<img id="Image-Maps_5201211070133251" src="Site.png" usemap="#Image-Maps_5201211070133251" border="0" width="1920" height="1080" alt="" />
<map id="_Image-Maps_5201211070133251" name="Image-Maps_5201211070133251">
<area shape="poly" coords="737,116,1149,118,944,473," href="http://essper.bandcamp.com" alt="Bandcamp" title="Bandcamp"   />
<area shape="poly" coords="1006,589,1418,590,1211,945," href="http://soundcloud.com/essper" alt="Soundcloud" title="Soundcloud"   />
<area shape="poly" coords="502,590,910,591,708,944," href="http://facebook.com/the.essper" alt="Facebook" title="Facebook"   />
</map>

ultrazoid
  • 461
  • 1
  • 6
  • 12
  • you could use css to adjust the size of the div and / or the img, but since the coordinates for the area are absolute you might have to do this with javascript – Horen Nov 10 '12 at 10:08
  • Please correct me if I've misunderstood your question. You have a fixed size and centered `IMG` which won't resize according to the window, and now you want a `MAP` which always will cover only the visible part of the `IMG`? – Teemu Nov 10 '12 at 10:53
  • No I am adding the dynamic for the image and the map then resizes according to the size of the image – ultrazoid Nov 11 '12 at 11:50

13 Answers13

61

I wrote a small little lib to keep an imageMap scaled to a resizable image, so the map stays in sync as the image scales. Useful when you want to map a percentage scaled image etc.

It can be used with or without jQuery.

https://github.com/davidjbradshaw/imagemap-resizer

and you can see it working at.

http://davidjbradshaw.com/imagemap-resizer/example/

David Bradshaw
  • 11,859
  • 3
  • 41
  • 70
52

If you end up to do the task with JavaScript, here is a cross-browser codesnippet to resize all areas in MAP element.

window.onload = function () {
    var ImageMap = function (map) {
            var n,
                areas = map.getElementsByTagName('area'),
                len = areas.length,
                coords = [],
                previousWidth = 1920;
            for (n = 0; n < len; n++) {
                coords[n] = areas[n].coords.split(',');
            }
            this.resize = function () {
                var n, m, clen,
                    x = document.body.clientWidth / previousWidth;
                for (n = 0; n < len; n++) {
                    clen = coords[n].length;
                    for (m = 0; m < clen; m++) {
                        coords[n][m] *= x;
                    }
                    areas[n].coords = coords[n].join(',');
                }
                previousWidth = document.body.clientWidth;
                return true;
            };
            window.onresize = this.resize;
        },
        imageMap = new ImageMap(document.getElementById('map_ID'));
    imageMap.resize();
}

previousWidth must be equal to the width of the original image. You also need to use some relative units in HTML:

<div style="width:100%;">
<img id="Image-Maps_5201211070133251" src="Site.png" usemap="#Image-Maps_5201211070133251" border="0" width="100%" alt="" />

Working demo at jsFiddle. If you open the fiddle in IE, you can actually see AREAs when clicking them.

Áxel Costas Pena
  • 5,886
  • 6
  • 28
  • 59
Teemu
  • 22,918
  • 7
  • 53
  • 106
  • @ultrazoid In your question you said the image will resize according to a window, the code will resize `MAP` when the window is resized, if the size of the image is related to the size of the window. – Teemu Nov 11 '12 at 15:48
  • 1
    Nice, a solution that doesn't use jQuery! Thanks! – Rolf Feb 16 '14 at 23:09
  • Nice solution, but I'm having some issues with my image map where some of my circles mapped still don't properly line up. Any fixes to that? – Barry Michael Doyle May 08 '14 at 18:45
  • @BarryDoyle Please post a question with a code you have, a good explanation of the problem and maybe with a small example at jsfiddle.net , we'll see, what we can do. Notice, that it's important not to use any rounding when setting values to coords. – Teemu May 08 '14 at 19:00
  • @Teemu, I think rounding is the problem.. But how do I fix that? – Barry Michael Doyle May 19 '14 at 19:21
  • 1
    Ok I've posted a question here: http://stackoverflow.com/questions/23752408/resizing-image-maps-containing-circles – Barry Michael Doyle May 20 '14 at 06:26
  • I can't get this to work, and i'm only a snippet user with almost no knowledge of javascript. I have a function writeText for my imagemap. Does this mean i have to do something differently? – kim holder Jun 28 '14 at 18:50
  • Hi Teemu. How do I produce an initial image map for an image of `width:100%` ? Thanks. – Steve Jul 27 '15 at 12:56
  • 1
    @Steve Just use the code in the answer. "`previousWidth` must be equal to the width of the original image." Maybe a bit bad wording in the answer, `previousWidth` is the width which the image had at the time you've originally created the coordinates for `area` elements. – Teemu Jul 27 '15 at 13:01
  • Excellent. Thank you Teemu. – Steve Jul 27 '15 at 13:10
  • 1
    @Steve Did you notice Barry's question and [my answer](http://stackoverflow.com/a/23759659/1169519) to it. A little change introduced in that answer makes the ImageMapper more generic. – Teemu Jul 27 '15 at 13:12
  • Three problems with this answer: 1) This will clobber whatever window resize events you might have setup prior to this, which also means it will only work with one image, 2) it will accumulate error over time, if there are multiple resize events, 3) it assumes the image starts at set, static width, which is probably wrong more often than right. – moron4hire Oct 16 '15 at 20:44
  • @moron4hire Well, this was an answer to OP's question only, not a multi-purpose generic image map resizer. Anyway, the original code I had, was made for old IEs only, which had also `img.onresize`. You can use `addEventListener` instead of `onresize` property to get this work for more than one image. The text below the code says: "must be equal to the width of the original image", nothing prevents you to get the width dynamically, a static value is used to keep the code simple. I've also stripped a debouncer out of the code, with a debouncer it will become lighter to a browser to execute. – Teemu Oct 17 '15 at 06:38
5

This is my simplest solution. No jquery or any plugin needed. Caution, this solution does not handle any errors in markup, or images that are not proportionally sized.

function mapResizer(maps) {
    if (!maps) {maps = document.getElementsByTagName('map');}
    for (const map of maps) {
        map.img = document.querySelectorAll(`[usemap="#${map.name}"]`)[0];
        map.areas = map.getElementsByTagName('area');
        for (const area of map.areas) {
            area.coordArr = area.coords.split(',');
        }
    }
    function resizeMaps() {
        for (const map of maps) {
            const scale = map.img.offsetWidth / (map.img.naturalWidth || map.img.width);
            for (const area of map.areas) {
                area.coords = area.coordArr.map(coord => Math.round(coord * scale)).join(',');
            }
        }
    }
    window.addEventListener('resize', () => resizeMaps());
    resizeMaps();
}
if (document.readyState == 'complete') {
    mapResizer();
} else {
    window.addEventListener('load', () => mapResizer());
}
Nagy Zoltán
  • 659
  • 8
  • 7
3

As a class (ES6):

class ResponsiveImageMap {
    constructor(map, img, width) {
        this.img = img;
        this.originalWidth = width;
        this.areas = [];

        for (const area of map.getElementsByTagName('area')) {
            this.areas.push({
                element: area,
                originalCoords: area.coords.split(',')
            });
        }

        window.addEventListener('resize', e => this.resize(e));
        this.resize();
    }

    resize() {
        const ratio = this.img.offsetWidth / this.originalWidth;

        for (const area of this.areas) {
            const newCoords = [];
            for (const originalCoord of area.originalCoords) {
                newCoords.push(Math.round(originalCoord * ratio));
            }
            area.element.coords = newCoords.join(',');
        }

        return true;
    };
}

Usage:

var map = document.getElementById('myMapId');
var image = document.getElementById('myImageId');
new ResponsiveImageMap(map, image, 800);
Andriy Kuba
  • 8,093
  • 2
  • 29
  • 46
3

If you have access to Illustrator or another program that can generate an SVG it is trivially easy to create a dynamic image map with an SVG.

This does not require any programming.

Here are instructions for Illustrator (only takes a few seconds):

  1. open image in Illustrator, save under new name

  2. resize document to same size as image

  3. draw filled rectangles for the map parts (helpful to put opacity at 50%)

  4. using the "Attributes" palette add a link to each rectangle

  5. change all the rectangles' opacity to 0%

  6. select the image and in the "links" palette menu select "Unembed…" (name doesn't matter, we're not going to use the image)

  7. file › save as SVG (Image Location : Link, CSS Properties : Style Elements, Responsive is checked)

  8. open the resulting svg file

  9. delete first two lines (XML & Adobe comment)

  10. update image source

  11. paste the svg code in your html document

This works in all major browsers. Here is a screen capture of the SVG export settings for Illustrator:

enter image description here

Andy Swift
  • 2,179
  • 3
  • 32
  • 53
3

You can multiply the coordinates by the ratio of the original image and the styled image.

<img id="paredea" usemap="#PAREDE-A"  src="https://i.imgur.com/o9nrUMR.png">

    <map name="PAREDE-A">
        <area id="paredea0" shape="rect"  onclick="alert('colmeia A')">
        <area id="paredea1" shape="rect"  onclick="alert('colmeia B')">
        <area id="paredea2" shape="rect"  onclick="alert('colmeia C')">
        <area id="paredea3" shape="rect"  onclick="alert('colmeia D')">
        <area id="paredea4" shape="rect"  onclick="alert('colmeia E')"> 

        <area id="paredea5" shape="rect"  onclick="alert('comeia F')">
        <area id="paredea6" shape="rect"  onclick="alert('colmeia G')">
        <area id="paredea7" shape="rect"  onclick="alert('colmeia H')">
        <area id="paredea8" shape="rect"  onclick="alert('colmeia I')">
        <area id="paredea9" shape="rect"  onclick="alert('colmeia J')">  

        <area id="paredea10" shape="rect"  onclick="alert('colmeia K')">
        <area id="paredea11" shape="rect"  onclick="alert('colmeia L')">
        <area id="paredea12" shape="rect"  onclick="alert('colmeia M')">
        <area id="paredea13" shape="rect"  onclick="alert('colmeia N')">
        <area id="paredea14" shape="rect"  onclick="alert('colmeia O')">  
    </map>

    <script>


        var coordsA = [];
        coordsA[0] = "0,0,200,130";
        coordsA[1] = "200,0,400,130";
        coordsA[2] = "400,0,600,130";
        coordsA[3] = "600,0,800,130";
        coordsA[4] = "800,0,1000,130";

        coordsA[5] = "0,160,200,240";
        coordsA[6] = "200,160,400,240";
        coordsA[7] = "400,160,600,240";
        coordsA[8] = "600,160,800,240";
        coordsA[9] = "800,160,1000,240";

        coordsA[10] = "0,270,200,400";
        coordsA[11] = "200,270,400,400";
        coordsA[12] = "400,270,600,400";
        coordsA[13] = "600,270,800,400";
        coordsA[14] = "800,270,1000,400";


        function setcoords(areaid, totalOfAreas) {
            document.getElementById('paredea').style.width = "auto";
            var width1 = document.getElementById('paredea').width;
            document.getElementById('paredea').style.width = "100%";
            var width2 = document.getElementById('paredea').width;
            var ratio = width2 / width1;

            for (var i = 0; i < totalOfAreas; i++) {
                var temp = coordsA[i].split(",");
                var newcoords = "";
                for (var j = 0; j < temp.length; j++) {
                    temp[j] *= ratio;
                    newcoords += temp[j] + ",";
                }
                newcoords = newcoords.substr(0, newcoords.length - 1);

                document.getElementById(areaid + i).coords = newcoords;
            }
        }


       window.onload = function () {
            setcoords("paredea", 15);
        };

        window.onresize = function () {
            setcoords("paredea", 15);
        };
    </script>
Lucas
  • 57
  • 1
  • 5
1

I had the same problem last week and I ended up writing a jQuery plugin for this.

Here's the project gitHub:

https://github.com/etienne-martin/mapify

Basic usage:

$("img[usemap]").mapify();

Live example

http://emartin.ca/mapify/

Etienne Martin
  • 10,018
  • 3
  • 35
  • 47
  • The documentation is ambiguous : does it only work for .svg image or for everything (jpg, png...) ? – Moebius May 14 '15 at 16:23
  • Definitely. It looks like it is only work with .svg files. If it is the case, you should state it clearly. If not, you should skip all the quotes about svg. None of the two things as been stated clearly. – Moebius May 14 '15 at 19:49
  • Sure, but I think my advices still apply. If I took so much time to figure it out, it is probably that I have been unfocused, but also that it was confusing. Thanks anyway – Moebius May 14 '15 at 20:06
1

for this to work you need to have data-original-coords attribute having the coords to the original picture

$(function () {
    function adjeustCoords() {
        var image=$('img'); //change that to your image selector
        var originalWidth=image[0].naturalWidth;
        var currentWidth=image.width();
        var ratio=currentWidth/originalWidth;
        $("map area").each(function(){
            //change that to your area selector
            var coords=$(this).attr('data-original-coords').split(',');
            coords = coords.map(function (x) {
                return Math.round(x*ratio);
                //i don't know if all browsers can accept floating point so i round the result
            });
            $(this).attr('coords',coords.join());
        });
    }
    adjeustCoords();
    $(window).resize(function(){
        adjeustCoords();
    });
});

this works with chrome , firefox and edge least versions

Robert
  • 2,342
  • 2
  • 24
  • 41
0

You can use CSS sprites to achieve this. You will have the image pieces fit into just one image and this way you will just be making one http request to load all the images. This technique doesn't require javascript and you will just be using background-position; property to move your images.

This is an efficient technique for page optimization.

defau1t
  • 10,593
  • 2
  • 35
  • 47
0

I've only tested this for rectangular coordinates, but I think it should generalize to circular or polygon

function wrap ( img, map ) {
  var originalCoords = [ ],
      test = new Image();

  for ( var i = 0; i < map.areas.length; ++i ) {
    var coords = map.areas[i].coords;
    originalCoords.push( coords.split( "," ).map( parseFloat ) );
  }

  function resize () {
    var ratio = img.width / test.width;
    for ( var i = 0; i < map.areas.length; ++i ) {
      map.areas[i].coords = originalCoords[i].map( function ( n ) {
        return ratio * n;
      } ).join( "," );
    }
  }

  test.addEventListener( "load", function () {
    window.addEventListener( "resize", resize, false );
    resize();
  }, false );

  test.src = img.src;
}

var imgs = document.querySelectorAll( "img[usemap]" );
for ( var i = 0; i < imgs.length; ++i ) {
  var map = document.querySelector( "map[name=" + imgs[i].useMap.substring( 1 ) + "]" );
  wrap( imgs[i], map );
}
moron4hire
  • 703
  • 5
  • 13
0

Here is another plugin I just wrote to manage image maps: https://github.com/gestixi/pictarea

Amongst other things, the areas automatically scale depending on the size of the image. Note that it is using canvas to render the areas.

Basic usage:

$(function() {
  $('#map').pictarea({
    rescaleOnResize: true
  });
});
Nicolas BADIA
  • 5,612
  • 7
  • 43
  • 46
0

I have found that I can get multiple image sizes out of a single image map by adjusting the background-size and all other CSS attribute sizes and positions appropriately.

The following CSS was used for an ImageMap that contained the images for multiple social networks. Using CSS I could pull out three different sizes of a single Twitter icon.

.twitterIcon64 { /* Actual Size */
    background-size: 300px 282px;
    background: url('/images/social-media-icons.png') no-repeat -18px -109px;
    width: 64px;
    height: 64px;
}
.twitterIcon32 { /* 1/2 size */
    background-size: 150px 141px;
    background: url('/images/social-media-icons.png') no-repeat -9px -54px;
    width: 32px;
    height: 32px;
}
.twitterIcon21 { /* 1/3 size */
    background-size: 100px 94px;
    background: url('/images/social-media-icons.png') no-repeat -6px -36px;
    width: 22px;  /* Round up to avoid truncation */
    height: 22px; /* Round up to avoid truncation */
}

This works extremely well with media queries (very dynamic).

If necessary javascript could be used to either select the appropriate class or calculate the appropriate sizes.

Tested on IE 11, Edge, Firefox and Chrome.

AnthonyVO
  • 3,821
  • 1
  • 36
  • 41
0

Improved version for specific Vue.js usage of the answer of @Nagy Zoltán above You pass the component in which you have your image(s) map(s). It will handle dynamic updates or source images, by testing if image is loaded. It will also avoid to resize twice image maps in a component if the function was called more than once

export const MapResizer = {
    mapResizer : function (comp) {
        const map =comp.$refs['image-map'];
        if (!map) return;
        map.img = document.querySelectorAll(`[usemap="#${map.name}"]`)[0];
        if (!map.img) return;
        if (map.resized) return; // don't recompute if already done for the given map as we place a listener
        map.resized = true;

        comp.areas = map.getElementsByTagName('area');
        for (const area of comp.areas) {
            area.coordArr = area.coords.split(',');            
        }
        function resizeMaps() {                        
            const scale = map.img.offsetWidth / (map.img.naturalWidth || map.img.width);
            for (const area of comp.areas) {
                area.coords = area.coordArr.map(coord => Math.round(coord * scale)).join(',');                
            }
        }
        window.addEventListener('resize', () => resizeMaps());
        resizeMaps();
    }    
  };
Cédric NICOLAS
  • 1,006
  • 2
  • 12
  • 24