Twitter Bootstrap modal opening/closing causes fixed header to jump - css

I am almost done with a simple 2-page website for my registered domain names.
Unfortunately I have one small issue I can't seem to fix: a jumpy header when a Twitter Bootstrap modal opens and closes.
On mobile devices there's no problem. The problem only occurs in larger viewports like desktops and laptops.
How to recreate
Open http://www.domains.cloudlabz.nl/domains in a webbrowser and make sure you get a vertical scrollbar by lowering the viewport height.
Click on one of the blue 'more info' buttons.
Notice the jumping header and disappearing scrollbar once the modal opens.
Close the modal and notice the header jumping back and the scrollbar reappearing.
Check the following image (same result in Opera, Safari, Firefox and Chrome):
What I'd like
I'd like the header to stop jumping when opening/closing a modal. The fact the scrollbar disappears is not an issue. Actually, I would like it to stay like that.
Update
I noticed the jumping header only occurs with fixed position elements such as my header (added Bootstrap class navbar-fixed-top). It even occurs on the Bootstrap website itself: http://getbootstrap.com/javascripts. Go to the 'Modals > Optional Sizes' area on a desktop and click one of the buttons. You'll see the right side menu jumping back and forth.
When the modal opens, the class .modal-open is added to the body element (thanks for pointing that out #Pred). It adds a padding of 15px to the right, which is the same width as the scrollbar gutter. This prevents the body from jumping back and forth.
Unfortunately this padding apparently does not apply to fixed elements.

Bootstrap adds class="modal-open" and padding-right: 15px; to body when the modal is shown. To remove the right shift and keep the scroll bar add this to your css:
body.modal-open {
overflow: inherit;
padding-right: 0 !important;
}
Tried in bootstrap 3.3.4

I seemed to have found a quick fix for my issue. It uses a piece of javascript to add extra style to the header (15px padding-right) to prevent it from jumping.
This might not be the best solution but for now it works just fine.
Since there were no issues on viewports smaller than 768px (mobile) this piece of code only adds the extra 15px to larger viewports such as desktops and laptops
<script>
$(document).ready(function(){
// Dirty fix for jumping scrollbar when modal opens
$('#requestModal').on('show.bs.modal', function (e) {
if ($(window).width() > 768) {
$(".navbar-default").css("padding-right","15px");
}
});
$('#requestModal').on('hide.bs.modal', function (e) {
if ($(window).width() > 768) {
$(".navbar-default").css("padding-right","0px");
}
});
});
</script>
If you know a better solution (preferably CSS3 only), please let me know.
Thanks for all the help!

As you usually put Bootstrap navbar as a direct child of the body container:
<body>
<nav class="navbar navbar-fixed-top">...</nav>
</body>
You can use the body padding-right value, calculated in the Bootstrap core code to prevent it from "jumping" when opening a modal window, to fix the navbar issue as well . A pure CSS solution is below:
.navbar-fixed-top {
padding-right: inherit;
}
Easy as that.

When the modal opens, the "modal-open" class is added to the HTML <body> element which hides overflow. You can change this by over-writing the "modal-open" class with overflow: inherit. This will keep the scrollbar in view, just as it is when the modal is closed. Keep in mind that this will change the overflow option whenever any modal is opened on the page. Hope this helps. Good luck!

All this happens because of this part of code in bootstrap.js:
Modal.prototype.setScrollbar = function () {
var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)
if (this.scrollbarWidth) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)
}
That annoying problem happens in Firefox 32.0 (Gecko/20100101) and Chromium Version 37.0.2062.94 (Webkit 537.36) (Ubuntu 14.04). Not happens in QupZilla Version 1.6.6 (WebKit 537.21).
For me, the dirty fix is to comment the conditional line, after that it works in all browsers I tested (including some android's browsers).
NOTE: if you comment that line, you should be careful with the size of your modals since bootstrap will not create enough space for the new scrollbar.
Regards.

Another solution is to add:
.modal-open .navbar-fixed-top {
overflow-y: scroll;
}
to prevent its content from jumping. Again, easy as that.

This one only works if you know your page content is longer than the viewport (so any long scrolling page). You can just add the following to your CSS -
html {
overflow-y: scroll;
}
.modal-open {
padding-right: 0!important;
}

I came around same issue and I solved it as follows
just add
body.modal-open {
position: fixed;
overflow: scroll;
width: 100%;
padding-right: 0!important;
}

I manually change bootstap.js:
before change
Modal.prototype.setScrollbar = function () {
var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)
if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)
}
Modal.prototype.resetScrollbar = function () {
this.$body.css('padding-right', '')
}
after change:
Modal.prototype.setScrollbar = function () {
var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)
var headerPad = parseInt(($('.navbar-fixed-top').css('padding-right') || 0), 10)
if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)
if (this.bodyIsOverflowing) $('.navbar-fixed-top').css('padding-right', headerPad + this.scrollbarWidth)
}
Modal.prototype.resetScrollbar = function () {
this.$body.css('padding-right', '')
$('.navbar-fixed-top').css('padding-right', '')
}

