7

I am having an issue with Bootstrap 4.1 modals displayed within iframes in Safari on iOS 12. Every other browser tested works as expected (even Safari on iOS 11). The issue seems to be specific to iOS 12.

I have created a minimal example demonstrating the issue. The first two buttons seem to function as expected, however the last 4 you can see the issue, with each one being worse as you traverse downward, where the last one disappearing all together when you attempt to scroll or focus on an element inside of the modal (see below screenshots):

enter image description here enter image description here enter image description here

I will note that we are handling this functionality in a way that may be a tad unorthodox, as instead of allowing the content of the iframe to scroll, we adjust it's height after it has loaded by passing messages between parent and child via a message event handler and postMessage: https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage This is where I suspect something has gone afoul (but have yet been able to track it down (and as mentioned previously this is only an issue on ios devices running version 12)).

Edit

It has recently been discovered that this issue is not specific to Safari on iOS 12, but chrome as well.

Code below is from the previous minimal example link:

Parent (/modal-test/index.html):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>Title</title>

    <link rel="stylesheet" href="./bootstrap.min.css">

    <script src="./jquery.min.js"></script>
    <script src="./popper.min.js"></script>
    <script src="./bootstrap.min.js"></script>

    <script>
        $(document).ready(function(){

            $ifCon = $("#ifCon");

            window.addEventListener("message", function(event){
                if(event.data.method === "returnWindowSize"){
                    $ifCon.height(event.data.content);
                }
            }, false);

        });
    </script>

    <style>

        #ifCon {
            display: flex;
            width: 100%;
            height: 100%;
            flex-direction: column;
            background-color: #F2F2F2;
            overflow: hidden;
            border-radius:10px;
            border:1px solid grey;
            margin-left:auto;
            margin-right:auto;
            /*box-shadow:2px 2px 3px #000;*/
            box-shadow: 0px 1px 3px 0px rgba(0,0,0,0.75);
        }
        #ifCon iframe {
            flex-grow: 1;
            border: none;
            margin: 0;
            padding: 0;
        }

    </style>


</head>
<body>

    <div class="container">

        <div class="row">

            <div class="col text-center">

                <div id="ifCon">
                    <iframe height="100%" width="100%" scrolling="no" src="/modal-test/frameable.html"></iframe>
                </div>

            </div>

        </div>

    </div>

</body>
</html>

