Workaround to iOS 11 WebKit iframe bugs

Krzysztof Przygoda picture Krzysztof Przygoda · Apr 10, 2018 · Viewed 12.3k times · Source

iOS WebKit iframe bug description

iOS WebKit resizes iframes to the full size of their content (see pic below). It's a bug known since 2016 and still not resolved in iOS 11: https://bugs.webkit.org/show_bug.cgi?id=155198

iOS 11

My current findings

1. For fixed iframe content (e.g. video)

It's enough to apply CSS below, but it prevents iframe content scrolling.

.fixed iframe {
    width: 0;
    height: 0;

    min-width: 100%;
    min-height: 100%;    
}

2. For scrollable iframe content (e.g. pages)

  1. We need two iframe containers: one as a boundary (fixed size) and second one as a scrolling area.
  2. To fit iframe contents, its div container must have w/h defined in pixels. Any relative measures (like %, vw/vh) doesn't work.
  3. Some RWD pages (let's say with "incomplete RWD") are experiencing iframe overflow (iframe does not fit into the iframe container). Unfortunately, we can't fix that from the iframe outside and to solve this issue, document inside iframe requires at least:

    body {
        max-width: 100vw !important;
    }
    

    Optionally, we can scale iframe content as a last resort.

  4. Because of 2, to keep container proportion we need to use at least CSS media queries or JS to adjust its height.

Some incomplete solutions:

My workaround is posted in answer.

Answer

Krzysztof Przygoda picture Krzysztof Przygoda · Apr 10, 2018

This is what I've come so far. Any contribution much appreciated. Most recent version on Github Gist.

/* 1. Beautifiers (optional) */

iframe {
    border: none;
    width: 100%;
    height: 100%;
}

.simple-container {
    width: 50vw;
    height: 50vh;
    padding: 1em;
}

/* 2. Resolving iOS iframe rendering issue */

/* 2.1. Sizing reorganization (obligatory) */

.popover {
    /* To control popover size by .popover-body instead of .popover */
    max-width: 100% !important;
}

.popover-body {
    box-sizing: border-box;
    max-width: 100%;
    max-height: 100%;
}

.iframe-container,
.iframe-container iframe {
    width: 100%;
    height: 100%;
    
    margin: 0 !important;
    padding: 0 !important;
    box-sizing: border-box;
}

.fixed iframe {
    /* This only fits iframe inside iframe-container but prevents scrolling */
    width: 0;
    height: 0;
    
    min-width: 100%;
    min-height: 100%;
}

.popover-body {
    width: 640px; height: 360px;
}

/* 2.2. RWD Resizings (optional) */

@media only screen and (max-width: 568px)
{
    .rwd .popover-body {
        width: 320px; height: 180px;
    }    
}

@media only screen and (min-width: 569px) and (max-width: 965px)
{
    .rwd .popover-body {
        width: 480px; height: 270px;
    }    
}

@media only screen and (min-width: 968px) and (max-width: 1023px)
{
    .rwd .popover-body {
        width: 640px; height: 360px;
    }    
}

/* 2.3. Resolving iOS iframe scrolling issue (obligatory) */

/*
    Due to iOS WebKit bug an iframe content cannot be scrolled, because WebKit renders entire iframe content:
    https://bugs.webkit.org/show_bug.cgi?id=155198
    (still not resolved on iOS11)
    The workaround is to scroll an div container content with full iframe rendered inside.
*/

.scroll {
    overflow: scroll !important;
    -webkit-overflow-scrolling: touch !important;
}

/* 2.4. Resolving iframe and container double scrollbars on desktop browsers (rather obligatory) */

.no-scrollbar {
    position: relative;
}

.no-scrollbar iframe {
    position: absolute;
    top: 0;
    left: 0;
}

.no-scrollbar {
    /* Hide scrollbars in IE Edge */
    /* Autohiding is needed inside iframe document */
    /*-ms-overflow-style: -ms-autohiding-scrollbar;*/
    /* In the parent iframe container we don't want any scrollbars */
    -ms-overflow-style: none;
}

/* 3. Scale non-RWD iframe content (optional) */

/* Warning! iOS 11 Safari crashes on two-fingers zoom of a page with scaled iframe */

.scale {   
    -ms-transform-origin: 0 0;
    -moz-transform-origin: 0 0;
    -o-transform-origin: 0 0;
    -webkit-transform-origin: 0 0;
    transform-origin: 0 0;    
}

.scale.x2 {
    width: 200% !important;
    height: 200% !important;
    
    -ms-transform: scale(0.5);
    -moz-transform: scale(0.5);
    -o-transform: scale(0.5);
    -webkit-transform: scale(0.5);
    transform: scale(0.5);
}

.scale.x4 {
    width: 400% !important;
    height: 400% !important;
    
    -ms-transform: scale(0.25);
    -moz-transform: scale(0.25);
    -o-transform: scale(0.25);
    -webkit-transform: scale(0.25);
    transform: scale(0.25);
}

/* DEBUG */

/* To receive click events on iOS */
/*
* {
    cursor: pointer;    
}
*/
.popover-body {
    border: 1px green dotted;
}

.simple-container,
.iframe-container {
    border: 1px blue dotted;
}

iframe {
    border: 1px red dotted;
}
<!DOCTYPE html>
<html lang="en">
    <head>
        <title>iOS iframes</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta http-equiv="cache-control" content="no-cache" />

        <!-- Solution -->
        <link rel="stylesheet" href="iframe.css" />
        
        <!-- Bootstrap with Popover -->
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" />
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.bundle.min.js"></script>    
        <script>
            jQuery(function ($) {
                $('a.popover-me').on('click', function(e) {
                    e.preventDefault();
                    if (!$(this).data('bs.popover')) $(this).popover({
                        container: 'body',
                        boundary: 'window',
                        placement: 'auto',
                        trigger: 'manual',
                        html: true,
                        title: $(this).text(),
                        content: '<div class="iframe-container scroll no-scrollbar"><iframe src="' + this.href + '"></iframe></div>'
                    });
                    $(this).popover('toggle');
                });
            });
        </script>
    </head>
    <body class="rwd" style="padding: 2em;">
        <h2>Embracing iOS WebKit weirdness with iframes</h2>
        <div class="alert alert-primary" role="alert">
            Ready for Bootstrap v4.0.0 <a class="popover-me" href="https://en.wikipedia.org/wiki/WebKit">Popover</a>.
        </div>
        <div class="alert alert-danger" role="alert">
            Display this page on iOS device.
        </div>        
        <h3>1. Workaround for scrollable iframe</h3>
        
        <p>
            <div class="popover-body">
                <div class="iframe-container scroll no-scrollbar">
                    <iframe src="https://en.wikipedia.org/wiki/WebKit"></iframe>
                </div>
            </div>
        </p>
        
        <div class="alert alert-warning" role="alert">
            <strong>Hint: </strong>
            <em>
                Some RWD pages (let's say with "incomplete RWD") are experiencing iframe overflow (iframe does not fit into the iframe-container).
                Unfortunately, we can't fix that from the iframe outside and to solve this issue, document inside iframe requires at least:
            </em>
            <br /><br />
<pre>
body {
    /* Resolves iOS overflow rendering bug */
    max-width: 100vw !important;
}
</pre>
            <em>
                Optionally, you can scale iframe document as below.
            </em>
        </div>
        
        <h3>2. Workaround for non-RWD scrollable iframe</h3>
        
        <em>
            Page inside iframe is zoomed out to 50%.
        </em>
        
        <p>
            <div class="popover-body">
                <div class="iframe-container scroll no-scrollbar scale x2">
                    <iframe src="https://en.wikipedia.org/wiki/WebKit"></iframe>
                </div>
            </div>
        </p>
        
        <h3>3. Workaround for fixed iframe</h3>
        
        <em>
            iframe fits in iframe-container.
        </em>
        
        <p>
            <div class="popover-body">
                <div class="iframe-container fixed scroll no-scrollbar">
                    <iframe src="https://en.wikipedia.org/wiki/WebKit"></iframe>
                </div>
            </div>
        </p>
        
        <h3>4. [BUG] Plain iframe inside simple container</h3>
        
        <em>
            iframe should fit into simple container.
        </em>

        <p>
            <div class="simple-container">
                <iframe src="https://en.wikipedia.org/wiki/WebKit"></iframe>
            </div>
        </p>
    </body>
</html>