ChatGPT解决这个技术问题 Extra ChatGPT

iOS 11 Safari bootstrap modal text area outside of cursor

With iOS 11 safari, input textbox cursor are outside of input textbox. We did not get why it is having this problem. As you can see my focused text box is email text input but my cursor is outside of it. This only happens with iOS 11 Safari

https://i.stack.imgur.com/34d6d.png

This is a bug with Safari. Apple has fixed it internally but the fix is not in a public (or even beta) release of iOS yet. bugs.webkit.org/show_bug.cgi?id=176896

J
Jen Person

I fixed the issue by adding position:fixed to the body when opening a modal. Hope this will help you.


Note per answers below -- Bootstrap adds .modal-open to body when the modal is open, so you can simply add position: fixed to that class.
You might also need to add width:100% to constrain the body width to the device width. In some cases, depending on existing markup, that could be an issue. I also like the solution by @gentleboy (below) so as not to penalize other browsers without the problem, because when setting the body to fixed causes the body to scroll to the top, which is somewhat annoying.
Strangely, I had this issue with an application build with meteor-cordova. It seemed like any iOS device running v11+ had this issue. In my case I already had position:fixed applied to the body of the application. Instead I changed it to position:absolute on the html element and it fixed my issue. Thank you Jen!
This could be the end of modal forms for us. Even if Apple fixes the issue, it will then be up to the user to update their device to see the working form. Is there not an easier universal way to fix this? What about a poly fill?
you saved my life <3
M
Mike Casan Ballester

Personally, position: fixed scroll to top automatically. Quite annoying !

To avoid penalizing other devices and versions I apply this fix only to the appropriate versions of iOS.

**VERSION 1 - All modals fix**

For the javascript/jQuery

$(document).ready(function() {

    // Detect ios 11_x_x affected  
    // NEED TO BE UPDATED if new versions are affected
    var ua = navigator.userAgent,
    iOS = /iPad|iPhone|iPod/.test(ua),
    iOS11 = /OS 11_0|OS 11_1|OS 11_2/.test(ua);

    // ios 11 bug caret position
    if ( iOS && iOS11 ) {

        // Add CSS class to body
        $("body").addClass("iosBugFixCaret");

    }

});

For the CSS

/* Apply CSS to iOS affected versions only */
body.iosBugFixCaret.modal-open { position: fixed; width: 100%; }

**VERSION 2 - Selected modals only**

I modified the function to fire only for selected modals with a class .inputModal

Only the modals with inputs should be impacted to avoid the scroll to top.

For the javascript/jQuery

$(document).ready(function() {

    // Detect ios 11_x_x affected
    // NEED TO BE UPDATED if new versions are affected 
    (function iOS_CaretBug() {

        var ua = navigator.userAgent,
        scrollTopPosition,
        iOS = /iPad|iPhone|iPod/.test(ua),
        iOS11 = /OS 11_0|OS 11_1|OS 11_2/.test(ua);

        // ios 11 bug caret position
        if ( iOS && iOS11 ) {

            $(document.body).on('show.bs.modal', function(e) {
                if ( $(e.target).hasClass('inputModal') ) {
                    // Get scroll position before moving top
                    scrollTopPosition = $(document).scrollTop();

                    // Add CSS to body "position: fixed"
                    $("body").addClass("iosBugFixCaret");
                }
            });

            $(document.body).on('hide.bs.modal', function(e) {
                if ( $(e.target).hasClass('inputModal') ) {         
                    // Remove CSS to body "position: fixed"
                    $("body").removeClass("iosBugFixCaret");

                    //Go back to initial position in document
                    $(document).scrollTop(scrollTopPosition);
                }
            });

        }
    })();
});

For the CSS

/* Apply CSS to iOS affected versions only */
body.iosBugFixCaret.modal-open { position: fixed; width: 100%; }

For the HTML Add the class inputModal to the modal

<div class="modal fade inputModal" tabindex="-1" role="dialog">
    ...
</div>

Nota bene The javascript function is now self-invoking

**UPDATE iOS 11.3 - Bug corrected 😃🎉 **

As of iOS 11.3, the bug has been corrected. There is no need to test for OS 11_ in iOS11 = /OS 11_0|OS 11_1|OS 11_2/.test(ua);

But be careful as iOS 11.2 is still widely used (as of April 2018). See

stat 1

stat 2


Nice advice. I do not have this issue but I guess this might be global as answer. I’ll add it after I finish my beers
@gentleboy Why not use a REGEX to find any OS 11? So that way you wouldn't have to update each OS 11 update to your code? something like this iOS11 = /OS 11_(\d{1,2})(_{0,1})(\d{1,2})/.test(us);
@RonakK it looks like it is still present in beta 11.2 and 11.3 according to bugs.webkit.org
@T.Evans that regex doesn't work. A correct regex might be something like this: ios11 = /OS 11_(\d{1,2})/.test(ua);
Position fixed will still take document / body scroll to top. I suggest to grab current scrollTop position on modal open and readd scrollTop value once modal closed. Like this jsfiddle.net/im4aLL/hmvget9x/1
E
Eric Shawn