I have a structure:
<html>
<body>
<nav class="navbar navbar-fixed-top">...</nav>
<section>
<div class="container">...</div>
</section>
<section>
<div class="container">...</div>
</section>
<footer>...</foter>
</body>
</html>
I'm using this CSS:
body.modal-open {padding-right: 0 !important}
body.modal-open nav,
body.modal-open section,
body.modal-open footer {padding-right: 17px !important}

Add the following to your CSS:
body {
overflow: inherit;
padding-right: 0 !important;
}

For Bootstrap 4 sticky-top, use the following snippet. Tested all the CSS solutions on this page but none worked for Bootstrap 4.
<script type="text/javascript">
$('body').on('show.bs.modal', function () {
$('.sticky-top').css('margin-left', '-=0px');
});
$('body').on('hidden.bs.modal', function () {
$('.sticky-top').css('margin-left', 'auto');
});
</script>
My page design was as follows
<header class="container">
<div class="row">
</div>
</header>
<div class="container sticky-top">
<nav class="navbar navbar-expand-lg">
This div jumped/shifted 17px to the right when opening a modal
</nav>
</div>

In my case, it can be solved by adding comments or remove these two lines right: 0; and left: 0; in bootstrap.css file:
.navbar-fixed-top,
.navbar-fixed-bottom {
position: fixed;
/* right: 0;
left: 0; */
z-index: 1030;
}
Note: I use bootstrap v3.3.7

In 4.5, the following code solved my issue of the fixed header sliding left.
body.modal-open {
position: fixed;
overflow-y: scroll;
width: 100%;
padding-right: 0!important;
}
.modal-open .fixed-top {
padding-right: inherit!important;
}

Related

CSS Grid with meta viewport causing html and body to be under sized

