jquery: fancybox 3, responsive image maps and zoomable content

542 Views Asked by At

I want to use image-maps inside fancybox 3. Goal is to display mountain panoramas, where the user could point on a summit and get name and data. The usual recommendation is to use a SVG based image map for this like in this pen. Due to the size of the images the fancybox zoom functionality is important.

While fancybox will display SVGs as an image like in this pen it is not possible to use the <image> tag with an external source inside the SVG file. Even worse: SVG files used as source of an <img> tag would not show the map functionality (see this question).

I tried to replace the <img> tag in fancybox with an <object> tag using the SVG file as data attribute. This shows the image with the map functionality correctly but fancybox won't zoom it any more.

So eventually the question boils down to how I can make an <object> (or an inline SVG or an iframe) zoomable just like an image in fancybox 3.

I'm open to other solutions as well. I only want to use fancybox to keep the appearance and usage the same as other image galleries on the same page. I'd even use an old style <map>, where I would change the coords using jquery to have it responsive. I tried that, attaching the map manually in developer tools as well as programmatically in afterLoad event handler, but apparently this doesn't work in fancybox 3 either.

The areas are polygons, so using positioned div's as overlays is no solution.

Edit: I just discovered that I can replace <img> with a <canvas> in fancybox without loosing the zoom and drag functionality. So in theory it would be possible to use canvas paths and isPointInPath() methode. Unfortunately I need more than one path, which requires the Path2D object, which is not available in IE...

1

There are 1 best solutions below

0
On

Since all options discussed in the question turned out to be not feasible and I found the pnpoly point in polygon algorithm, I did the whole thing on my own. I put the coordinates as percentages (in order to be size-independent) in an array of javascript objects like so:

    var maps = {
      alpen : [
        {type:'poly',name:'Finsteraarhorn (4274m)',vertx:[56.48,56.08,56.06,56.46], verty:[28.5,28.75,40.25,40.25]},
        {type:'rect',name:'Fiescherhörner (4049m)',coords:[58.08,29.5,59.26,43.5]},
        {type:'poly',name:'Eiger (3970m)',vertx:[61.95,61.31,61.31,60.5,60.5], verty:[43,35.25,30.25,30.25,45.5]}
  ] 
}; // maps

Since the pnpoly function requires the vertices for x and y separately I provide the coordinates this way already.

The Id of the map is stored in a data attribute in the source link:

  <a href="/img/bilder/Alpen.jpg" data-type='image' data-Id='alpen' data-fancybox="img" data-caption="<h5>panorama of the alps from the black forest Belchen at sunset</h5>">
    <img src="/_pano/bilder/Alpen.jpg">
  </a>

CSS for the tooltip:

.my-tooltip {
    color: #ccc;
    background: rgba(30,30,30,.6);
    position: absolute;
    padding: 5px;
    text-align: left;
    border-radius: 5px;
    font-size: 12px;
}

pnpoly and pnrect are provided as simple functions, the handling of that all is done in the afterShow event handler:

// PNPoly algorithm checkes whether point in polygon
function pnpoly(vertx, verty, testx, testy) {
    var i, j, c = false;
    var nvert = vertx.length;
    for(i=0, j=nvert-1; i<nvert; j=i++) {
        if (((verty[i] > testy) != (verty[j] > testy)) &&
            (testx < (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + vertx[i])) {
                c = !c;
        }
    }
    return c;
}

// checks whether point in rectangle
function pnrect(coords,testx,testy) {
  return ((testx >= coords[0]) && (testx <= coords[2]) && (testy >= coords[1]) && (testy <= coords[3]));
}

$("[data-fancybox]").fancybox({
   afterShow: function( instance, slide ) {
     var map = maps[$(slide.opts.\$orig).data('id')]; // Get map name from source link data-ID
     if (map && map.length) {                         // if map present
       $(".fancybox-image")
         .after("<span class='my-tooltip' style='display: none'></span>") // append tooltip after image
         .mousemove(function(event) {                                     // create mousemove event handler
          var offset = $(this).offset();                                  // get image offset, since mouse coords are global
          var perX = ((event.pageX - offset.left)*100)/$(this).width();   // calculate mouse coords in image as percentages
          var perY = ((event.pageY - offset.top)*100)/$(this).height();
          var found = false;
          var i;
          for (i = 0; i < map.length; i++) {                              // loop over map entries
            if (found = (map[i].type == 'poly')                           // depending on area type
                          ?pnpoly(map[i].vertx, map[i].verty, perX, perY) // look whether coords are in polygon
                          :pnrect(map[i].coords, perX, perY))             // or coords are in rectangle
              break;                                                      // if found stop looping
          } // for (i = 0; i < map.length; i++)
          if (found) {
            $(".my-tooltip")
              .css({bottom: 'calc(15px + '+ (100 - perY) + '%'})  // tooltip 15px above mouse coursor
              .css((perX < 50)                                    // depending on which side we are
                     ?{right:'', left: perX + '%'}                // tooltip left of mouse cursor
                     :{right: (100 - perX) + '%', left:''})       // or tooltip right of mouse cursor
              .text(map[i].name)                                  // set tooltip text
              .show();                                            // show tooltip
          } else {
            $(".my-tooltip").hide();                              // if nothing found: hide.
          }
       });
     } else { // if (map && map.length)         // if no map present
       $(".fancybox-image").off('mousemove');   // remove event mousemove handler
       $(".my-tooltip").remove();               // remove tooltip
     }  // else if (map && map.length)
   } // function( instance, slide )
});

Things left to do: Find a solution for touch devices, f.e. provide a button to show all tooltips (probably rotated 90°).

As soon as the page is online I'll provide a link here to see it working...