Prevent body scrolling but allow overlay scrolling - css

I've been searching for a "lightbox" type solution that allows this but haven't found one yet (please, suggest if you know of any).
The behavior I'm trying to recreate is just like what you'd see at Pinterest when clicking on an image. The overlay is scrollable (as in the whole overlay moves up like a page on top of a page) but the body behind the overlay is fixed.
I attempted to create this with just CSS (i.e. a div overlay on top of the whole page and body with overflow: hidden), but it doesn't prevent div from being scrollable.
How to keep the body/page from scrolling but keep scrolling inside the fullscreen container?

Theory
Looking at current implementation of the pinterest site (it might change in the future), when you open the overlay, a noscroll class is applied to the body element (setting overflow: hidden) making the body no longer scrollable.
The overlay created on-the-fly or already injected in the page and made visible via display: block — it makes no difference – has position : fixed and overflow-y: scroll, with top, left, right and bottom properties set to 0: this style makes the overlay fill the whole viewport (but now we are in 2022, so you may use inset: 0 instead).
The div inside the overlay is in position: static so the vertical scrollbar is related to that element. This is resulting in a scrollable but fixed overlay.
When you close the overlay, you have to hide it (using display: none) and you could even remove the node via javascript (or just the content inside, it's up to you but also depends on the nature of the content).
The final step is to also remove the noscroll class applied to the body (so the overflow property gets back to the value it had previously)
Code
Codepen Example
(it works by changing the aria-hidden attribute of the overlay in order to show and hide it and to increase its accessibility).
Markup
(open button)
<button type="button" class="open-overlay">OPEN LAYER</button>
(overlay and close button)
<section class="overlay" aria-hidden="true" tabindex="-1">
<div>
<h2>Hello, I'm the overlayer</h2>
...
<button type="button" class="close-overlay">CLOSE LAYER</button>
</div>
</section>
CSS
.noscroll {
overflow: hidden;
}
.overlay {
position: fixed;
overflow-y: scroll;
inset: 0; }
[aria-hidden="true"] { display: none; }
[aria-hidden="false"] { display: block; }
Javascript (vanilla-JS)
var body = document.body,
overlay = document.querySelector('.overlay'),
overlayBtts = document.querySelectorAll('button[class$="overlay"]'),
openingBtt;
[].forEach.call(overlayBtts, function(btt) {
btt.addEventListener('click', function() {
/* Detect the button class name */
var overlayOpen = this.className === 'open-overlay';
/* storing a reference to the opening button */
if (overlayOpen) {
openingBtt = this;
}
/* Toggle the aria-hidden state on the overlay and the
no-scroll class on the body */
overlay.setAttribute('aria-hidden', !overlayOpen);
body.classList.toggle('noscroll', overlayOpen);
/* On some mobile browser when the overlay was previously
opened and scrolled, if you open it again it doesn't
reset its scrollTop property */
overlay.scrollTop = 0;
/* forcing focus for Assistive technologies but note:
- if your modal has just a phrase and a button move the
focus on the button
- if your modal has a long text inside (e.g. a privacy
policy) move the focus on the first heading inside
the modal
- otherwise just focus the modal.
When you close the overlay restore the focus on the
button that opened the modal.
*/
if (overlayOpen) {
overlay.focus();
}
else {
openingBtt.focus();
openingBtt = null;
}
}, false);
});
/* detect Escape key when the overlay is open */
document.body.addEventListener('keyup', (ev) => {
if (ev.key === "Escape" && overlay.getAttribute('aria-hidden') === 'false') {
overlay.setAttribute('aria-hidden', 'true');
body.classList.toggle('noscroll', false);
openingBtt.focus();
openingBtt = null;
}
})
Finally, here's another example in which the overlay opens with a fade-in effect by a CSS transition applied to the opacity property. Also a padding-right is applied to avoid a reflow on the underlying text when the scrollbar disappears.
Codepen Example (fade)
CSS
.noscroll { overflow: hidden; }
#media (min-device-width: 1025px) {
/* not strictly necessary, just an experiment for
this specific example and couldn't be necessary
at all on some browser */
.noscroll {
padding-right: 15px;
}
}
.overlay {
position: fixed;
overflow-y: scroll;
inset: 0;
}
[aria-hidden="true"] {
transition: opacity 1s, z-index 0s 1s;
width: 100vw;
z-index: -1;
opacity: 0;
}
[aria-hidden="false"] {
transition: opacity 1s;
width: 100%;
z-index: 1;
opacity: 1;
}

overscroll-behavior css property allows to override the browser's default overflow scroll behavior when reaching the top/bottom of content.
Just add the following styles to overlay:
.overlay {
overscroll-behavior: contain;
...
}
Codepen demo
Currently works in Chrome, Firefox and IE(caniuse)
For more details check google developers article.

If you want to prevent overscrolling on ios, you can add position fixed to your .noscroll class
body.noscroll{
position:fixed;
overflow:hidden;
}