This issue goes beyond Bootstrap, and beyond just Safari. It is a full display bug in iOS 11 that occurs in all browsers. The fix above does not fix this issue in all instances.

The bug is reported in detail here:

https://medium.com/@eirik.luka/how-to-fix-the-ios-11-input-element-in-fixed-modals-bug-aaf66c7ba3f8

Supposedly they already reported it to Apple as a bug.


This seems to be the issue for me, was working fine before updating to IOS 11. Android users aren't having the problem.
S
Scott David Murphy

Frustrating bug, thanks for identifying it. Otherwise, I would be banging my iphone or my head against the wall.

The simplest fix is (1 line of code change):

Just add the following CSS to the html or to an external css file.

<style type="text/css">
.modal-open { position: fixed; }
</style>

Here is a full working example:

.modal-open { position: fixed; } ...more buttons...

I submitted an issue here: https://github.com/twbs/bootstrap/issues/24059


This should be the answer. Bootstrap adds the modal-open class to the body when a modal is visible. You just need to target that class.
I have been dealing with this issue for some time. This solution is inadequate for me as adding position fixed scrolls the page back to the top. So when the user closes the modal, they are at a different scroll position. This leads to a terrible user experience
This fix worked for me in using Chrome on mobile. Was confused at first since .modal-open does not appear in the modal html, but I added it anyway as CSS in my header and it worked.
l
lfkwtz

Easiest/cleanest solution:

body.modal-open { position: fixed; width: 100%; }

This is the easiest solution, and it works, but the only problem is the cursor disappears altogether. It's not as bad as the initial situation, but there's a usability problem.
Weird, I haven't experienced a disappearing cursor
I also experienced the disappearing cursor, any thoughts as to why that could happen?
Yeah I'm also seeing a disappearing cursor for just the first focus event. Subsequent input focuses have the cursor appearing. Very strange. This is only happening in Safari on iOS.
@DevinWalker have you guys ever solved the disappearing cursor issue?
p
piet.t

This issue is no longer reproducible after updating your apple devices to iOS 11.3


What do you mean, Apple fixed it ?
@Starscream yes it's fixed by apple with this software version (iOS 11.3)
A
Anuruk S.

Add position: fixed; to body when modal is open.

$(document).ready(function($){ $("#myBtn").click(function(){ $("#myModal").modal("show"); }); $("#myModal").on('show.bs.modal', function () { $('body').addClass('body-fixed'); }); $("#myModal").on('hide.bs.modal', function () { $('body').removeClass('body-fixed'); }); }); .body-fixed { position: fixed; width: 100%; }


Similar to other solutions on this thread, once the modal closes - you're back at the top again. For example: user scrolls through grid of items, launches details in a modal.. and with this fix, when they close the modal.. they are once again at the top of the grid of items again and must re-scroll
on my iPhone 7 plus, this fix causes my cursor to disappear altogether
F
FlavioEscobar

Those solutions using position: fixed and position correction based on scrollTop work really well, but some people (including me) got another issue: keyboard caret/cursor not showing when inputs are focused.

I observed that caret/cursor works only when we DON'T use position: fixed on body. So after trying several things, I gave up on using this approach and decided to use position: relative on body and use scrollTop to correct modal's top position instead.

See code below:

var iosScrollPosition = 0;

function isIOS() {
   // use any implementation to return true if device is iOS
}

function initModalFixIOS() {
    if (isIOS()) {
        // Bootstrap's fade animation does not work with this approach
        // iOS users won't benefit from animation but everything else should work
        jQuery('#myModal').removeClass('fade');
    }
}

function onShowModalFixIOS() {
    if (isIOS()) {
        iosScrollPosition = jQuery(window).scrollTop();
        jQuery('body').css({
            'position': 'relative', // body is now relative
            'top': 0
        });
        jQuery('#myModal').css({
            'position': 'absolute', // modal is now absolute
            'height': '100%',
            'top': iosScrollPosition // modal position correction
        });
        jQuery('html, body').css('overflow', 'hidden'); // prevent page scroll
    }
}

function onHideModalFixIOS() {
    // Restore everything
    if (isIOS()) {
        jQuery('body').css({
            'position': '',
            'top': ''
        });
        jQuery('html, body').scrollTop(iosScrollPosition);
        jQuery('html, body').css('overflow', '');
    }
}

