42

How to prevent background scrolling when Bootstrap 3 modal open on mobile platforms? On desktop browsers the background is prevented from scrolling and works as it should.

On mobile browsers (Safari ios, Chrome ios, etc), when a modal is opened and using your finger to scroll it, the background also scrolls as does the modal. How do I prevent that?

Anshad Vattapoyil
  • 23,145
  • 18
  • 84
  • 132
fat fantasma
  • 7,483
  • 15
  • 48
  • 66

19 Answers19

37

See here: https://github.com/twbs/bootstrap/issues/7501

So try:

$('body').css('overflow','hidden');
$('body').css('position','fixed');

V3.0.0. should have fixed this issue. Do you use the latest version? If so post an issue on https://github.com/twbs/bootstrap/

Bass Jobsen
  • 48,736
  • 16
  • 143
  • 224
  • 1
    thanks, that was it. I had some left over code from BS 2 messing me up. – fat fantasma Sep 28 '13 at 20:13
  • 6
    I actually posted the issue here - https://github.com/twbs/bootstrap/issues/15852 - ```body.modal-open { position: fixed; }``` has a side effect of scrolling to the top... *(still not sure what is the best way)* – Mars Robertson Mar 26 '15 at 13:36
  • 1
    Michael Stefanow did you figure out the scrolling to top issue by any chance? I am having the same issue with this fix. – Julian Dormon Mar 27 '15 at 14:31
  • 1
    one thing to maintain the scroll position I did the following when you load the overlay save your scroll position [var scrollTop = document.getElementById("body").scrollTop] when you close the overlay restore your position with [document.getElementById("body").scrollTop = scrollTop;] – Ade Nov 05 '15 at 05:33
  • 9
    V3 Did not fix this issue – Perry Nov 08 '15 at 18:33
  • 1
    V4 Did not fix this issue. [http://v4-alpha.getbootstrap.com/getting-started/browsers-devices/#modals-and-dropdowns-on-mobile](http://v4-alpha.getbootstrap.com/getting-started/browsers-devices/#modals-and-dropdowns-on-mobile) – m.spyratos Sep 15 '16 at 09:33
  • I posted below a solution that fixed the scroll to top issue - similar to what @Ade says above, but a little more detailed. – Mark Pruce Dec 08 '16 at 22:04
24

Try this,

 body.modal-open {
    overflow: hidden;
    position:fixed;
    width: 100%;
}
Sivakumar
  • 1,089
  • 1
  • 13
  • 24
20

I tried the accepted answer which prevented the body from scrolling but had the issue of scrolling to the top. This should solve both issues.

As a side note, it appears overflow:hidden doesn't work on body for iOS Safari only as iOS Chrome works fine.

var scrollPos = 0;

$('.modal')
    .on('show.bs.modal', function (){
        scrollPos = $('body').scrollTop();
        $('body').css({
            overflow: 'hidden',
            position: 'fixed',
            top : -scrollPos
        });
    })
    .on('hide.bs.modal', function (){
        $('body').css({
            overflow: '',
            position: '',
            top: ''
        }).scrollTop(scrollPos);
    });
Joe
  • 362
  • 1
  • 4
  • 9
  • To fix the right margin, add padding with width equal to the width of the scrollbar: https://stackoverflow.com/a/19015262/4160062 – Hubert Jul 10 '17 at 09:43
  • Does not work - chrome Version 63.0.3239.132 Still scrolls background. –  Jan 06 '18 at 21:26
10
$('.modal') 
.on('shown', function(){ 
  console.log('show'); 
  $('body').css({overflow: 'hidden'}); 
}) 
.on('hidden', function(){ 
  $('body').css({overflow: ''}); 
}); 

use this one

Karthick Kumar
  • 2,349
  • 1
  • 17
  • 30
5

No scripts needed.

BS 3 sets a .modal-open class on body that you can use to set the position and overflow values (made with LESS).

body {
    font-family:'Open Sans';
    margin:0;

    &.modal-open {
        position:fixed;
        overflow:hidden;

        .modal { 
            overflow: scroll;

            @media only screen and (min-resolution:150dpi) and (max-width: @screen-sm),
            only screen and (-webkit-min-device-pixel-ratio:1.5) {
                overflow: scroll; 
                -webkit-overflow-scrolling: touch; 
            }
        }
    }   
}
Stalinko
  • 3,319
  • 28
  • 31
Martin
  • 51
  • 1
  • 1
4

The chosen solution works, however they also snap the background to the top scrolling position. I extended the code above to fix that 'jump'.

//Set 2 global variables
var scrollTopPosition = 0;
var lastKnownScrollTopPosition = 0;

//when the document loads
$(document).ready(function(){

  //this only runs on the right platform -- this step is not necessary, it should work on all platforms
  if( navigator.userAgent.match(/iPhone|iPad|iPod/i) ) {

    //There is some css below that applies here
    $('body').addClass('platform-ios');

    //As you scroll, record the scrolltop position in global variable
    $(window).scroll(function () {
      scrollTopPosition = $(document).scrollTop();
    });

    //when the modal displays, set the top of the (now fixed position) body to force it to the stay in the same place
    $('.modal').on('show.bs.modal', function () {

      //scroll position is position, but top is negative
      $('body').css('top', (scrollTopPosition * -1));

      //save this number for later
      lastKnownScrollTopPosition = scrollTopPosition;
    });

    //on modal hide
    $('.modal').on('hidden.bs.modal', function () {

      //force scroll the body back down to the right spot (you cannot just use scrollTopPosition, because it gets set to zero when the position of the body is changed by bootstrap
      $('body').scrollTop(lastKnownScrollTopPosition);
    });
  }
});

The css is pretty simple:

// You probably already have this, but just in case you don't
body.modal-open {
  overflow: hidden;
  width: 100%;
  height: 100%;
}
//only on this platform does it need to be fixed as well
body.platform-ios.modal-open {
  position: fixed;
}
Mark Pruce
  • 945
  • 10
  • 25
  • Thank you. Worked great for me, tested on desktop and ios. My last issue is trying to prevent scrolling to top on iOS after I an ajax action. Doesn't scroll to top on desktop. – iamse7en Mar 14 '17 at 16:10
  • nevermind, adding `$('body').scrollTop(lastKnownScrollTopPosition);` to my x.js.erb file as well (the file that executes js code upon submit) worked great. thanks! – iamse7en Mar 14 '17 at 16:19
3

If you use jQuery you can do this with scrollTop

  1. Save the body's vertical scroll position;
  2. Disable scroll on the body;
  3. Show modal;
  4. Close modal;
  5. Reenable scroll on body;
  6. Set saved scroll position.

#modal {
    bottom: 0;
    position: fixed;
    overflow-y: scroll;
    overflow-x: hidden;
    top: 0;
    width: 100%;
}