Child (/modal-test/frameable.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>Title</title>

    <link rel="stylesheet" href="./bootstrap.min.css">

    <script src="./jquery.min.js"></script>
    <script src="./popper.min.js"></script>
    <script src="./bootstrap.min.js"></script>

    <script>

        var $embedContent, modalState;


        $(document).ready(function(){

            $embedContent = $('#embedContent');

            parent.postMessage({method:"returnWindowSize", content:$embedContent.height()}, '*');


            $('.modal').on('shown.bs.modal', function(e){

                modalState = {
                    id:$(this).attr('id'),
                    contentElement:$(this).find('.modal-content'),
                    initialContentContainerHeight:$embedContent.height(),
                    invokerElement:$(e.relatedTarget)
                };

                adjustModal();

            });

            $('.modal').on('hidden.bs.modal', function(e){
                modalState = null;
                $(this).find('.modal-content').css("margin-top", "0px");
                $embedContent.css('height', 'auto');
                parent.postMessage({method:"returnWindowSize", content:$embedContent.height()}, '*');
            });


        });


        function adjustModal(){

            if(modalState.contentElement.css('margin-top') !== modalState.invokerElement.offset().top){
                modalState.contentElement.animate({'margin-top':modalState.invokerElement.offset().top}, 200, "linear");
            }

            if(

                // modal position + modal height is greater than or equal to the height of the embedContent so we need to resize the
                // embedContent (make it taller)
                ((modalState.invokerElement.offset().top + modalState.contentElement.height()) >= $embedContent.height()) ||

                // modal position + modal height is less than or equal to the height of the embedContent AND the current height of the
                // embedContent is greater than or equal to the size of the embedContent height when the modal was originally shown
                (((modalState.invokerElement.offset().top + modalState.contentElement.height()) <= $embedContent.height()) &&
                    ($embedContent.height() > modalState.initialContentContainerHeight))

            ){
                var newEmbedContentHeight = modalState.invokerElement.offset().top + modalState.contentElement.height() + 30;
                $embedContent.height(newEmbedContentHeight);
                parent.postMessage({method:"returnWindowSize", content:newEmbedContentHeight}, '*');
            }

        }

    </script>


</head>
<body>


    <div id="embedContent" class="container">

        <div class="row" style="height:200px;margin-top:100px;">
            <div class="col text-center">
                <button id="btn1" type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal">
                  Launch demo modal
                </button>
            </div>
        </div>

        <div class="row" style="height:200px;">
            <div class="col text-center">
                <button id="btn2" type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal">
                  Launch demo modal
                </button>
            </div>
        </div>

        <div class="row" style="height:200px;">
            <div class="col text-center">
                <button id="btn3" type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal">
                  Launch demo modal
                </button>
            </div>
        </div>

        <div class="row" style="height:200px;">
            <div class="col text-center">
                <button id="btn3" type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal">
                  Launch demo modal
                </button>
            </div>
        </div>

        <div class="row" style="height:200px;">
            <div class="col text-center">
                <button id="btn3" type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal">
                  Launch demo modal
                </button>
            </div>
        </div>

        <div class="row" style="height:200px;">
            <div class="col text-center">
                <button id="btn3" type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal">
                  Launch demo modal
                </button>
            </div>
        </div>

    </div>


    <div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" 
        aria-labelledby="exampleModalLabel" aria-hidden="true" data-focus="false" style="overflow-y:hidden;">
      <div class="modal-dialog" role="document">
        <div class="modal-content" style="margin-top:500px;">
          <div class="modal-header">
            <h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
            <button type="button" class="close" data-dismiss="modal" aria-label="Close">
              <span aria-hidden="true">&times;</span>
            </button>
          </div>
          <div class="modal-body">
            <form>
              <div class="form-group">
                <label for="exampleFormControlInput1">Email address</label>
                <input type="email" class="form-control" id="exampleFormControlInput1" placeholder="name@example.com">
              </div>
              <div class="form-group">
                <label for="exampleFormControlInput1">Email address</label>
                <input type="email" class="form-control" id="exampleFormControlInput1" placeholder="name@example.com">
              </div>
              <div class="form-group">
                <label for="exampleFormControlSelect1">Example select</label>
                <select class="form-control" id="exampleFormControlSelect1">
                  <option>1</option>
                  <option>2</option>
                  <option>3</option>
                  <option>4</option>
                  <option>5</option>
                </select>
              </div>
              <div class="form-group">
                <label for="exampleFormControlSelect1">Example select</label>
                <select class="form-control" id="exampleFormControlSelect1">
                  <option>1</option>
                  <option>2</option>
                  <option>3</option>
                  <option>4</option>
                  <option>5</option>
                </select>
              </div>
              <div class="form-group">
                <label for="exampleFormControlSelect1">Example select</label>
                <select class="form-control" id="exampleFormControlSelect1">
                  <option>1</option>
                  <option>2</option>
                  <option>3</option>
                  <option>4</option>
                  <option>5</option>
                </select>
              </div>
              <div class="form-group">
                <label for="exampleFormControlSelect1">Example select</label>
                <select class="form-control" id="exampleFormControlSelect1">
                  <option>1</option>
                  <option>2</option>
                  <option>3</option>
                  <option>4</option>
                  <option>5</option>
                </select>
              </div>
            </form>
          </div>
          <div class="modal-footer">
            <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
            <button type="button" class="btn btn-primary">Save changes</button>
          </div>
        </div>
      </div>
    </div>




</body>
</html>
Nidhin Joseph
  • 9,981
  • 4
  • 26
  • 48
whitwhoa
  • 2,389
  • 4
  • 30
  • 61
  • Why is overflow-y set to hidden? – Roya Aug 18 '19 at 01:06
  • @Roya overflow-y is hidden because there should be no scroll bars. The height of the iframe container (and thus the iframe) within the parent are adjusted if the height of the child page is altered. This code is for an embedable widget which needs to appear as if it is part of a third party site, and not a frame within it. – whitwhoa Aug 18 '19 at 19:27

3 Answers3

3

The solution to this ended up being that we needed to force hardware acceleration by including the following css in the <head></head> of the child document (element within the iframe (frameable.html in our case)):

<style>
    body{
        transform: translate3d(0,0,0);
    }
</style>

I however cannot explain why this resolves the issue, or why it's required for ios12 as opposed to ios11. If anyone else can shed some light onto that topic, I'm sure it would help someone else down the road.

whitwhoa
  • 2,389
  • 4
  • 30
  • 61
  • 1
    This solves the issue because [`transform` creates a new *stacking context*](https://stackoverflow.com/a/20852489/1813169). – MTCoster Aug 26 '19 at 13:23
0

You need to set margin-top for model-content as the scroll height of the parent document.

This would put the document exactly on top, from where you may need to provide some offset value like +80 to make it like not stuck to the top.

'margin-top':modalState.invokerElement.offset().top

'margin-top':parent.document.documentElement.scrollTop
Nidhin Joseph
  • 9,981
  • 4
  • 26
  • 48
  • `model-content` must align with the element that invoked it. Since this modal is within an iframe, if we set it's `margin-top` to the parent scrollTop position, any fixed or absolute header would always hide the top of the modal (this is for an embedable widget for third party sites, so we wouldn't be able to adjust for it). – whitwhoa Aug 25 '19 at 20:59
  • when you say `align with the element that invoked it.`, where should the modal appear? like, if its the last button you have. – Nidhin Joseph Aug 25 '19 at 21:48
  • If last button clicked, then the top of `modal-content` is aligned with the top of the last button, with the height of `#ifcon` adjusted so that the modal is not cut off. This can be seen in the live example link on a device that is not running ios12. – whitwhoa Aug 25 '19 at 22:01
0

In your CSS for #ifCon add

#ifCon {
  height: 100vh;
 }

feel free to mess with the numbers to your preference, VH sets a View Height to 100%.