jQuery(document).ready(function() {
    initModalFixIOS();
    jQuery('#myModal')
        .on('show.bs.modal', onShowModalFixIOS)
        .on('hide.bs.modal', onHideModalFixIOS);
});

A
Arman Charan

As previously mentioned: setting the style.position property of body to fixed solves the iOS cursor misplacement issue.

However, this gain comes at the cost of being forcibly scrolled to the top of the page.

Fortunately, this new UX problem can be negated without much overhead by leveraging HTMLElement.style and window.scrollTo().

The basic gist is to counteract the scroll to top by manipulating the body element's style.top when mounting. This is done using the YOffset value captured by the ygap variable.

From there it's simply a matter of resetting the body's style.top to 0 and reframing the user's view using window.scrollTo(0, ygap) when dismounting.

See below for a practical example.

// Global Variables (Manage Globally In Scope).
const body = document.querySelector('body') // Body.
let ygap = 0 // Y Offset.


// On Mount (Call When Mounting).
const onModalMount = () => {

  // Y Gap.
  ygap = window.pageYOffset || document.documentElement.scrollTop

  // Fix Body.
  body.style.position = 'fixed'

  // Apply Y Offset To Body Top.
  body.style.top = `${-ygap}px`

}


// On Dismount (Call When Dismounting).
const onModalDismount = () => {

  // Unfix Body.
  body.style.position = 'relative'

  // Reset Top Offset.
  body.style.top = '0'

  // Reset Scroll.
  window.scrollTo(0, ygap)

}

Do you have a more detailed example for this? I'm guessing I would have the Mount in a function that is called when the modal opens, and then put the Dismount in a function and call that when the modal is closed.
Aahh yep. I see what you mean. Please see above for a revised solution. Also; yes the intent is for you to call those functions when mounting and dismounting. Thanks.
M
Manuel Otto

Incase anyone is looking for a fix in vanilla js that works on IOS >11.2 and doesnt require any additional CSS:

(function() {
    if (!/(iPhone|iPad|iPod).*(OS 11_[0-2]_[0-5])/.test(navigator.userAgent)) return

    document.addEventListener('focusin', function(e) {
        if (!e.target.tagName == 'INPUT' && !e.target.tagName != 'TEXTAREA') return
        var container = getFixedContainer(e.target)
        if (!container) return
        var org_styles = {};
        ['position', 'top', 'height'].forEach(function(key) {
            org_styles[key] = container.style[key]
        })
        toAbsolute(container)
        e.target.addEventListener('blur', function(v) {
            restoreStyles(container, org_styles)
            v.target.removeEventListener(v.type, arguments.callee)
        })
    })

    function toAbsolute(modal) {
        var rect = modal.getBoundingClientRect()
        modal.style.position = 'absolute'
        modal.style.top = (document.body.scrollTop + rect.y) + 'px'
        modal.style.height = (rect.height) + 'px'
    }

    function restoreStyles(modal, styles) {
        for (var key in styles) {
            modal.style[key] = styles[key]
        }
    }

    function getFixedContainer(elem) {
        for (; elem && elem !== document; elem = elem.parentNode) {
            if (window.getComputedStyle(elem).getPropertyValue('position') === 'fixed') return elem
        }
        return null
    }
})()

What this does is:

Check if the browser is Safari on iOS 11.0.0 - 11.2.5 Listen for any focusin events on the page If the focused element is an input or a textarea and is contained in an element with fixed position, change the container position to absolute while regarding scrollTop and the containers original dimensions. On blur, restore the container's position to fixed.


A
Afzaal Khalid

This solution worked for me and its working well across all browsers on iOS.

.safari-nav-force{
/* Allows content to fill the viewport and go beyond the bottom */
height: 100%;
overflow-y: scroll;
/* To smooth any scrolling behavior */
-webkit-overflow-scrolling: touch;
}

JavsScript

var iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
$('.modal').on('shown.bs.modal', function () {
    if (iOS && $('.modal').hasClass('in')){
        $('html,body').addClass('safari-nav-force');
    }
});
$('.modal').on('hidden.bs.modal', function () {
    if (iOS && !$('.modal').hasClass('in')){
        $('html,body').removeClass('safari-nav-force');
    }
});

O
Oncleroger

Have you try viewport-fit=cover for the meta viewport.

Look at this : https://ayogo.com/blog/ios11-viewport/


This has no effect on the bug in question, I'm afraid.
D
Dan

Override modal css and change its position from fixed to absolute

.modal {
position: absolute !important;
}

Didn't work in my scenario. Changing fixed to absolute can have MANY knock on effects
@SteveD - to make it work you should append your modal to . And the should have - position: relative. And when it should work :)
K
Karla

add to the #modal position:absolute it fix future issues related to the position: fixed


Your answer is an exact duplicate. Please make sure not to post duplicate answers.