Twitter Bootstrap modal on mobile devices

AndroidIosMobileWebkitTwitter Bootstrap

Android Problem Overview


Bootstrap modals don't work correctly on Android and iOS. The issue tracker acknowledges the problem but does not offer a working solution:

Modals in 2.0 are broken on mobile.

Modal window in 2.0 not positioning properly

The screen darkens but the modal itself is not visible in the viewport. It's possible to find it at the top of the page. The problem occurs when you've scrolled down on the page.

Here is a relevant portion of bootstrap-responsive.css:

.modal {
    position:fixed;
    top:50%;
    left:50%;
    z-index:1050;
    max-height:500px;
    overflow:auto;
    width:560px;
    background-color:#fff;
    border:1px solid #999;
    -webkit-border-radius:6px;
    -moz-border-radius:6px;
    border-radius:6px;
    -webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);
    -moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);
    box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);
    -webkit-background-clip:padding-box;
    -moz-background-clip:padding-box;
    background-clip:padding-box;
    margin:-250px 0 0 -280px;
}

Is there a fix I can apply?

Android Solutions


Solution 1 - Android

EDIT: An unofficial Bootstrap Modal modification has been built to address responsive/mobile issues. This is perhaps the simplest and easiest way to remedy the problem.

There has since been a fix found in one of the issues you discussed earlier

in bootstrap-responsive.css

.modal { 
    position: fixed; 
    top: 3%; 
    right: 3%; 
    left: 3%; 
    width: auto; 
    margin: 0; 
}
.modal-body { 
    height: 60%; 
}

and in bootstrap.css

.modal-body { 
    max-height: 350px; 
    padding: 15px; 
    overflow-y: auto; 
    -webkit-overflow-scrolling: touch; 
 }

Solution 2 - Android

Gil's answer holds promise (the library he linked to) --- but for the time being, it still doesn't work when scrolled down on the mobile device.

I solved the issue for myself using just a snippet of CSS at the end of my CSS files:

@media (max-width: 767px) {
  #content .modal.fade.in {
    top: 5%;
  }
}

The #content selector is simply an id that wraps my html so I can override Bootstrap's specificity (set to your own id wrapping your modal html).

The downside: It's not centered vertically on the mobile devices.

The upside: It's visible, and on smaller devices, a reasonably sized modal will take up much of the screen, so the "non-centering" won't be as apparent.

Why it works:

When you're at low screen sizes with Bootstrap's responsive CSS, for devices with smaller screens, it sets .modal.fade.in's 'top' to 'auto'. For some reason the mobile webkit browsers seem to have a hard time with figuring out the vertical placement with the "auto" assignment. So just switch it back to a fixed value and it works great.

Since the modal is already set to postition: absolute, the value is relative to the viewport's height, not the document height, so it works no matter how long the page is or where you're scrolled to.

Solution 3 - Android

The solution by niftylettuce in issue 2130 seems to fix modals in all mobile platforms...

9/1/12 UPDATE: The fix has been updated here: twitter bootstrap jquery plugins

(the code below is older but still works)

// # Twitter Bootstrap modal responsive fix by @niftylettuce
//  * resolves #407, #1017, #1339, #2130, #3361, #3362, #4283
//   <https://github.com/twitter/bootstrap/issues/2130>
//  * built-in support for fullscreen Bootstrap Image Gallery
//    <https://github.com/blueimp/Bootstrap-Image-Gallery>

// **NOTE:** If you are using .modal-fullscreen, you will need
//  to add the following CSS to `bootstrap-image-gallery.css`:
//
//  @media (max-width: 480px) {
//    .modal-fullscreen {
//      left: 0 !important;
//      right: 0 !important;
//      margin-top: 0 !important;
//      margin-left: 0 !important;
//    }
//  }
//

var adjustModal = function($modal) {
  var top;
  if ($(window).width() <= 480) {
    if ($modal.hasClass('modal-fullscreen')) {
      if ($modal.height() >= $(window).height()) {
        top = $(window).scrollTop();
      } else {
        top = $(window).scrollTop() + ($(window).height() - $modal.height()) / 2;
      }
    } else if ($modal.height() >= $(window).height() - 10) {
      top = $(window).scrollTop() + 10;
    } else {
      top = $(window).scrollTop() + ($(window).height() - $modal.height()) / 2;
    }
  } else {
    top = '50%';
    if ($modal.hasClass('modal-fullscreen')) {
      $modal.stop().animate({
          marginTop  : -($modal.outerHeight() / 2)
        , marginLeft : -($modal.outerWidth() / 2)
        , top        : top
      }, "fast");
      return;
    }
  }
  $modal.stop().animate({ 'top': top }, "fast");
};