$('.open-modal').click(function (e) {
    e.preventDefault();
    $('#modal').toggle();
    scrollTo = $('body').scrollTop();
    $('body').css("position", "fixed");
});

$('.close-modal').click(function (e) {
    e.preventDefault();
    $('#modal').toggle();
    $('body').css("position", "static");
    $('body').animate({scrollTop: scrollTo}, 0);
});
Tonijn
  • 41
  • 4
2

The above answers did not help so what I did was:

.modal {
  -webkit-overflow-scrolling: touch; 
}

My particular problem was when I increased the modal size after loading.

It's a known iOS issue, see here. Since it does not break anything else the above solution was enough for my needs.

Mugur 'Bud' Chirica
  • 4,246
  • 1
  • 31
  • 34
1

Had an issue with this as well, iPhone + Safari where needed to add:

position: fixed;

As mentioned elsewhere, this created a scroll-to-top issue. Fix that worked for me was to capture the position to top upon modal open, and then animate to that position on modal close

upon modal open:

scrollTo = $('body').scrollTop();
$('body').css("position", "fixed");

upon modal close

$('body').css("position", "static");
$('body').animate({scrollTop: scrollTo}, 0);
Ryan Charmley
  • 1,127
  • 15
  • 18
  • Maybe this would be a better choice hence the page doesn't scrolls back up: `$('body').css({"position": "fixed", 'top': (-scrollTo) + 'px'});` – Tim Rijavec Jun 21 '15 at 16:27
  • Fix does not seem to work. Jump to top happens when the position is set back to static given the need for the 0 second animate. – Ryan Charmley Jun 24 '15 at 02:59
  • So, given a global variable `var scrollTo;` you can have two functions `function beforeModalOpen() { scrollTo = $('body').scrollTop(); $('body').css({"position": "fixed", 'top': (-scrollTo) + 'px'}); }` and `function afterModalClose() {$('body').css("position", "static"); $('body').animate({scrollTop: scrollTo}, 0);}` and call them appropriately. – Tim Rijavec Jun 24 '15 at 08:14
1

Hey guys so i think i found a fix. This is working for me on iphone and android at the moment. Its a mash up of hours upon hours of searching, reading and testing. So if you see parts of your code in here credit goes to you lol.

@media only screen and (max-device-width:768px){

body.modal-open {
// block scroll for mobile;
// causes underlying page to jump to top;
// prevents scrolling on all screens
overflow: hidden;
position: fixed;
}
}