Most solutions have the problem that they do not retain the scroll position, so I took a look at how Facebook does it. In addition to setting the underlaying content to position: fixed they also set the top dynamically to retain the scroll position:
scrollPosition = window.pageYOffset;
mainEl.style.top = -scrollPosition + 'px';
Then, when you remove the overlay again, you need to reset the scroll position:
window.scrollTo(0, scrollPosition);
I created a little example to demonstrate this solution
let overlayShown = false;
let scrollPosition = 0;
document.querySelector('.toggle').addEventListener('click', function() {
if (!overlayShown) {
showOverlay();
} else {
removeOverlay();
}
overlayShown = !overlayShown;
});
function showOverlay() {
scrollPosition = window.pageYOffset;
const mainEl = document.querySelector('.main-content');
mainEl.style.top = -scrollPosition + 'px';
document.body.classList.add('show-overlay');
}
function removeOverlay() {
document.body.classList.remove('show-overlay');
window.scrollTo(0, scrollPosition);
const mainEl = document.querySelector('.main-content');
mainEl.style.top = 0;
}
.main-content {
background-image: repeating-linear-gradient( lime, blue 103px);
width: 100%;
height: 200vh;
}
.show-overlay .main-content {
position: fixed;
left: 0;
right: 0;
overflow-y: scroll; /* render disabled scroll bar to keep the same width */
/* Suggestion to put: overflow-y: hidden;
Disabled scrolling still makes a mess with its width. Hiding it does the trick. */
}
.overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.3);
overflow: auto;
}
.show-overlay .overlay {
display: block;
}
.overlay-content {
margin: 50px;
background-image: repeating-linear-gradient( grey, grey 20px, black 20px, black 40px);
height: 120vh;
}
.toggle {
position: fixed;
top: 5px;
left: 15px;
padding: 10px;
background: red;
}
/* reset CSS */
body {
margin: 0;
}
<main class="main-content"></main>
<div class="overlay">
<div class="overlay-content"></div>
</div>
<button class="toggle">Overlay</button>

Don't use overflow: hidden; on body. It automatically scrolls everything to the top. There's no need for JavaScript either. Make use of overflow: auto;. This solution even works with mobile Safari:
HTML Structure
<div class="overlay">
<div class="overlay-content"></div>
</div>
<div class="background-content">
lengthy content here
</div>
Styling
.overlay{
position: fixed;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
background-color: rgba(0, 0, 0, 0.8);
.overlay-content {
height: 100%;
overflow: scroll;
}
}
.background-content{
height: 100%;
overflow: auto;
}
See the demo here and source code here.
Update:
For people who want keyboard space bar, page up/down to work: you need to focus on the overlay, e.g., clicking on it, or manually JS focusing on it before this part of the div will respond to keyboard. Same with when the overlay is "switched off", since it's just moving the overlay to the side. Otherwise to browser, these are just two normal divs and it wouldn't know why it should focus on any one of them.

It is worth noting that sometimes adding "overflow:hidden" to the body tag doesn't do the job. In those cases, you'll have to add the property to the html tag as well.
html, body {
overflow: hidden;
}

The behaviour you want to prevent is called scroll chaining. To disable it, set
overscroll-behavior: contain;
on your overlay in CSS.

You can easily do this with some "new" css and JQuery.
Initially: body {... overflow:auto;}
With JQuery you can dynamically switch between 'overlay' and 'body'. When on 'body', use
body {
position: static;
overflow: auto;
}
When on 'overlay' use
body {
position: sticky;
overflow: hidden;
}
JQuery for the switch('body'->'overlay'):
$("body").css({"position": "sticky", "overflow": "hidden"});
JQuery for the switch('overlay'->'body'):
$("body").css({"position": "static", "overflow": "auto"});

if anyone is looking for a solution for React function components, you can put this inside the modal component:
useEffect(() => {
document.body.style.overflowY = 'hidden';
return () =>{
document.body.style.overflowY = 'auto';
}
}, [])

Generally speaking, if you want a parent (the body in this case) to prevent it from scrolling when a child (the overlay in this case) scrolls, then make the child a sibling of the parent to prevent the scroll event from bubbling up to the parent. In case of the parent being the body, this requires an additional wrapping element:
<div id="content">
</div>
<div id="overlay">
</div>
See Scroll particular DIV contents with browser's main scrollbar to see its working.

The chosen answer is correct, but has some limitations:
Super hard "flings" with your finger will still scroll <body> in the background
Opening the virtual keyboard by tapping an <input> in the modal will direct all future scrolls to <body>
I don't have a fix for the first issue, but wanted to shed some light on the second. Confusingly, Bootstrap used to have the keyboard issue documented, but they claimed it was fixed, citing http://output.jsbin.com/cacido/quiet as an example of the fix.
Indeed, that example works fine on iOS with my tests. However, upgrading it to the latest Bootstrap (v4) breaks it.
In an attempt to figure out what the difference between them was, I reduced a test case to no longer depend on Bootstrap, http://codepen.io/WestonThayer/pen/bgZxBG.
The deciding factors are bizarre. Avoiding the keyboard issue seems to require that background-color is not set on the root <div> containing the modal and the modal's content must be nested in another <div>, which can have background-color set.
To test it, uncomment the below line in the Codepen example:
.modal {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 2;
display: none;
overflow: hidden;
-webkit-overflow-scrolling: touch;
/* UNCOMMENT TO BREAK */
/* background-color: white; */
}

