20

Safari on iOS 10.1.1 seems to have a bug when setting focus on an element inside an iframe.

When we call element.focus() on an element inside an iframe, Safari will immediately scroll the parent page down and move the focussed element off-screen (instead of scrolling the focussed element into view).

However, it only happens if the element is in an iframe that is taller than the device screen height (shorter iframes are OK).

So if there are two elements, one at the top of the iframe and another one further down the page, the first will focus fine but the second one will jump off-screen when we set focus.

To me it looks like Safari is trying to scroll the element into view but the maths is wrong and they end up scrolling to a random position further down the page. Everything works OK in iOS9 so I think this is a new bug in iOS10.

Is some way of preventing the parent page from scrolling or some other way of avoiding this bug?

I've put together a one-page gist that replicates the issue on iOS 10 devices or the Simulator.

Here is a a URL you can actually use on a phone: goo.gl/QYi7OE

Here's a plunker so you can check desktop behaviour: https://embed.plnkr.co/d61KtGlzbVtVYdZ4AMqb/

And a gist version of that plunker: https://gist.github.com/Coridyn/86b0c335a3e5bf72e88589953566b358

Here's a runnable version of the gist (the shortened URL above points here): https://rawgit.com/Coridyn/86b0c335a3e5bf72e88589953566b358/raw/62a792bfec69b2c8fb02b3e99ff712abda8efecf/ios-iframe-bug.html

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="apple-mobile-web-app-capable" content="yes" />

    <script>
    document.addEventListener('DOMContentLoaded', function(){
        document.querySelector('#frame').srcdoc = document.querySelector('#frameContent').innerHTML;
    });
    </script>
</head>
<body>
    <iframe id="frame" frameborder="1"
        style="width: 100%; height: 1200px;"
        width="100%" height="1200">
    </iframe>

    <!--
    To keep this a one-page example, the content below will be inserted into the iframe using the `srcdoc` attribute.

    The problem occurs in iOS10 regardless of using `srcdoc` or `src` (and regardless of same-domain or cross-domain content).
    -->
    <script id="frameContent" type="text/template">
<div>
    <style>
        .spacer {
            padding-top: 400px;
            padding-bottom: 400px;
        }
        .green-block {
            width: 20px;
            height: 20px;
            background-color: green;
        }
        .red-block {
            width: 20px;
            height: 20px;
            background-color: red;
        }
    </style>

    <div class="green-block" tabindex="0"></div>

    <p>Scroll down and press the 'Focus Green' or 'Focus Red' button.</p>

    <h2>'Focus Green'</h2>
    <p><b>Expected:</b> should set focus on '.green-block' and scroll to the top of the page.</p>
    <p><b>Actual:</b> sets focus but does not scroll page.</p>

    <h2>'Focus Red'</h2>
    <p><b>Expected:</b> should set focus on '.red-block' and not scroll page (because element is already on-screen).</p>
    <p><b>Actual:</b> sets focus and scrolls down to the bottom of the host page.</p>

    <hr/>

    <p class="spacer">1 Filler content to force some visible scrolling 1</p>

    <div class="red-block" tabindex="0"></div>

    <div>
        <button type="button" onclick="document.querySelector('.green-block').focus();">Focus Green</button>
        <p>'Focus Green' should go to top of page, but on iOS10 the view doesn't move.</p>
    </div>
    <div>
        <button type="button" onclick="document.querySelector('.red-block').focus();">Focus Red</button>
        <p>'Focus Red' should stay here, but on iOS10 the view scrolls to the bottom of the page</p>
    </div>

    <p class="spacer">20 Filler content to force some visible scrolling 20</p>
    <p>Bottom of iframe</p>

</div>
    </script>

</body>
</html>

Things we've found:

  • The issue happens on iOS 10+ in both Safari and Chrome
  • The issue does not happen on Safari iOS 9.3.2 (behaviour matches desktop browser, as expected)
  • The problem only happens when focussing on non-input HTML elements e.g. DIVs, SPANs, anchor tags, etc; setting focus on INPUT elements works correctly
  • Desktop Safari works fine
  • It's the parent page that is scrolling, not the iframe (the iframe is sized to 100% of its content to avoid scrolling inside the frame)
  • We've tried calling preventDefault on the parent page with: window.onscroll = (event) => event.preventDefault(); but this doesn't work because the scroll event is not cancelable
  • The scroll event raised on the parent page seems to come from Safari/Webkit itself because there are no other functions in the callstack (when inspecting with DevTools and Error.stack)
Sly_cardinal
  • 12,270
  • 5
  • 49
  • 50
  • I'm experiencing the same issue. I'm filing a bug report with Apple. Also, I was having a problem accessing your plunkr preview on my iPhone, so I uploaded the files here: http://jeremyjon.es/apple-bug-report-iframe-page-jump/ (I also added a blue outline on focus for the elements because the focus ring wasn't showing on my phone.) – Jerry Jan 18 '17 at 16:46
  • Thanks for that! I was having a lot of trouble getting a public version you could run on an iOS device. – Sly_cardinal Feb 02 '17 at 01:29
  • I have a similar issue, but where the input cursor scrolls with the parent, ie out of the iframe's input field. I am also not able to click buttons until onscreen keyboard is retracted. Happens on devices and simulator, ios 11. Did you get any further with your problem @Sly_cardinal? – Jørgen Tvedt Jan 11 '18 at 10:12
  • @JørgenTvedt, did you get a solution to this? – Andrew Feb 20 '18 at 16:03
  • @JørgenTvedt no, we haven't found a solution yet. Our only workaround was to detect iOS devices and avoid setting focus on non-input elements (which I understand is not an option in a lot of cases). I've opened a bug in their tracking system but nothing has happened with it yet: https://bugs.webkit.org/show_bug.cgi?id=164512 – Sly_cardinal Feb 21 '18 at 01:10
  • 1
    @Andrew @Sly_cardinal, my problems was primarily caused by using a `fixed` position on the iframe. I turned to using `absolute` position when showing a form. Now it works acceptable. Might not be related to the problem this question is about. – Jørgen Tvedt Mar 11 '18 at 17:33

2 Answers2

2

I had a similar issue with scroll jumping around on iOS. It was happening on all iOS versions for me, 8, 9 and 10.

Testing your plunker in a real iOS 10 device didn't cause the problem for me. I am not sure if I tested correctly.

Can you try this version of your plunker on iOS? https://embed.plnkr.co/NuTgqW/

My workaround for my issue was to make sure the body and html tag in the iframe content had a defined 100% height and width, and an overflow scroll. This was able to force the iframe's dimension. It prevented the scroll jumping.

Let me know if it helps.

Nic128
  • 472
  • 4
  • 8
0

In my situation a page contained an iframe with an input. On page load the input got focus. This caused the parent page to be scrolled to the position of the input. It happened in all browsers.

Solution: when the page started to scroll, set the scroll position back to the top.

Because the issue happened only once, I used a namespaced event to remove a listener as soon as the undesired scroll has been prevented.

$(function() {
    if ($('#iframe').length) {
        var namespace = 'myNamespace';
        $(window).on('scroll.' + namespace, function() {
            $(window).scrollTop(0).off('scroll.' + namespace);
        });
    }
});
Arsen K.
  • 5,494
  • 7
  • 38
  • 51