Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swiping over an iframe, passing click events, and elementFromPoint returning null on imagemap area

I have an HTML document that includes 3 iframes representing previous, current, and next pages. I'm trying to enable page swiping (via the jQuery touchswipe plugin), but also let clicks go through to the document inside the iframe. Here's the HTML:

<body>
    <div id="pages-wrapper">
        <div id="page1" class="page-div previous-page">
            <iframe id="frame1" class="page-frame"></iframe>
        </div>
        <div id="page2" class="page-div active-page">
           <iframe id="frame2" class="page-frame"></iframe>
        </div>
        <div id="page3" class="page-div next-page">
           <iframe id="frame3" class="page-frame"></iframe>
        </div>                      
    </div> 
</body>

When the pages-wrapper element is swiped, either the previous or next page becomes the active-page. Pages are sized 100% and the active page fills the browser's viewport. Everything is within the same domain. The iframe's documents can contain images with imagemaps.

Unfortunately, the iframes capture the mouse events, disabling the swipe capability on the parent pages. As others have suggested, the answer is to overlay a transparent div, use elementFromPoint to locate the target in the iframe's document, and manually dispatch a click event to the target.

I've implemented this using the coverIframes plugin:

$.fn.coverIframes = function(){
    $.each($("iframe",this),function(i,v){
        var ifr = $(v);
        var wr = $("<div id='wr"+new Date().getTime()+i+"' style='z-index: 999999; opacity: 0; position:absolute; width:100%;'></div>");
        ifr.before(wr);
        wr.height(ifr.height());
        wr.click(function(event){
           var iframe = ifr.get(0);
           var iframeDoc = (iframe.contentDocument) ? iframe.contentDocument : iframe.contentWindow.document;
           // Find click position (coordinates)
           var x = event.offsetX;
           var y = event.offsetY;
           // Trigger click inside iframe
           var link = iframeDoc.elementFromPoint(x, y);
           var newEvent = iframeDoc.createEvent('HTMLEvents');
           newEvent.initEvent('click', true, true);
           link.dispatchEvent(newEvent);
        });
    })
};

Although this approach works for most elements, it does not do so for an imagemap's areas -- at least within the Chrome browser (ver 32). In these instances, it returns null. In trying to find a workaround, here are 3 questions that came to mind:

  1. Is there a way to pass a click event in the parent directly to the iframe window and let it handle the event exactly as if the user had clicked on the window? (i.e., the window finds the target, and bubbles the event up through the dom's elements.)

  2. Is there any alternative function similar to elementFromPoint that reliably returns the target element?

  3. If I want to manually handle the null result on an area, is there an existing JavaScript function or jQuery plugin that will determine if a given point is within a map's area? (I've seen some functions for a finding a point in a polygon.)

like image 602
JeffR Avatar asked Jan 24 '14 22:01

JeffR


1 Answers

I'm not going to tell you how to use transparent overlays and event capturing. I have done this, and it can work, but it is not worth it.

But don't worry, the good news is: we have AJAX!

Don't use iframe for this; you can easily eliminate all the hassles of having multiple window objects. If all pages are on the same domain like you said, use $.ajax({url:'XXX.html'}) to get the content and load it into <div id='page1'></div> instead.

Like in this example: https://jsfiddle.net/q69d0L63/

Thoughts:

1) Requested pages would probably need a CORS header if they are not in same domain (but you said same domain, so no problemo).

2) Contents of the requested URL doesn't have to be a full HTML document, it could be only HTML document fragment.

Sample code (doesn't work on SO due to headers, but works on JSFiddle. To verify, monitor the JS console and network traffic while you click):

var urls = ['page1.html', 'page2.html', 'page3.html']; // just an example; test data
var curIdx = -1;
swipe = function(e) {
  getNextPage();
}
getNextPage = function() {
  curIdx = (curIdx + 1) % 3;
  var url = urls[curIdx];
  $("#page_content").html('Loading...');
  $.ajax({
    url: url,
    async: true,
    success: function(data) {
      var html = $(data).find("span:first"); // filter input if desired
      $("#page_content").html(html.html());
    },
    error: function(jqXHR, status, error) {
      $("#page_content").html([status, error].join(': '));
    }
  });
}
getNextPage();
#page_content {
  background-color: #eee;
  padding: 10px;
  font-family: 'Segoe UI', Arial;
  font-size: 20px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type='button' value='Touch or click to simulate swipe' onclick='swipe();' />

<h3>Content loaded by AJAX</h3>

<div id='page_content'></div>
like image 156
nothingisnecessary Avatar answered Nov 18 '22 04:11

nothingisnecessary