var show = function() {
  var $modal = $(this);
  adjustModal($modal);
};

var checkShow = function() {
  $('.modal').each(function() {
    var $modal = $(this);
    if ($modal.css('display') !== 'block') return;
    adjustModal($modal);
  });
};

var modalWindowResize = function() {
  $('.modal').not('.modal-gallery').on('show', show);
  $('.modal-gallery').on('displayed', show);
  checkShow();
};

$(modalWindowResize);
$(window).resize(modalWindowResize);
$(window).scroll(checkShow);

Solution 4 - Android

We use this code to center the Bootstrap modal dialogs when they open. I haven't had any issue with them on iOS while using this but I'm not sure if it will work for Android.

$('.modal').on('show', function(e) {
    var modal = $(this);
    modal.css('margin-top', (modal.outerHeight() / 2) * -1)
         .css('margin-left', (modal.outerWidth() / 2) * -1);
    return this;
});

Solution 5 - Android

Admittedly, I haven't tried any of the solutions listed above but I was (eventually) jumping for joy when I tried jschr's Bootstrap-modal project in Bootstrap 3 (linked to in the top answer). The js was giving me trouble so I abandoned it (maybe mine was a unique issue or it works fine for Bootstrap 2) but the CSS files on their own seem to do the trick in Android's native 2.3.4 browser.

In my case, I've resorted so far to using (sub-optimal) user-agent detection before using the overrides to allow expected behaviour in modern phones.

For example, if you wanted all Android phones ver 3.x and below only to use the full set of hacks you could add a class "oldPhoneModalNeeded" to the body after user agent detection using javascript and then modify jschr's Bootstrap-modal CSS properties to always have .oldPhoneModalNeeded as an ancestor.

Solution 6 - Android

you can add this property globally in javascript:

if( navigator.userAgent.match(/iPhone|iPad|iPod/i) ) {
    var styleEl = document.createElement('style'), styleSheet;
    document.head.appendChild(styleEl);
    styleSheet = styleEl.sheet;
    styleSheet.insertRule(".modal { position:absolute; bottom:auto; }", 0);
 }

Solution 7 - Android

OK this does fix it I tried it today Sept 5-2012 but you have to be sure to check out the demo

> The solution by niftylettuce in issue 2130 seems to fix modals in all > mobile platforms... > > 9/1/12 UPDATE: The fix has been updated here: > twitter bootstrap jquery plugins

here is the link to the Demo It works great heres the code I used

            title_dialog.modal();
            title_dialog.modalResponsiveFix({})
            title_dialog.touchScroll();

Solution 8 - Android

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');
}

Solution 9 - Android

Found a very hacky solution to this problem, but it works. I added a class to the link that is used to open the modal (With the data-target), then using Jquery, added a click event to that class that gets the data-target, finds the modal it is supposed to open, and then opens it via Javascript. Works just fine for me. I also added a mobile check on mine so that it only runs on mobile, but that's not required.

$('.forceOpen').click(function() {
  var id = $(this).attr('data-target');
  $('.modal').modal('hide');
  $(id).modal('show');
});

Solution 10 - Android

Mainly Nexus 7 modal issue , modal was going down the screen

  .modal:before {
    content: '';
    display: inline-block;
    height: 50%; (the value was 100% for center the modal)
    vertical-align: middle;
    margin-right: -4px;
  }

Solution 11 - Android

For me just $('[data-toggle="modal"]').click(function(){}); is working fine.

Solution 12 - Android

Albeit this question is quite old, some people still may stumble upon it when looking for solution to improve UX of modals on mobile phones.

I've made a lib to improve Bootrsrap modals' behavior on phones.

Bootstrap 3: https://github.com/keaukraine/bootstrap-fs-modal

Bootstrap 4: https://github.com/keaukraine/bootstrap4-fs-modal

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionty.View Question on Stackoverflow
Solution 1 - AndroidacconradView Answer on Stackoverflow
Solution 2 - AndroidjlovisonView Answer on Stackoverflow
Solution 3 - AndroidGil BirmanView Answer on Stackoverflow
Solution 4 - AndroidJeradanView Answer on Stackoverflow
Solution 5 - AndroidVishnar TadelerathaView Answer on Stackoverflow
Solution 6 - AndroidHetdevView Answer on Stackoverflow
Solution 7 - AndroidedgarView Answer on Stackoverflow
Solution 8 - Androidinfinito84View Answer on Stackoverflow
Solution 9 - AndroidRockster160View Answer on Stackoverflow
Solution 10 - Androidgem007bdView Answer on Stackoverflow
Solution 11 - AndroidOtabek SuvankulovView Answer on Stackoverflow
Solution 12 - AndroidkeaukraineView Answer on Stackoverflow