For touch devices, try adding a 1px wide, 101vh min-height transparent div in the wrapper of the overlay. Then add -webkit-overflow-scrolling:touch; overflow-y: auto; to the wrapper. This tricks mobile safari into thinking the overlay is scrollable, thus intercepting the touch event from the body.
Here's a sample page. Open on mobile safari: http://www.originalfunction.com/overlay.html
https://gist.github.com/YarGnawh/90e0647f21b5fa78d2f678909673507f

I found this question trying to solve issue I had with my page on Ipad and Iphone - body was scrolling when I was displaying fixed div as popup with image.
Some answers are good, however none of them solved my issue. I found following blog post by Christoffer Pettersson. Solution presented there helped issue I had with iOS devices and it helped my scrolling background problem.
Six things I learnt about iOS Safari's rubber band scrolling
As it was suggested I include major points of the blog post in case link gets outdated.
"In order to disable that the user can scroll the background page while the "menu is open", it is possible to control what elements should be allowed to be scrolled or not, by applying some JavaScript and a CSS class.
Based on this Stackoverflow answer you can control that elements with the disable-scrolling should not
perform their default scroll action when the touchmove event is triggered."
document.ontouchmove = function ( event ) {
var isTouchMoveAllowed = true, target = event.target;
while ( target !== null ) {
if ( target.classList && target.classList.contains( 'disable-scrolling' ) ) {
isTouchMoveAllowed = false;
break;
}
target = target.parentNode;
}
if ( !isTouchMoveAllowed ) {
event.preventDefault();
}
};
And then put the disable-scrolling class on the page div:
<div class="page disable-scrolling">

Simple inline styling for the body tag:
<body style="position: sticky; overflow: hidden;">

If the intent is to disable on mobile/ touch devices then the most straightforward way to do it is using touch-action: none;.
Example:
const app = document.getElementById('app');
const overlay = document.getElementById('overlay');
let body = '';
for (let index = 0; index < 500; index++) {
body += index + '<br />';
}
app.innerHTML = body;
app.scrollTop = 200;
overlay.innerHTML = body;
* {
margin: 0;
padding: 0;
}
html,
body {
height: 100%;
}
#app {
background: #f00;
position: absolute;
height: 100%;
width: 100%;
overflow-y: scroll;
line-height: 20px;
}
#overlay {
background: rgba(0,0,0,.5);
position: fixed;
top: 0;
left: 0;
right: 0;
height: 100%;
padding: 0 0 0 100px;
overflow: scroll;
}
<div id='app'></div>
<div id='overlay'></div>
(The example does not work in the context of Stack Overflow. You will need to recreate it in a stand-alone page.)
If you want to disable scrolling of the #app container, just add touch-action: none;.

I'd like to add to previous answers because I tried to do that, and some layout broke as soon as I switched the body to position:fixed. In order to avoid that, I had to also set body's height to 100% :
function onMouseOverOverlay(over){
document.getElementsByTagName("body")[0].style.overflowY = (over?"hidden":"scroll");
document.getElementsByTagName("html")[0].style.position = (over?"fixed":"static");
document.getElementsByTagName("html")[0].style.height = (over?"100%":"auto");
}

Use the following HTML:
<body>
<div class="page">Page content here</div>
<div class="overlay"></div>
</body>
Then JavaScript to intercept and stop scrolling:
$(".page").on("touchmove", function(event) {
event.preventDefault()
});
Then to get things back to normal:
$(".page").off("touchmove");

In my case, none of these solutions worked out on iPhone (iOS 11.0).
The only effective fix that is working on all my devices is this one - ios-10-safari-prevent-scrolling-behind-a-fixed-overlay-and-maintain-scroll-position

try this
var mywindow = $('body'), navbarCollap = $('.navbar-collapse');
navbarCollap.on('show.bs.collapse', function(x) {
mywindow.css({visibility: 'hidden'});
$('body').attr("scroll","no").attr("style", "overflow: hidden");
});
navbarCollap.on('hide.bs.collapse', function(x) {
mywindow.css({visibility: 'visible'});
$('body').attr("scroll","yes").attr("style", "");
});