I am trying to build a really simple CSS Grid Layout: header/content/footer. I had it working, then I tried opening it up in device emulator and I could not understand the behavior at all. To make matters worse it also did not behave as expected on my phone, but it behaved differently than in the device emulator.
In the emulator the page loads as I expected: the Body formed a grid with a header, the main content overflowed on the x axis so the whole page had a horizontal scroll bar, and the footer was flush with the bottom of the page. When I removed the class that made the main content oversized it looked pretty nice! Then when I added it back in the html and body in the element inspector suddenly were a small portion of the screen and the footer was flush with that bottom, the whole page scrolled left and right, and there was a giant empty space at the bottom of the actual view.
The red box above shows the empty space. The blue box on the same screenshot shows where the element inspector claimed the html and body elements were. On my phone (iPhone 13 Pro, Safari), I get more or less the expected behavior, but the window chrome hides the footer, so I have to scroll and I can't make it go away. I have to assume that if I had a smaller physical device to test it on the behavior would be the same...To add another mystery: sometimes after I've had it open it will load straight into the weirdly resized mode.
I stripped it down to the simplest I could make it and then added a few buttons to demonstrate the behavior. The code is here, and it's up live here.
I know this is some sort of interaction between the <meta viewport> and what 100vh and 100vw means...but I really, really don't understand what's going on. I know how to get my code working mostly the way I want it. An acceptable answer will explain what I'm seeing in the emulator. To phrase it as a question: "What is going on in the emulator that causes this strange re-sizing?"
EDIT
It looks like the initial rendering depends on the previous state of the page. If you reload the demo above when the section is not oversized it renders as in the first screenshot. If you reload the demo when the screenshot is oversized it initially renders as in the final screenshot.
I think the issue is in the viewport meta. Try adding initial-scale=1 - this will set the initial zoom level of the page (device-width).
My guess is the zoom happens because changing overflow to anything but visible creates a new block formatting context - causing the initial state to be re-evaluated. At the point of re-evaluation, the content exceeds the viewport why the width is considered to be 960px (iOS default) - triggering the zoom to fit.
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1">
I think I found the problem, but I'm not sure I can explain it properly since I'm not an expert or specialist. I actually spent more than an hour inspecting your code, trying different solutions, and reading some related research.
1- I noticed that you use rem unit to resize elements NOT font but when I research it, I found most if not all results relate rem with root element and font-size. so I was wondering if we can use rem to size element and kept searching about and found that it's possible, but I couldn't find enough details about this specific part.
Even on mdn web docs, it says "the rem unit means "The root element's font-size" (rem stands for "root em")."
2- Anyway, in your code, I changed 50rem to 80% and the problem disappeared in the emulator and everywhere.
I hope my effort helps.
function toggleContentOverflow() {
const isOn = document.body.classList.toggle("content-overflow");
contentOverflow.textContent = isOn ? "On" : "Off";
}
function toggleMainScroll() {
const isOn = document.body.classList.toggle("overflow-scroll");
overflowScroll.textContent = isOn ? "On" : "Off";
}
html, body {
width: 100vw;
height: 100vh;
max-width: 100vw;
max-height: 100vh;
font-size: 14px;
padding: 0;
margin: 0;
}
body {
display: grid;
grid-template-rows: auto 1fr auto;
}
header, main, footer {
padding: 1rem 2rem;
}
main {
padding-block-start: 0;
padding-block-end: 0;
}
header, footer {
background-color: #ffe;
}
header {
border-bottom: 1px solid #eee;
}
footer {
border-top: 1px solid #eee;
}
section {
background-color: #ddd;
padding: 2rem;
margin: 2rem auto;
}
body.content-overflow section {
width: 80%;
}
body.overflow-scroll main {
overflow: auto;
}
<body class="content-overflow">
<header>
Header Content
</header>
<main>
<section>
<h1>Too Large Card</h1>
</section>
<aside style='margin-top: 1em'>
<button type="button" onclick="toggleContentOverflow(event)">"section { width: 80%; }" <span id="contentOverflow">On</span></button>
</aside>
<aside style='margin-top: 1em'>
<button type="button" onclick="toggleMainScroll(event)">"main { overflow: scroll }" <span id="overflowScroll">Off</span></button>
</aside>
<p>
To see this quirk:
</p>
<ol>
<li>Open this page in private mode (to avoid extensions inserting elements).</li>
<li>Open the developer tools</li>
<li>Turn on the device emulator</li>
<li>refresh the page</li>
<li>Click the 'section { width: var(--size-15); }' button.</li>
</ol>
</main>
<footer>
Footer Content
</footer>
</body>

Fixed header position in bootstrap 3 modal

