activeImageMap script

Jan 2, 2020

Happy 2020 everyone! It's... been a while :)

Here's something for the new year: A little script I've been working on to solve two issues with HTML imagemaps. First, it builds on some code found on StackOverflow to dynamically resize <area> element coordinates so that imagemaps can behave responsively. Second, it appends <a> elements to the <map> element overlapping the <area> elements exactly, so they can be visually styled with CSS.

One of the biggest issues with imagemaps is the fact that the hyperlinks available in the map are invisible and the only indicator they exist is when the user hovers the mouse cursor over them. In a mobile environment, where mouse-hover doesn't make any sense, using imagemaps at all seems like an accessibility mistake on par with mystery-meat navigation.

Here's the code:

/* ********************************************************************
 * activeImageMap w/ link highlight function
 *   - By Brian Huisman (GreyWyvern)
 *
 * Pastes a highlightable <a> link over the <area> element to which you
 * can add CSS styles. For selector purposes, the <a> elements will be
 * appended as children to the <map> element. Links and <area> elements
 * will responsively resize with the dimensions of the image.
 *
 * img => String id of <img> element, or reference to actual element
 * width => The intrinsic width of the image
 * 
 * The script accepts an optional third argument that, when set to
 * false, disables the placing of <a> elements and resizes the <area>
 * elements only.
 *   eg. activeImageMap('image_id', 500, false);
 *
 *
 * NOTES when highlighting <area> elements with shape='poly':
 *  1) Since the script creates a link and crops it with CSS clip-path,
 *     it's recommended to only use background + opacity properties to
 *     style the links.
 *  2) Only works in MS Edge and above; does not work in IE11. This
 *     script can be used safely in IE11 if either:
 *      a) only <area> of shape 'rect' and/or 'circle' are used.
 *           OR
 *      b) link highlighting is disabled by setting third arg to false.
 *
 *
 * Adapted from Andriy Kuba's StackOverflow post:
 *   - https://stackoverflow.com/a/48370297
 */
function activeImageMap(img, width) {
  if (typeof img == 'string') img = document.getElementById(img);
  if (!img.useMap) return console.log('Image does not reference a <map>.');
  var map = document.getElementsByName(img.useMap.replace(/^#/, ''))[0];
  if (!map) return console.log('Associated <map> element could not be found.');

  this.img = img;
  this.link = !(arguments[2] === false);
  this.width = parseInt(width);
  this.areas = [];

  var area = map.getElementsByTagName('area');
  for (var x = 0, a; x < area.length; x++, a = false) {
    area[x].shape = area[x].shape.toLowerCase();
    if (this.link && (
          area[x].shape == 'rect' ||
          area[x].shape == 'circle' ||
          area[x].shape == 'poly')) {
      a = document.createElement('a');
      a.href = area[x].href;
      if (area[x].shape == 'circle')
        a.style.borderRadius = '50%';
      a.style.position = 'absolute';
      map.appendChild(a);
    }
    this.areas.push({
      element: area[x],
      link: a,
      coords: area[x].coords.split(',')
    });
  }

  this.resize = function() {
    clearInterval(this.check);
    var ratio = this.img.offsetWidth / this.width;
    for (var x = 0, tArea; tArea = this.areas[x++];) {
      for (var y = 0, coords = []; y < tArea.coords.length; y++)
        coords.push(Math.round(tArea.coords[y] * ratio));
      tArea.element.coords = coords.join(',');

      if (this.link && tArea.link) {
        switch (tArea.element.shape) {
          case 'rect':
            tArea.link.style.top = coords[1] + 'px';
            tArea.link.style.left = coords[0] + 'px';
            tArea.link.style.width = (coords[2] - coords[0]) + 'px';
            tArea.link.style.height = (coords[3] - coords[1]) + 'px';
            break;
          case 'circle':
            tArea.link.style.top = (coords[1] - coords[2]) + 'px';
            tArea.link.style.left = (coords[0] - coords[2]) + 'px';
            tArea.link.style.width = (coords[2] * 2) + 'px';
            tArea.link.style.height = (coords[2] * 2) + 'px';
            break;
          case 'poly': // Doesn't work in IE11
            var mx = [coords[0], coords[0]], my = [coords[1], coords[1]];
            for (var i = 2; i < coords.length - 1; i += 2) {
              mx = [Math.min(mx[0], coords[i]), Math.max(mx[1], coords[i])];
              my = [Math.min(my[0], coords[i + 1]), Math.max(my[1], coords[i + 1])];
            }
            for (var i = 0, final = []; i < coords.length - 1; i += 2) {
              final.push(
                Math.round((coords[i] - mx[0]) / (mx[1] - mx[0]) * 1000) / 10 + '% ' +
                Math.round((coords[i + 1] - my[0]) / (my[1] - my[0]) * 1000) / 10 + '%'
              );
            }
            tArea.link.style.top = my[0] + 'px';
            tArea.link.style.left = mx[0] + 'px';
            tArea.link.style.width = (mx[1] - mx[0]) + 'px';
            tArea.link.style.height = (my[1] - my[0]) + 'px';
            tArea.link.style.clipPath = 'polygon(' + final.join(',') + ')';
        }
      }
    } return true;
  };

  window.addEventListener('resize', function() { this.resize(); });
  this.check = setInterval(function() {
    if (img.complete) this.resize();
  }, 10);
}

Or download it.

Cheers, and I hope to post here more often in 2020!


Comments closed

Recent posts

  1. Customize Clipboard Content on Copy: Caveats Dec 2023
  2. Orcinus Site Search now available on Github Apr 2023
  3. Looking for Orca Search 3.0 Beta Testers! Apr 2023
  4. Simple Wheel / Tire Size Calculator Feb 2023
  5. Dr. Presto - Now with MUSIC! Jan 2023
  6. Archive