One solution for a React functional component is to use the useEffect hook.
Here's the code example bellow (pay attention to the useEffect definition):
import {useEffect, useRef} from "react";
export default function PopoverMenu({className, handleClose, children}) {
const selfRef = useRef(undefined);
useEffect(() => {
const isPopoverOpenned = selfRef.current?.style.display !== "none";
const focusedElement = document?.activeElement;
const scrollPosition = {x: window.scrollX, y: window.scrollY};
if (isPopoverOpenned) {
preventDocBodyScrolling();
} else {
restoreDocBodyScrolling();
}
function preventDocBodyScrolling() {
const width = document.body.clientWidth;
const hasVerticalScrollBar = (window.innerWidth > document.documentElement.clientWidth);
document.body.style.overflowX = "hidden";
document.body.style.overflowY = hasVerticalScrollBar ? "scroll" : "";
document.body.style.width = `${width}px`;
document.body.style.position = "fixed";
}
function restoreDocBodyScrolling() {
document.body.style.overflowX = "";
document.body.style.overflowY = "";
document.body.style.width = "";
document.body.style.position = "";
focusedElement?.focus();
window.scrollTo(scrollPosition.x, scrollPosition.y);
}
return () => {
restoreDocBodyScrolling(); // cleanup on unmount
};
}, []);
return (
<>
<div
className="backdrop"
onClick={() => handleClose && handleClose()}
/>
<div
className={`pop-over-menu${className ? (` ${className}`) : ""}`}
ref={selfRef}
>
<button
className="pop-over-menu--close-button" type="button"
onClick={() => handleClose && handleClose()}
>
X
</button>
{children}
</div>
</>
);
}
Originally posted on this other related Stackoverflow question: https://stackoverflow.com/a/69016517/14131330

CSS
.noScroll {
overflow: hidden;
}
Javascript
<script>
function toggleNav() {
document.body.classList.toggle("noScroll");
}
</script>
Button
<button onclick="toggleNav()">
Toggle Nav
</button>

If you want to stop body/html scroll add as the following
CSS
html, body {
height: 100%;
}
.overlay{
position: fixed;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
background-color: rgba(0, 0, 0, 0.8);
.overlay-content {
height: 100%;
overflow: scroll;
}
}
.background-content{
height: 100%;
overflow: auto;
}
HTML
<div class="overlay">
<div class="overlay-content"></div>
</div>
<div class="background-content">
lengthy content here
</div>
Basically, you could do it without JS.
The main idea is to add html/body with height: 100% and overflow: auto.
and inside your overlay, you could either enable/disable scroll based on your requirement.
Hope this helps!

Use below code for disabling and enabling scroll bar.
Scroll = (
function(){
var x,y;
function hndlr(){
window.scrollTo(x,y);
//return;
}
return {
disable : function(x1,y1){
x = x1;
y = y1;
if(window.addEventListener){
window.addEventListener("scroll",hndlr);
}
else{
window.attachEvent("onscroll", hndlr);
}
},
enable: function(){
if(window.removeEventListener){
window.removeEventListener("scroll",hndlr);
}
else{
window.detachEvent("onscroll", hndlr);
}
}
}
})();
//for disabled scroll bar.
Scroll.disable(0,document.body.scrollTop);
//for enabled scroll bar.
Scroll.enable();

Related

Why Codemirror editor does NOT scroll horizontally with long line?