I want to use a fixed header in my Bootstrap modal, however if I set .modal-header as position:fixed it scrolls along with the moda content. How do I create a trully fixed header in BS modal?
Instead of trying to make the header fixed, just fix the height of the body and make it scrollable. That way the header (and footer) will always be visible.
You can easily do this using the CSS3 vh unit together with calc. Both vh as calc have pretty good browser support (IE9+).
The vh unit is relative to the viewport (= browser window) height. 1 vh is 1% of the height and 100vh means 100% of the viewport height.
We just need to substract the height of the modal's header, footer and margins. It's going to be difficult it that dynamic. If those sizes are fixed, we just add all the heights.
Set either the height or max-height to calc(100vh - header+footer px).
.modal-body {
max-height: calc(100vh - 210px);
overflow-y: auto;
}
See the jsfiddle
Here's a simple trick.
Let me assume that i have to fix the div with class "fixedHeader"
Simple Jquery Way:
$('.modal').scroll(function() {
var a=$('.modal').scrollTop();
$('.fixedHeader').css('top',a+'px');
});
CSS
.fixedHeader {
position:fixed;
}
Whatever i have answered above is for normal bootstrap using jquery.But if someone is using angular bootstrap for the modal then
Angular
$timeout(function() {
angular.element('.modal').scroll(function() {
var a = angular.element('.modal').scrollTop();
angular.element('.fixedHeader').css('top', a + 'px');
});
}, 10);
/*Put the above in modalinstance controller*/
/*timeout is necessary as you want to run the function after the modal is loaded or sometimes it may be unable to find the class '.modal' */
CSS
.fixedHeader {
position:fixed;
}
Make sure you have jquery dependency installed in both cases.
My solution may seem a little silly, but it details the steps I took to solve this problem for my use case.
I tried something like ritz078's answer, but what I found was that it did not work well on iOS when scrolling, since Safari likes to do things its own way.
So, my solution was to duplicate the bit of code I wanted to affix and place that duplicate code outside of the modal altogether in its own hidden wrapper:
<div class="fixed-header">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title">Modal title</h4>
</div>
</div>
Then I used JS to 1) make the duplicate code visible after I scroll through the modal a bit, 2) close the duplicate code whenever I click out of the modal, and 3) restore functionality to the duplicate modal's close button:
$('#myModal').on('scroll', function() {
var threshold = 60;
if ($('#myModal').scrollTop() > threshold) {
$('.fixed-header').addClass('affixed');
}
else {
$('.fixed-header').removeClass('affixed');
}
});
$('#myModal').on('hide.bs.modal', function (e) {
$('.fixed-header').removeClass('affixed');
});
$('.fixed-header button').click(function() {
$('#myModal').modal('hide');
});
The challenge here is matching the modal's styling (particularly its width and margins), but this solution lets you scroll the modal freely on iOS without looking funky, which was my goal.
JSFiddle (forked from Jasny's answer to show how it's different in scope from his answer)
As per my comment, this was actually an improvement in Bootstrap 3. Allowing long content and having the whole modal scroll, not just the 'content' of the modal.
You can override it with something like this, but it's not as nice functionality.
.modal {
overflow: hidden;
}
.modal-body {
height: 300px;
overflow: auto;
}
Demo
In javascript add a class to modal:
windowClass: 'framework-modal'
In css adjust o modal-body:
.framework-modal .modal-body {
max-height: 600px;
overflow: auto;
}
In Body content
CSS:
body{
overflow:hidden;
}

<ul> with overflow-y: auto and <li>s with relative position behaves oddly

I've the following mark up
<div class="row-fluid">
<div class="span4">
<ul class="test">
<li>Arun</li>
<li>Krishna</li>
<li>Soundar</li>
</ul>
</div>
</div>
And css
.test {
height: 500px;
margin-top: 10px;
overflow-y: auto;
padding: 10px 4px 70px;
}
And script
$('.test li').draggable({
revert: 'invalid'
})
If you drag the items to the right side, it disappears, I don't know why it is behaving so.
If the overflow-y: auto; style is removed from .test it works fine.
Demo: Fiddle
You may have to increase the width of the preview tab because of the responsive css to replicate the issue in the fiddle
I know this is an old question, but I think I found an answer. The reason the list disappears is because you drag it off the edge of the div and it begins to auto scroll. So, you you just remove it from the flow of the page when it is clicked on, like so:
$('.test li').draggable({
revert: 'invalid'
}).mousedown(function() {
var that = $(this);
//create div to fill the vacated space
$div = $('<div></div>');
$div.width(that.width());
$div.height(that.height());
$div.insertAfter(that);
//remove from flow of the page, set a different background color to see it work
that.css({'position':'absolute'});
});
New fiddle here
The problem with the CSS on bootstrap-combined.min.css. It sets some width due to that it causes the issue.
So remove bootstrap-combined.min.css and try.
Refer LIVE DEMO
UPDATE:
Add border: 1px solid; to your .test class then you will see the actual difference, why it is working if you comment overflow
If you debug on firebug, you will see below CSS class (that is from bootstrap-combined.min.css) is causing your issue
.row-fluid .span4 {
width: 31.4917%;
}
All you required was, the 'min-width:1100px', and 'width:100%;'
I have updated fiddle for you
Fiddle Link : http://jsfiddle.net/br57F/5/
Its working fine. overflow works with content, while draggable, will not add content to parent div. it will just change position in run time of the element.
I hope this will do :)
Just during drag, wrap dragged element by a div include position:absolute style.
So instead this code:
$('.test li').draggable({
revert: 'invalid'
})
Use this one:
$('.test li').draggable({
revert: 'invalid',
start: function(event, el){
$(el.helper[0]).wrap( "<div style='position:absolute;'></div>" );
},
stop: function(event, el){
$(el.helper[0]).unwrap();
}
});
Live Demo

