Dynamically resizing Image-maps and images
Asked Answered
H

13

43

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>

Homorganic answered 10/11, 2012 at 10:4 Comment(3)
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 javascriptNympho
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?Risteau
No I am adding the dynamic for the image and the map then resizes according to the size of the imageHomorganic
R
53

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.

Risteau answered 10/11, 2012 at 12:28 Comment(11)
@Homorganic 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.Risteau
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?Musclebound
@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.Risteau
@Teemu, I think rounding is the problem.. But how do I fix that?Musclebound
Ok I've posted a question here: #23752908Musclebound
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?Cotillion
Hi Teemu. How do I produce an initial image map for an image of width:100% ? Thanks.Dreadnought
@Dreadnought 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.Risteau
@Dreadnought Did you notice Barry's question and my answer to it. A little change introduced in that answer makes the ImageMapper more generic.Risteau
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.Livonia
@Livonia 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.Risteau
G
62

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/

Gaur answered 26/2, 2014 at 23:54 Comment(9)
This is awesome! I wished I could upvote multiple timesAnabolite
This is pretty awesome! But is there a simple way to hilight the area on hover?Heraldic
This is absolutely fantastic.. worked like a charm and saved me a lot of hours! @craphunter: In my opinion, a different plugin should be written for highlighting, this lib by David serve it's purpose extremely well!Eaglewood
@Heraldic Have you tried setting the CSS outline property to do that?Gaur
Hi David. Does this work on responsive images? i.e: images that are initially 100% wide? Thanks.Dreadnought
@Steve, yep that is the main reason for it.Gaur
This is a fantastic library, thank you. I did suggest a small change to the code regarding dealing with hash changes. It's particularly useful with the rise of SPAs/ React/ Vue/ etc.Advertisement
Can i ask what the coordinates in coord="10,10,10,10" in rect mean? are those x,y,lengh,height of the rectangle we want to make clickable? and how to find the exact coordinates?Tver
@Tver top left bottom rightGaur
R
53

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.

Risteau answered 10/11, 2012 at 12:28 Comment(11)
@Homorganic 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.Risteau
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?Musclebound
@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.Risteau
@Teemu, I think rounding is the problem.. But how do I fix that?Musclebound
Ok I've posted a question here: #23752908Musclebound
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?Cotillion
Hi Teemu. How do I produce an initial image map for an image of width:100% ? Thanks.Dreadnought
@Dreadnought 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.Risteau
@Dreadnought Did you notice Barry's question and my answer to it. A little change introduced in that answer makes the ImageMapper more generic.Risteau
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.Livonia
@Livonia 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.Risteau
D
6

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());
}
Derogative answered 21/6, 2019 at 10:44 Comment(2)
See my improvement for VueJS belowCommentator
This is definetely the shortest solution. But it´s extremely low, it takes a lot of time.Cay
M
4

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);
Margy answered 21/1, 2018 at 18:34 Comment(0)
P
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

Phosphoroscope answered 27/12, 2018 at 9:48 Comment(0)
M
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>
Minestrone answered 21/1, 2019 at 17:50 Comment(0)
L
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/

Lamdin answered 12/11, 2014 at 23:41 Comment(3)
The documentation is ambiguous : does it only work for .svg image or for everything (jpg, png...) ?Hyams
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.Hyams
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 anywayHyams
C
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

Caerphilly answered 18/3, 2016 at 22:49 Comment(0)
B
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.

Branen answered 10/11, 2012 at 10:34 Comment(0)
L
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 );
}
Livonia answered 16/10, 2015 at 20:40 Comment(0)
R
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
  });
});
Raspings answered 20/5, 2016 at 9:58 Comment(0)
M
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.

Mien answered 15/12, 2020 at 16:37 Comment(0)
K
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();
    }    
  };
Kuopio answered 8/6, 2022 at 22:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.