I've a code mirror version: 5.65.3 with Blazor. When I've a long line in the editor the horizontal scroll doesn't work, it rather uses the scroll of the page which mess out the whole page.
Like this:
I don't think that I changed any CSS in Codemirror.
Here is some related CSS lines:
.CodeMirror {
/* Set height, width, borders, and global font properties here */
font-family: monospace;
height: 750px;
color: black;
direction: ltr;
}
.CodeMirror-scroll {
overflow: scroll !important; /* Things will break if this is overridden */
/* 50px is the magic margin used to hide the element's real scrollbars */
/* See overflow: hidden in .CodeMirror */
margin-bottom: -50px; margin-right: -50px;
padding-bottom: 50px;
height: 100%;
outline: none; /* Prevent dragging from highlighting the element */
position: relative;
z-index: 0;
}
I'm calling the codemirror through this code: (the onchange is because I'm using Blazor for binding purposes )
window.editor= function (dontNetObjRef) {
editor = CodeMirror.fromTextArea(document.getElementById('myTextArea'), {
lineNumbers: true,
indentUnit: 4,
lineWrapping: true,
tabMode: "shift",
gutters: ["CodeMirror-lint-markers"]
});
//JavaScript function use the onchange event of CodeMirror to invoke the C# method and pass the value of the myTextArea .
editor.on("change", editor => {
dontNetObjRef.invokeMethodAsync("UpdateField", editor.getValue());
// console.log(editor.getValue());
});
Note: even if I used lineWrapping: true it moved to the second line and does the same issue with scroll.
Also, it works well when I set a fixed width like 1000px but I'd like to make it auto in case the screen size of the user changes.
Thanks to Jax-p for giving me some hints to fix the issue.
I've add width:70vw in .CodeMirror class and max-width:70vm in .CodeMirror-scroll
Another thing that was affecting the changes is that I was putting the textarea in inside a <div class=col-11> which was affecting the width in the CSS so I just removed that and everything is working.
While working on a project I've encountered the same issue - that is a problem with CSS.
I fixed it with that pretty simple flexbox solution:
<div class="root-wrapper"> <!-- Editor parent container -->
<div class="cm-editor ͼ1 ͼ2 ͼ4"> <!-- CodeMirror stuff (v6 in my case) -->
...
</div>
</div>
The corresponding styling:
.root-wrapper {
display: flex;
flex-direction: row;
.cm-editor {
width: 0;
flex-grow: 1;
}
}

avoid automatic jump to bottom on page with iframe video

I have a video showing on a page like this
<div class="embed-responsive embed-responsive-16by9">
<iframe class="embed-responsive-item" src="{{skin url="video/hande.mp4"}}"></iframe>
</div>
but when loading the page on tablet / mobile the page automatically jumps to the bottom where the video is. I tried adding something like this
<iframe style="display: none;" onload="this.style.display='block';" href="..."></iframe>
following from this question iframe on the page bottom: avoid automatic scroll of the page but the suggestions on there don't work for me.
Can anyone point me in the right direction? Thank you
UPDATE
The OP has found an acceptable solution by utilizing scrollTo:
<script type="text/javascript">
// <![CDATA[ window.onload = function(){ window.scrollTo(0,0); }// ]]>
</script>
which seems to work, there's a bit of a delay though so its not great but so far its the only thing that seems to have worked.
So to add to OP's solution, try:
<script>
// <![CDATA[ document.addEventListener("DOMContentLoaded", function(){ window.scrollTo(0,0); }, false )// ]]>
</script>
Using window.onload means your function will be called after everything else has loaded; DOM, images, script, etc.
Using DOMContentLoaded means your function will be called after the DOM has loaded (which means after any iframes have loaded, which is usually the slowest part of the DOM content). What it doesn't wait for is script, so make sure you place the YouTube script before this line. There are exceptions of course see ARTICLE
UPDATE
It seems that the focus event could be the culprit so what you can do is offer the browser to focus on something else.
Create a temporary transparent input while page is loading.
When the page is fully loaded, use a callback to remove the input.
Forgot to actually update the snippet...so it's added now.
Try this snippet below. View in 'Full Page'. You have to scroll down to the bottom and to your right, because it ain't gonna scroll without help.
document.addEventListener('DOMContentLoaded', init, false);
window.load = function() {
var fpt = document.querySelector('.focalPoint');
fpt.parentNode.removeChild(fpt);
}
function init() {
var fpt = document.createElement('input');
document.body.appendChild(fpt);
fpt.classList.add('focalPoint');
if (fpt != document.activeElement) {
fpt.focus();
}
}
.box {
width: 50vw;
/* Arbitrary */
}
.vidWrap {
position: relative;
/* Anchor the iframe's parent */
padding-bottom: 56%;
/* This is for AR 16:9 (ie. wide screen) videos */
padding-top: 20px;
/* Offset to padding-bottom */
height: 0;
/* Makes a tight 'seal' */
overflow-y: hidden;
/* Ensures that edges aren't breached */
overflow-x: hidden;
/* As above */
-webkit-overflow-scrolling: touch;
/* For iOS7 ... not so sure about iOS8 or iOS9 */
bottom: -50vw;
/* Arbitrary. */
left: 50vw;
/* Arbitrary */
}
.vid {
overflow-x: hidden;
/* See above */
overflow-y: hidden;
/* As above */
height: 100%;
/* stretched to the edge of parent */
width: 100%;
/* As above */
position: absolute;
/* Allows control within the parent */
/* These coords will stretch the iframe seemlessly to parent's edges */
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.focalPoint {
visibility: hidden;
opacity: 0;
line-height: 0;
font-size: 0;
border: 0;
outline: 0;
position: fixed;
top: 0;
left: 0;
z-index: 9999;
}
<section class="box">
<div class="vidWrap">
<iframe id="vid1" class="vid" src="http://media6000.dropshots.com/photos/1381926/20170326/023642.mp4" frameborder="0" scrolling="no" height="100%" width="100%" allowfullscreen></iframe>
</div>
</section>

iOS 7 iPad Safari Landscape innerHeight/outerHeight layout issue

We're seeing issues with a web app that has a height of 100% on Safari in iOS 7. It appears that the window.innerHeight (672px) doesn't match window.outerHeight (692px), but only in landscape mode. What ends up happening is that in an app with 100% height on the body, you get 20px of extra space. This means that when a user swipes up on our app, the navigation elements get pulled behind the browser chrome. It also means that any absolutely positioned elements that are at the bottom of the screen end up being 20px off.
This issue was also outlined in this question here:
IOS 7 - css - html height - 100% = 692px
And can be seen in this ambiguous screenshot:
What we're trying to do is hack around this so that until Apple fixes the bug, we don't have to worry about it.
One way of doing this is to absolutely position the body only in iOS 7, but this pretty much puts the extra 20px at the top of the page instead of the bottom:
body {
position: absolute;
bottom: 0;
height: 672px !important;
}
Any help with forcing outerHeight to match innerHeight, or hacking around it so that our users can't see this issue would be much appreciated.
In my case, the solution was to change positioning to fixed:
#media (orientation:landscape) {
html.ipad.ios7 > body {
position: fixed;
bottom: 0;
width:100%;
height: 672px !important;
}
}
I also used a script to detect iPad with iOS 7:
if (navigator.userAgent.match(/iPad;.*CPU.*OS 7_\d/i)) {
$('html').addClass('ipad ios7');
}
Simple, cleaner CSS-Only solution:
html {
height: 100%;
position: fixed;
width: 100%;
}
iOS 7 seems to set the height correctly with this. Also there is no need for resize javascript events, etc.
Since you are working with a full height app, it doesn't really matter if it is always position fixed.
Samuel's answer, as also stated by Terry Thorsen, is working great, but fails in case the webpage has been added to the iOS home.
A more intuitive fix would be to check for window.navigator.standalone var.
if (navigator.userAgent.match(/iPad;.*CPU.*OS 7_\d/i) && !window.navigator.standalone) {
$('html').addClass('ipad ios7');
}
This way it only applies when opened inside Safari, and not if launched from home.
Samuel's answer is the best although it breaks if a user adds the page to their home screen (home screen pages don't exhibit the bug). Check the innerHeight before adding the class like so:
if (navigator.userAgent.match(/iPad;.*CPU.*OS 7_\d/i)) {
if(window.innerHeight==672){
$('html').addClass('ipad ios7');
}
}
Note that the bug also does not exhibit under webview.
I used this JavaScript solution for solving that problem:
if (navigator.userAgent.match(/iPad;.*CPU.*OS 7_\d/i) && window.innerHeight != document.documentElement.clientHeight) {
var fixViewportHeight = function() {
document.documentElement.style.height = window.innerHeight + "px";
if (document.body.scrollTop !== 0) {
window.scrollTo(0, 0);
}
}.bind(this);
window.addEventListener("scroll", fixViewportHeight, false);
window.addEventListener("orientationchange", fixViewportHeight, false);
fixViewportHeight();
document.body.style.webkitTransform = "translate3d(0,0,0)";
}
A variant of Samuel's approach, but with position: -webkit-sticky set on html worked for me the best.
#media (orientation:landscape) {
html.ipad.ios7 {
position: -webkit-sticky;
top: 0;
width: 100%;
height: 672px !important;
}
}
Notice 'top: 0', not 'bottom: 0', and target element is 'html', not 'body'
Basically there are two bugs - the hight of the window in landscape mode and the scroll position when the user rewerts to it from portrait mode. We have solved it this way:
the hight of the window is controlled by:
// window.innerHeight is not supported by IE
var winH = window.innerHeight ? window.innerHeight : $(window).height();
// set the hight of you app
$('#yourAppID').css('height', winH);
// scroll to top
window.scrollTo(0,0);
now, the above can be put into a function and bind to window resize and/or orientation change events. that's it... see example:
http://www.ajax-zoom.com/examples/example22.php
You need JavaScript to work around this bug. window.innerHeight has the correct height. Here's the simplest solution I can think of:
$(function() {
function fixHeightOnIOS7() {
var fixedHeight = Math.min(
$(window).height(), // This is smaller on Desktop
window.innerHeight || Infinity // This is smaller on iOS7
);
$('body').height(fixedHeight);
}
$(window).on('resize orientationchange', fixHeightOnIOS7);
fixHeightOnIOS7();
});
You'll also need to set position: fixed on the <body>.
Here's a complete, working example:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<title>iOS7 height bug fix</title>
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script>
$(function() {
function fixHeightOnIOS7() {
var fixedHeight = Math.min(
$(window).height(),
window.innerHeight || Infinity
);
$('body').height(fixedHeight);
}
$(window).on('resize orientationchange', fixHeightOnIOS7);
fixHeightOnIOS7();
// Generate content
var contentHTML = $('#content').html();
for (var i = 0; i < 8; i++) contentHTML += contentHTML;
$('#content').html(contentHTML);
});
</script>
<style>
html,
body
{
margin: 0;
padding: 0;
height: 100%;
width: 100%;
overflow: auto;
position: fixed;
}
#page-wrapper
{
height: 100%;
position: relative;
background: #aaa;
}
#header,
#footer
{
position: absolute;
width: 100%;
height: 30px;
background-color: #666;
color: #fff;
}
#footer
{
bottom: 0;
}
#content
{
position: absolute;
width: 100%;
top: 30px;
bottom: 30px;
overflow: auto;
-webkit-overflow-scrolling: touch;
}
</style>
</head>
<body>
<div id="page-wrapper">
<div id="header">Header</div>
<div id="content">
<p>Lorem ipsum dolor sit amet.</p>
</div>
<div id="footer">Footer</div>
</div>
</body>
</html>
With reference to the accepted answer, I've also had luck with the following rule:
html.ipad.ios7 {
position: fixed;
width: 100%;
height: 100%;
}
This has the added advantage in that it appears to stop the html element scrolling "under" a fixed body element.
If I use this:
if (navigator.userAgent.match(/iPad;.*CPU.*OS 7_\d/i) && !window.navigator.standalone) {
$('html').addClass('ipad ios7');
}
My Safari on Mac shows the same html classes... SO its not working correctly.
I tried to combine a few things - that worked for me, so I can manage it in the browser and without Browser view.
jQuery
if (navigator.userAgent.match(/iPad/i) && (window.orientation) ){
$('html').addClass('ipad ');
if (window.innerHeight != window.outerHeight ){
$('html').addClass('browser landscape');
}
else{
$('html').addClass('browser portrait');
}
}
CSS
#media (orientation:landscape) {
html.ipad.browser > body {
position: fixed;
height: 671px !important;
}
}
///// With this you are more flexible or other OS and Browsers
I came across this page for the same issue.
There are a lot of useful answers here, and others not (for me).
However, I found a solution, which works in my case, and works totally independent of which OS version and which bug now or in the past or future.
Explaination: Developping a web app (no native app) with several modules of fixed size in fullscreen, with class name "module"
.module {position:absolute; top:0; right:0; bottom:0; left:0;}
which contains a footer with class name "footer"
.module > .footer {position:absolute; right:0; bottom:0; left:0; height:90px;}
Nevermind, if I set the height of the footer later to another height, or even its height is set by its content, I can use this following code for correction:
function res_mod(){
$('.module').css('bottom', 0); // <-- need to be reset before calculation
var h = $('.module > .footer').height();
var w = window.innerHeight;
var o = $('.module > .footer').offset();
var d = Math.floor(( w - o.top - h )*( -1 ));
$('.module').css('bottom',d+'px'); // <--- this makes the correction
}
$(window).on('resize orientationchange', res_mod);
$(document).ready(function(){
res_mod();
});
Thanks to the skills of Matteo Spinelli I can still use iScroll with no problems, as its change events fortunately are fired after. If not, it would be necessary to recall the iScroll-init again after the correction.
Hope this helps somebody
The accepted answer doesn't cope when the favorites bar is showing. Here is am improved catch all fix:
#media (orientation:landscape) {
html.ipad.ios7 > body {
position: fixed;
height: calc(100vh - 20px);
width:100%;
}
}
what if you try
html{ bottom: 0;padding:0;margin:0}body {
position: absolute;
bottom: 0;
height: 672px !important;
}

