iframe prevents iScroll scrolling on mobile Safari
Asked Answered
M

2

11

I am using iScroll on my mobile enable website (using iPhone here) to scroll inside a div.

In this this div, I have an iframe with a fixed height like this:

<body>
  <div id="iscroller">
    <iframe id="theIframe"></iframe>
    Other stuff
  </div>
</body>

Now, while scrolling within the div, everything works as expected but I cannot scroll when the scrolling gesture begins on the iframe.

The problem is described here pretty well: https://github.com/cubiq/iscroll/issues/41

So, I used the css workaround from that post by applying pointer-events:none to the iframe.

Now scrolling works perfectly but I cannot click any links which are defined within the iframe because all click/touch events on the iframe seems to be blocked due to pointer-events: none.

So, I thought:

"Ok, while the user scrolls, I need pointer-events:none. If he is not scrolling (and instead clicking), I must set pointer-events:auto in order to let the click/touch events pass."

So I did this:

CSS

#theIframe{pointer-events:none}

JavaScript

$("#theIframe").bind("touchstart", function(){
  // Enable click before click is triggered
  $(this).css("pointer-events", "auto");
});

$("#theIframe").bind("touchmove", function(){
  // Disable click/touch events while scrolling
  $(this).css("pointer-events", "none");
});

Even adding this doesn't work:

$("#theIframe").bind("touchend", function(){
  // Re-enable click/touch events after releasing
  $(this).css("pointer-events", "auto");
});

No matter what I do: Either scrolling doesn't work or clicking the link inside the iframe doesn't work.

Doesn't work. Any ideas?

Maestricht answered 6/3, 2013 at 13:43 Comment(0)
M
11

I found the perfect solution. Works great on iOS and Android.

The basic idea is to put a div layer on top of that iframe. This way scrolling works smoothly.

If the user wants to tap/click on an element on that iframe I simply catch that click on the layer, save the x and y coordinates and trigger a click event on the iframe's content at these coordinates:

HTML:

<div id="wrapper">
    <div id="layer"></div>
    <iframe id="theIframe"></iframe>
</div>
Other stuff

CSS:

#layer{
    position:absolute;
    opacity:0;
    width:100%;
    height:100%;
    top:0;
    left:0;
    right:0;
    bottom:0;
    z-index:2
}

JavaScript:

$('#layer').click(function(event){
    var iframe = $('#theIframe').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);
});
Maestricht answered 18/3, 2013 at 11:10 Comment(6)
there is not other way to do this? this is a smart way to do it, but i dont have control over the iframe, and if it does change im screw, thanks mate :)Trilingual
@jycr753 If your user don't need to click links inside the iframe, you could use css attribute pointer-events:none instead.Maestricht
What if the user need to be able to do a swipe or a pinch in the iFrame?Subphylum
@SimonArnold Well, WITHIN the iframe it would work but all the touch events are being "sucked" into the iframe so if you expect touch events (like swipe and stuff) to be triggered outside the iframe (like on top of it), it won't work.Maestricht
Thank you thank you thank you! It's a hack, but it's the only one that I've found works for this situation. @jycr753 you don't need control of the contents of the iframe to use this solution.Saporific
Please do note that this solution won't work on iFrames loading another domain (e.g. Google Forms) due to cross-origin security policiesCacia
J
1

I found a solution for this, it happens to be close to what other guys already mentioned on github but this may be useful for whoever wants to find a fast working resolution for this problem.

I'm assuming a few things, like there's only one iscroll container, here represented as ID. This is not properly tested and needs refactor. It's working in my project, but I changed it here slightly for the example but I guess you'll easily understand what you need to do:

var $iscroll = $('#iscroll');

document.addEventListener('touchstart', function(e) {

if ($iscroll.find('iframe').length > 0){

    $.each($iscroll.find('iframe'), function(k,v){

        var $parent = $(v).parent().first();

        if ($parent.find('.preventTouch').length == 0){

            $('<div class="preventTouch" style="position:absolute; z-index:2; width:100%; height:100%;"></div>')
                .prependTo($parent);

        };

        $parent
            .css('position', 'relative').css('z-index', 1);

    });

    $iscroll.find('.preventTouch').on('click', function(e){
        e.preventDefault();
        e.stopPropagation();
        return false;
    });

};

};

document.addEventListener('touchend', function(e) {

if ($iscroll.find('iframe').length > 0){

    setTimeout(function(){

        var $iscroll = $('#iscroll');

        $iscroll.find('.preventTouch').remove();
        $iscroll.find('iframe').css('z-index', '');
        $iscroll.find('.preventTouch').off('click');

    }, 400);

};

};

Thanks for looking!

Josephus answered 8/11, 2013 at 16:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.