Stop fixed positioned header to overlap navigation bar

I have something like this:
<div id="navigation"></div>
<div id="header"></div>
<div id="content"></div>
The #header has "position:fixed; top: 0;", I need it to be this way except when the #navigation is visible (not scrolled away), in this case the #header should be displayed after the #navigation.
Could this be done with pure css?
Or any clean JS solution?
Here is the jsfiddle.
Sounds like a job for the jQuery Waypoints plugin: http://imakewebthings.com/jquery-waypoints/
Using the sticky element shortcut:
Just add
$(document).ready(function() {
$('#header').waypoint('sticky');
})
and a style for the stuck element
#header.stuck {
position: fixed;
top: 0;
}
Here is the updated fiddle
Here's a solution using jQuery.
Updated JSFiddle
//Checks if navigation is visible and sets position of header accordingly
function headerPosition(){
if($('#navigation').is(':visible')){
$('#header').css('position', 'static');
}
else{
$('#header').css('position', 'fixed');
}
}
//Run function to set correct header position
headerPosition();
//Show/Hide navigation to see function in action - not needed for production
$('button').on('click', function(){
$('#navigation').toggle();
headerPosition();
});

IE7 relative/absolute positioning bug with dynamically modified page content

I was wondering if there's anyone having an idea how to tackle with the following problem in IE7:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>IE7 absolute positioning bug</title>
<style type="text/css">
#panel { position: relative; border: solid 1px black; }
#spacer { height: 100px; }
#footer { position: absolute; bottom: 0px; }
</style>
<script type="text/javascript">
function toggle() {
var spacer = document.getElementById("spacer");
var style = "block";
if (spacer.style.display == "block" || spacer.style.display == "") {
style = "none";
}
spacer.style.display = style;
}
</script>
</head>
<body>
<div id="panel">
<button onclick="toggle();">Click me</button>
<br /><br /><br />
<div id="spacer"></div>
<div id="footer">This is some footer</div>
</div>
</body>
</html>
When you run this in IE7 you'll see that the "footer" element stays after modifying the CSS for "panel". The same example tested in IE8, FF and Chrome behaves exactly as expected.
I've already tried updating the element's class but this does not work if the browser's window has been opened maximized and no further size changes were made to the window (which is about 90% of the use cases we have for our product.... :( )
I'm stuck with a CSS-based solution however I think that I can make an exception in this case if it can easily be made IE7-specific (which means that other browsers will behave in a standard way with this).
Please help!
This is related to the "hasLayout bug" of IE. The relatively positioned #panel parent doesn't have layout and hence IE forgets to redraw its children when it get resized/repositioned.
The problem will go if you add overflow: hidden; to the relatively positioned #panel parent.
#panel { position: relative; overflow: hidden; border: solid 1px black; }
In depth background information about this IE bug can be found in the excellent reference "On having layout" and then for your particular problem specifically the chapter "Relatively positioned elements":
Note that position: relative does not trigger hasLayout, which leads to some rendering errors, mostly disappearing or misplaced content. Inconsistencies might be encountered by page reload, window sizing and scrolling, selecting. With this property, IE offsets the element, but seems to forget to send a “redraw” to its layout child elements (as a layout element would have sent correctly in the signal chain of redraw events).
The overflow property triggers the element to have layout, see also the chapter "Where Layout Comes From":
As of IE7, overflow became a layout-trigger.
This solution doesn't necessarily have anything to do with dynamic content, but it worked for me (at least made the page borked to a manageable degree): specify dimensions.
I only noticed IE7 thought a div didn't have a width when using the crappy 'Select element by click' tool (ctrl+B) in IE tools.
I have created my function to trigger redraw. Maybe it is not a right solution, but it works.
// Script to fix js positon bug on IE7
// Use that function, recomended delay: 700
function ie7fixElementDelayed(elements, delay) {
window.setTimeout(
function () {
if(navigator.appVersion.indexOf("MSIE 7.") != -1) {
ie7fixElement(elements);
}
},
delay
);
}
function ie7fixElement(elements) {
elements.each(function(i) {
var element = $(this);
var orginalDisplayValue = element.css("display");
element.css("display", "none");
element.css("display", orginalDisplayValue);
});
}
Sample usage:
ie7fixElementDelayed($('#HandPickedWidget .widget'), 700);

Resources