How can I prevent body scrollbar and shifting when twitter bootstrap modal dialog is loaded?

When I open the twitter bootstrap modal dialog, the backdrop causes a scrollbar and shift the content.
To avoid the scrollbar I use this css:
.modal {
overflow: hidden;
}
But I can not avoid the shift.
regards,
Marko
I am using Bootstrap Version 3
Marko,
I just had the same problem. It appears that Bootstrap 3.0.0 adds a class to <body>, modal-open, when the modal first shows. This class adds margin-right: 15px to the body to account for a scrollbar, which is there on longer pages. This is great, except for shorter pages when a scrollbar isn't on the body. In the no scrollbar case, the margin-right causes the body to shift left on modal open.
I was able to solve this by adding some short Javascript and a little CSS:
CSS:
/* override bootstrap 3 class to remove scrollbar from modal backdrop
when not necessary */
.modal {
overflow-y: auto;
}
/* custom class to override .modal-open */
.modal-noscrollbar {
margin-right: 0 !important;
}
JS:
(function($) {
$(document)
.on( 'hidden.bs.modal', '.modal', function() {
$(document.body).removeClass( 'modal-noscrollbar' );
})
.on( 'show.bs.modal', '.modal', function() {
// Bootstrap adds margin-right: 15px to the body to account for a
// scrollbar, but this causes a "shift" when the document isn't tall
// enough to need a scrollbar; therefore, we disable the margin-right
// when it isn't needed.
if ( $(window).height() >= $(document).height() ) {
$(document.body).addClass( 'modal-noscrollbar' );
}
});
})(window.jQuery);
These combined permit the margin-right scrollbar fix to work for long pages, yet is disabled for shorter pages (when document height <= window height). I hope this helps!
Bootstrap 3.0.1+ (tested up to 3.1.1) is a different story. Try the following:
CSS:
/* override bootstrap 3 class to remove scrollbar from modal backdrop
when not necessary */
.modal {
overflow-y: auto;
}
/* custom class to add space for scrollbar */
.modal-scrollbar {
margin-right: 15px;
}
JS:
(function($) {
$(document)
.on( 'hidden.bs.modal', '.modal', function() {
$(document.body).removeClass( 'modal-scrollbar' );
})
.on( 'show.bs.modal', '.modal', function() {
// Bootstrap's .modal-open class hides any scrollbar on the body,
// so if there IS a scrollbar on the body at modal open time, then
// add a right margin to take its place.
if ( $(window).height() < $(document).height() ) {
$(document.body).addClass( 'modal-scrollbar' );
}
});
})(window.jQuery);
EDIT:
In light of Mac eliminating scrollbars from inhabiting window render width, here's a more portable solution (3.0.1+) if you don't mind some feature detection. Reference: http://davidwalsh.name/detect-scrollbar-width
CSS:
.scrollbar-measure {
height: 100px;
overflow: scroll;
position: absolute;
top: -9999px;
}
JS:
window.jQuery(function() {
// detect browser scroll bar width
var scrollDiv = $('<div class="scrollbar-measure"></div>')
.appendTo(document.body)[0],
scrollBarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
$(document)
.on('hidden.bs.modal', '.modal', function(evt) {
// use margin-right 0 for IE8
$(document.body).css('margin-right', '');
})
.on('show.bs.modal', '.modal', function() {
// When modal is shown, scrollbar on body disappears. In order not
// to experience a "shifting" effect, replace the scrollbar width
// with a right-margin on the body.
if ($(window).height() < $(document).height()) {
$(document.body).css('margin-right', scrollBarWidth + 'px');
}
});
});
For me only the combination of two answers worked.
css:
body {
padding-right:0px !important;
margin-right:0px !important;
}
body.modal-open {
overflow: auto;
}
stylus:
body
padding-right:0px !important
margin-right:0px !important
&.modal-open
overflow: auto
Try this
body.modal-open {
overflow: auto;
}
Mine is easy there it is (CSS Only):
body {
padding-right:0px !important;
margin-right:0px !important;
}
The fact is the !important is overlapping bootstrap from changing padding and margin with the modal-open class and styles.
I had the same issue with the scroll bar disappearing, and the body shifting to the left when opening a bootstrap modal.
I've found out an easy fix:
.modal
{
overflow-y: auto !important;
}
.modal-open
{
overflow:auto !important;
overflow-x:hidden !important;
padding-right: 0 !important;
}
Good luck to all!
body {
/*prevent modal background jumping*/
padding-right:0px !important;
margin-right:0px !important;
}
/*prevent modal background jumping*/
body.modal-open {
overflow: auto;
}
the best way is:
to add to BODY overflow-y: scroll
and remove 4 functions from bootstrap.js: checkScrollbar, setScrollbar, resetScrollbar, and measureScrollbar.
I added this to my CSS and seemed to work when adding a 'fixed' overlay to view
.modal-open {
margin-right: 15px;
}
You should try this. this is working fine.
$j(document).ready(function() {
$('.modal').on('show.bs.modal', function () {
if ($(document).height() <= $(window).height()) {
$('body').css('margin-right','0','!important');
}
else {
$('body').removeAttr('style');
}
})
$('.modal').on('hide.bs.modal', function () {
$('body').removeAttr('style');
})
})
Try this simple javascript:
$j(document).ready(function() {
if ($(document).height() <= $(window).height()) {
$('body').css('margin-right','0','!important');
}
});
The following inclusion to my .css file fixed my centered content page from moving or resizing when the popup modal shows.
I did not need any Javascript, just the css code below. This fixed it for content with or without a vertical or horizontal scrollbar.
.modal
{
overflow-y: auto !important;
}
.modal-open {
overflow: auto !important;
overflow-x: hidden !important;
padding-right: 15px !important;
}
I'm using bootstrap 3.3.7.