body.viewport-lg {
// block scroll for desktop;
// will not jump to top;
// will not prevent scroll on mobile
position: absolute; 
}

body {  
overflow-x: hidden;
overflow-y: scroll !important;
}

The reason the media specific is on there is on a desktop i was having issues with when the modal would open all content on the page would shift from centered to left. Looked like crap. So this targets up to tablet size devices where you would need to scroll. There is still a slight shift on mobile and tablet but its really not much. Let me know if this works for you guys. Hopefully this puts the nail in the coffin

JoeyD
  • 693
  • 4
  • 25
  • no negative effects on desktop, does improve modal experience on my iPhone.. but, upon closing my modal, the original screen has scrolled-to-top – bkwdesign Sep 13 '17 at 20:54
  • I'm using your solution, minus your line of code `position: fixed` – bkwdesign Sep 13 '17 at 21:02
1

I thought you might forget to add attribute data-toggle="modal" to the link or button that triggers the modal popup event. Firstly, I also got the same problem but, after adding the attribute above it works well for me.

Tunaki
  • 132,869
  • 46
  • 340
  • 423
Micheal
  • 11
  • 1
1

This might be a bit like beating a dead horse here.. but, my currently implemented solution on DIY modals via vanilla JS:

On modal show:

if (document.body.style.position !== 'fixed') {
    document.body.style.top = -window.scrollY + 'px';
    document.body.style.position = 'fixed';
}

On modal hide:

document.body.style.position = '';
window.scrollTo(0, -parseInt(document.body.style.top, 10));
document.body.style.top = '';
Lucas
  • 875
  • 7
  • 15
0

As additional to @Karthick Kumar answer from bootstrap docs

show is triggered at the start of an event

shown is triggered on the completion of an action

... so it should be:

$('.modal')
    .on('show.bs.modal', function (){
            $('body').css('overflow', 'hidden');
        })
    .on('hide.bs.modal', function (){
            // Also if you are using multiple modals (cascade) - additional check
            if ($('.modal.in').length == 1) {
                $('body').css('overflow', 'auto');
            }
        });
Community
  • 1
  • 1
madzohan
  • 11,488
  • 9
  • 40
  • 67
0

I've found a simple javascript/jquery solution which utilizes the bootstrap modal events.

My solution also fixes the position:fixed problem where it scrolls the background page all the way back to the top instead of staying in place when modal window is opened/closed.

See details here

Community
  • 1
  • 1
TrieuNomad
  • 1,651
  • 1
  • 21
  • 24
0

I know this has been answered but none of these solutions worked for me. I needed to take a different approach. I am using PhoneGap and am compiling my code natively so I had to move the background to the body. I hope this helps someone else. Or if there a cleaner way to do this please feel free to comment...

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

    $("#" + e.target.id).find(".modal-backdrop").css("z-index", $("#" + e.target.id).css("z-index")).insertBefore("#" + e.target.id);

});
Paul
  • 1,527
  • 2
  • 16
  • 24
0

Using position:fixed has the side effect of scrolling the body to the top.

If you do not your body to scroll to the top, DO NOTE use position:fixed. Just disable touchmove on the body if the modal is open. Note: The modal itself is still able to scroll on touch (if larger than the screen).

CSS:

body.modal-open {
    overflow: hidden;
    width: 100%;
    /* NO position:fixed here*/
}

JS:

$('.modal').on('show.bs.modal', function (ev) { // prevent body from scrolling when modal opens
    $('body').bind('touchmove', function(e){
        if (!$(e.target).parents().hasClass( '.modal' )){ //only prevent touch move if it is not the modal
            e.preventDefault()
        }
    })
})
$('.modal').on('hide.bs.modal', function (e) { //unbind the touchmove restrictions from body when modal closes
    $('body').unbind('touchmove');
})

EDIT: Note, for very small modals, you might have to add the following line to your CSS:

.modal-dialog{
    height: 100%;
}
Kito
  • 1,375
  • 4
  • 17
  • 37
0

Courtesy of JDiApice who synthesized and extended the work of other contributors at iOS 8.x modal scroll issue #14839:

@media only screen and (max-device-width:768px) {

body.modal-open {
    // block scroll for mobile;
    // causes underlying page to jump to top;
    // prevents scrolling on all screens
    overflow: hidden;
    position: fixed;
    }
}

body.viewport-lg {
    // block scroll for desktop;
    // will not jump to top;
    // will not prevent scroll on mobile
    position: absolute;
}

body {
    overflow-x: hidden;
    overflow-y: scroll !important;
}

/* The reason the media specific is on there is 
   on a desktop i was having issues with when the modal would open 
   all content on the page would shift from centered to left. 
   Looked like crap. So this targets up to tablet size devices 
   where you would need to scroll. There is still a slight shift 
   on mobile and tablet but its really not much. */