CSS/JS Gradient-style opacity to fade out content

I'm wondering if there's a way to fade out (like a gradient) the opacity of an iframe and the content inside it. It's difficult to explain so a common example would be at the bottom of notification centre on Mountain Lion or iOs.
The whole idea is that when a user scrolls down (in an iframe) the content "fades out" at the bottom and it doesn't cut off with a straight line.
Not sure if this is possible with CSS or Javascript.
Any help is greatly appreciated. Thanks
If I've understood you correctly, you want something like this:
https://dl.dropbox.com/u/73603348/fadeout.html
What I've done in the past is create an overlay element at the bottom of the scrolling content. Pretty simple.
The markup:
<div class="content">
<div class="container">
[ content here ]
</div>
<div class="fader"></div>
</div>
The style:
.content {
width: 600px;
background: #fff;
margin: 50px auto 0;
overflow: auto;
position: relative;
}
.container {
height: 500px;
overflow: auto;
padding: 10px;
background: #ccc;
}
.fader {
position: absolute;
content: '';
bottom: 0;
left: 0;
right: 0;
height: 100px;
background: -webkit-linear-gradient(top, rgba(255,255,255,0), #fff);
}
Just in case you don't want to load the whole jQuery library, you can write your own function to do the fadeout. Here's my own try to write such a function:
var ifrm = document.getElementById("your_frame"); //find your frame
function fadeOut(var duration) { //duration: how many millseconds you want the effect to take
var step = 10 / duration; //step is how much the opacity will change each 10 milliseconds
var curOpacity = 1; //at first the iframe is fully opaque.
function animate() {
if(curOpacity < step) {
ifrm.style.opacity = 0; //we're done
return;
}
ifrm.style.opacity = curOpacity;
curOpacity -= step;
setTimeout(animate, 10); //wait 10 millseconds and move to next step of animation
}
animate();
}
So suppose you want to fadeout for 1 second, then the initial fadeOut function call would be: fadeOut(1000);.
Again, I hope that helped you.
You can use jQuery. Example on how to fade out an element:
$("#your_iframe_id").fadeOut();
More details on how to use fadeOut: jQuery API reference about fadeOut.

Resources