Unlike the other solutions we tried, it does not scroll the background to the top after the popup modal closes.

CAK2
  • 1,892
  • 1
  • 15
  • 17
0

I open a modal after a modal and fact the error on modal scrolling, and this css solved my problem:

.modal {
    overflow-y: auto;
    padding-right: 15px;
}
sokphea chea
  • 317
  • 3
  • 8
-2

My solution...

Ver en jsfiddle

//Fix modal mobile Boostrap 3
function Show(id){
    //Fix CSS
    $(".modal-footer").css({"padding":"19px 20px 20px","margin-top":"15px","text-align":"right","border-top":"1px solid #e5e5e5"});
    $(".modal-body").css("overflow-y","auto");
    //Fix .modal-body height
    $('#'+id).on('shown.bs.modal',function(){
        $("#"+id+">.modal-dialog>.modal-content>.modal-body").css("height","auto");
        h1=$("#"+id+">.modal-dialog").height();
        h2=$(window).height();
        h3=$("#"+id+">.modal-dialog>.modal-content>.modal-body").height();
        h4=h2-(h1-h3);      
        if($(window).width()>=768){
            if(h1>h2){
                $("#"+id+">.modal-dialog>.modal-content>.modal-body").height(h4);
            }
            $("#"+id+">.modal-dialog").css("margin","30px auto");
            $("#"+id+">.modal-dialog>.modal-content").css("border","1px solid rgba(0,0,0,0.2)");
            $("#"+id+">.modal-dialog>.modal-content").css("border-radius",6);               
            if($("#"+id+">.modal-dialog").height()+30>h2){
                $("#"+id+">.modal-dialog").css("margin-top","0px");
                $("#"+id+">.modal-dialog").css("margin-bottom","0px");
            }
        }
        else{
            //Fix full-screen in mobiles
            $("#"+id+">.modal-dialog>.modal-content>.modal-body").height(h4);
            $("#"+id+">.modal-dialog").css("margin",0);
            $("#"+id+">.modal-dialog>.modal-content").css("border",0);
            $("#"+id+">.modal-dialog>.modal-content").css("border-radius",0);   
        }
        //Aply changes on screen resize (example: mobile orientation)
        window.onresize=function(){
            $("#"+id+">.modal-dialog>.modal-content>.modal-body").css("height","auto");
            h1=$("#"+id+">.modal-dialog").height();
            h2=$(window).height();
            h3=$("#"+id+">.modal-dialog>.modal-content>.modal-body").height();
            h4=h2-(h1-h3);
            if($(window).width()>=768){
                if(h1>h2){
                    $("#"+id+">.modal-dialog>.modal-content>.modal-body").height(h4);
                }
                $("#"+id+">.modal-dialog").css("margin","30px auto");
                $("#"+id+">.modal-dialog>.modal-content").css("border","1px solid rgba(0,0,0,0.2)");
                $("#"+id+">.modal-dialog>.modal-content").css("border-radius",6);               
                if($("#"+id+">.modal-dialog").height()+30>h2){
                    $("#"+id+">.modal-dialog").css("margin-top","0px");
                    $("#"+id+">.modal-dialog").css("margin-bottom","0px");
                }
            }
            else{
                //Fix full-screen in mobiles
                $("#"+id+">.modal-dialog>.modal-content>.modal-body").height(h4);
                $("#"+id+">.modal-dialog").css("margin",0);
                $("#"+id+">.modal-dialog>.modal-content").css("border",0);
                $("#"+id+">.modal-dialog>.modal-content").css("border-radius",0);   
            }
        };
    });  
    //Free event listener
    $('#'+id).on('hide.bs.modal',function(){
        window.onresize=function(){};
    });  
    //Mobile haven't scrollbar, so this is touch event scrollbar implementation
    var y1=0;
    var y2=0;
    var div=$("#"+id+">.modal-dialog>.modal-content>.modal-body")[0];
    div.addEventListener("touchstart",function(event){
        y1=event.touches[0].clientY;
    });
    div.addEventListener("touchmove",function(event){
        event.preventDefault();
        y2=event.touches[0].clientY;
        var limite=div.scrollHeight-div.clientHeight;
        var diff=div.scrollTop+y1-y2;
        if(diff<0)diff=0;
        if(diff>limite)diff=limite;
        div.scrollTop=diff;
        y1=y2;
    });
    //Fix position modal, scroll to top.    
    $('html, body').scrollTop(0);
    //Show
    $("#"+id).modal('show');
}
infinito84
  • 1,971
  • 17
  • 8