I am trying to implement a solution to prevent the iOS bounce effect in Safari for iOS when a web page content is larger than the viewport.
The page I am working on is quite specific in it's structure, and is very similar to this page http://new.salt.ch/
The basic structure is bootstrap-based.
It has a fixed navbar at the top.
It has a full-screen background slideshow.
The slideshow has an overlay that is fixed to the bottom of the viewport.
There is a footer element that loads off-canvas and is only visible on scrolling the content.
The content scrolls behind the navbar.
The content consistes of a title which is positioned 20px below the navbar and a series of buttons that are positioned 20px above the viewport.
When scrolling, the buttons and title all move up the screen to display the footer.
The problem I am having is the same as the problem on the page http://new.salt.ch/ in that when you scroll up, you get a bounce effect at the bottom of the screen and which reveals the background and overlay.
I have tried various solutions, including iNoBounce.js, Nonbounce.js and a few other suggestions I have found on SO.
I have the same issue always...when I try to disable the bounce, all scrolling gets disabled. I am guessing this is because the content (other than the footer) is always just large enough that the scroll is not needed, and so scrolling gets disabled and the footer no longer is accessible on scroll.
This code should stop the bounce as it's the HTML tag that bounces
html {
height : 100%;
overflow: hidden;
position: relative;
}
body {
height : 100%;
overflow: auto;
position: relative;
}
I went through a few answers on SO and things were looking bleak until stumbled upon this code.
html {
position: fixed;
height: 100%;
overflow: hidden;
}
body {
width: 100vw;
height: 100vh;
overflow-y: scroll;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
}
The style declarations for body can be put on any element that you want to have the ability to scroll. You can also alter overflow-x and overflow-y as needed. I personally needed it to NOT scroll to the sides so I declared it as so.
update Sept 15 2017: I had to use this fix for another project and I was able to do without these declarations position: fixed and height: 100%; on the html selector. YMMV
Update April 12 2018 (mentioned in comments): If you're using fixed elements on the page, those elements may have a visual "shakiness" when scrolling.
If I'm interpreting your question correctly, we've been having the same issue for years developing cross-platform mobile web apps, trying to get all the different proprietary scroll features to work correctly on each device: Apple iOS, Google Android, Windows Phone, laptop Chrome, laptop Safari, IE, and laptop Edge.
jQuery Mobile continues to try and fix this within the confines of their framework, but it's too much whack-a-mole, with the constant updates from each device maker / OS maker.
Yes, we've got solutions for each individual mobile device. And we have tested, but not seriously considered developing device-selective paging frameworks for each device, requiring us to detect each device and present a slightly different framework for each. Insanely bad idea with basically maintaining at least 3 and really up to a dozen different versions of your code.
SOLUTION: We've had the most luck by just putting your persistent header and footer on top of your page framework. Here is the general solution using in-line styles for simplicity:
<html>
<head>
<title>Fixed Header and Footer on All Mobile Web Apps</title>
<meta name="viewport" content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0" />
<style>
html, body { height:100%; width:100%; }
</style>
</head>
<body>
<div style="position: fixed; height:100%; width:100%; top:0; left:0;">
<div style="height:calc(100% - 1px); width:100%; margin-top: 60px; z-index: 1; overflow-y: scroll; -webkit-overflow-scrolling: touch;">
[Body Content That Can Scroll if it extends beyond screen...]
</div>
<div style="position: absolute; top:0; left:0; width:100%; height: 60px; background:#dddddd; z-index:10;">
[Header Content...]
</div>
<div style="position: absolute; bottom:0; left:0; width:100%; height: 40px; background:#cccccc; z-index:11;">
[Footer Content...]
</div>
</div>
</body>
</html>
So, the Body could be any jQuery Mobile set of pages. In fact, theoretically, the Body could be almost any content from any framework.
Special note, the line with height:calc(100% - 1px); is critical to the magic.
The seemingly infinite combinations or permutations of this issue have almost become a crusade for us over the years, trying to find the most pure, simplest, and most universally compatible solution. So after dedicating an embarrassing number of man-hours to this topic, this is not only our best solution, it's also the ONLY universally compatible approach we've found that also allows you to stick with just a singular code-base. It has been successfully tested on the latest versions of iOS, Windows Phone, Android, laptop Chrome, laptop Safari, PhoneGap, laptop Firefox, IE 9-11, and Windows Edge.
TAGS: mobile app, web app, fixed header, fixed footer, persistent header, persistent footer, scroll issue, iOS scroll bounce, Chrome scroll bounce, Android scroll bounce, webkit scroll issue, webkit touch scrolling, iOS touch scrolling issue
I used iNoBounce https://github.com/lazd/iNoBounce and it works perfectly!
If you need to allow horizontal scrolling as well, there is a pull request by santi6291 at https://github.com/lazd/iNoBounce/pull/36 which fixes that
For 2019 Safari on iOS 13, I was able to use this fix:
html {
overflow: hidden;
height: 100%;
position: fixed;
}
body {
overflow: auto;
height: 100%;
position: relative;
}
At least for me it covers most of the cases.
In my case, I wanted the address bar to disappear on scroll, and I get the bounce effect when a user scrolls, but this solved it for me
html {
position: relative;
overflow-y: scroll;
}
body {
overflow-y: scroll;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
}
I managed to solve most problems supporting overflow: auto and overflow: scroll on mobile Safari:
without hanging the scroll view after touching at the beginning of list, then moving down and then up (mobile Safari runs its default bouncing behavior for the whole page in that case)
support for fixed header / action bar on top without ugly overlapping of it by a scrollbar
window.addEventListener('DOMContentLoaded', function () {
var atTop = true;
var atBottom = false;
var body = document.getElementById('fixedBody');
body.addEventListener('touchmove', function(event){
event.preventDefault();
});
body.addEventListener('touchstart', function(event){
event.preventDefault();
});
body.addEventListener('touchend', function(event){
event.preventDefault();
});
var scrollingDiv = document.getElementById('scrollDiv');
if (scrollingDiv.scrollHeight <= scrollingDiv.clientHeight) {
atBottom = true;
}
scrollingDiv.addEventListener('scroll', function(event){
if (event.target.scrollTop === 0) {
atTop = true;
} else {
atTop = false;
}
if (event.target.scrollHeight - event.target.scrollTop === event.target.clientHeight) {
atBottom = true;
} else {
atBottom = false;
}
});
var lastY = 0;
var topLock = false;
var bottomLock = false;
scrollingDiv.addEventListener('touchmove', function(event){
event.stopPropagation();
var currentY = event.touches[0].clientY;
if (currentY > lastY) {
// moved down
if (atTop) {
event.preventDefault();
topLock = true;
}
if (bottomLock) {
bottomLock = false;
// TODO: Emulate finger remove and touch again here
}
} else if(currentY < lastY){
// moved top
if (atBottom) {
event.preventDefault();
bottomLock = true;
}
if (topLock) {
topLock = false;
// TODO: Emulate finger remove and touch again here
}
}
lastY = currentY;
});
scrollingDiv.addEventListener('touchstart', function(event){
lastY = event.touches[0].clientY;
event.stopPropagation();
});
scrollingDiv.addEventListener('touchend', function(event){
event.stopPropagation();
});
});
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
</head>
<body id="fixedBody" style="overflow: hidden;">
<div style="position: fixed; height: 64px; width:100%; top:0; left:0; background-color: green; z-index: 1;">
Header
</div>
<div id="scrollDiv" style="position: absolute; width: 100%; top: 64px; bottom: 0px; overflow-y: auto; overflow-x: hidden; -webkit-overflow-scrolling: touch; background-color: white;">
<div style="height: 150px; background-color: blue;">
First
</div>
<div style="height: 150px; background-color: red;">
Second
</div>
<div style="height: 150px; background-color: green;">
Third
</div>
<div style="height: 150px; background-color: black;">
Another
</div>
</div>
</body>
</html>
The only caveat I have is that when user touches and starts moving down and then up, nothing happens (expected: the list should scroll down). But at least the method prevents "pseudo scrolling down" and not confuses user.
To overcome that last problem, it's necessary to emulate a touch end and then touch start event when direction changed (I've put "TODO" comments).
Update: it's possible to avoid using JavaScript code fix on iOS Cordova with DisallowOverscroll = true and WKWebView.
You just need to add a CSS property overscroll-behavior: none to your body
body {
overscroll-behavior: none
}
More details are in this article
I tried all CSS solutions to no avail. Either iOS still bounced certain divs when it reached the top/bottom or scrolling itself became really unreliable and buggy when working with fixed positioning. And so I came up with a JavaScript solution which is really quite simple that seems to have solved it for me:
function onTouchStart(e) {
// Save position of touch
console.log("touchstart");
const touch = e.touches[0] || e.changedTouches[0];
window.lastY = touch.pageY;
}
function onTouchMove(e) {
console.log("touchmove");
// Check user isn't scrolling past content. If so, cancel move to prevent ios bouncing
const touch = e.touches[0] || e.changedTouches[0];
y = touch.pageY;
if (y < window.lastY && e.srcElement.scrollTop == (e.srcElement.scrollHeight - e.srcElement.clientHeight)) {
console.log("user is trying to scroll down without anywhere to scroll to. Canceling propagation.");
e.preventDefault();
} else if (y > window.lastY && e.srcElement.scrollTop == 0) {
console.log("user is trying to scroll up without anywhere to scroll to. Canceling propagation.");
e.preventDefault();
}
};
document.addEventListener("touchstart", onTouchStart, { passive: false });
document.addEventListener("touchmove", onTouchMove, { passive: false });
For the ionic users -
a simple solution for me was using slot="fixed" on the child div of ion-content.
<ion-content>
<div slot="fixed" />
</ion-content>
css overscroll-behavior is now supported in iOS 16. If you are targeting > iOS 16 devices, to prevent the iOS bounce effect in Safari when a web page content is larger than the viewport, add the following CSS to the html root
html {
overscroll-behavior: none;
}
This is tested in Safari and WKWebview in iOS 16.
Related
I noticed a strange behavior with my fixed position header in Chrome on Android when embedding Instagram posts on my page: the fixed header is temporarily resized slightly wider while Instagram's embeds.js is processing the page to add the post contents.
The animated gif below shows the effect on a minimalistic page: the right edge of the blue-bordered header momentarily jumps to the right whenever I click the "Show Instagram post" button.
You can see the demo source code on CodePen, but here it is too:
HTML:
<header>
<span>Left</span>
<span class="right">Right</span>
</header>
<button class="show-button">Show Instagram post</button>
<div id="instagram-post"></div>
CSS:
header {
position: fixed;
top: 0;
left: 0;
right: 0;
border: 10px solid blue;
}
.right {
float: right;
}
.show-button {
font-size: 24px;
margin: 3rem 0 1rem;
}
JavaScript:
$('.show-button').click(function () {
$.ajax({
url: 'https://api.instagram.com/oembed/?omitscript=true&url=https://www.instagram.com/p/tsxp1hhQTG',
dataType: 'jsonp',
success: function (response) {
$('#instagram-post').html(response.html);
instgrm.Embeds.process();
}
});
});
jQuery and Instagram's embeds.js are also included on the page for the demo to work.
Some more observations:
I've only seen this behavior in Chrome on my Android device as well as in Chrome's DevTools when emulating a phone-sized device, not in desktop Chrome (even with a small window)
This doesn't happen in Firefox (neither on Android or in Firefox's DevTools' Responsive Design Mode)
This doesn't happen if I change the header from position: fixed to position: absolute
This also doesn't happen if I remove the call instgrm.Embeds.process(); (but then the post is not fully rendered)
What could explain this behavior? Since the fixed position element should take its dimensions from the viewport, is it possible that the viewport width is temporarily changed by Instagram's JavaScript code?
UPDATE: I noticed that adding width: 100vw; or max-width: 100vw; to the header (along with box-sizing: border-box;) seems to solve the problem, the header's width doesn't change. So looks like the header temporarily expands outside the viewport for whatever reason.
I'm looking at implementing the solution here:
http://luxiyalu.com/scrolling-on-overlay/
When I test out the demo in firefox (emulating mobile), everything seems to work.
When I try to implement into my site (which uses jquery mobile), the body underneath scrolls.
The way I am approaching it for now is:
<body>
<div class="overlay">
<div class="overlay-content">
put popup/overlay stuff here
</div>
</div>
<div class="background-content">
<div data-role="page">
put normal page stuff here
</div>
</div> <!-- background-content-->
</body>
Is there something that jquery mobile does that is causing the body to scroll? I'm surprised it works at all in the demo (without some type of fixed positioning for the body). But I only know a little CSS so it's hard for me to troubleshoot.
Thanks!
Edit 1:
Wow, i can be so stupid, now i know it's never a good idea to turn your brain off and copy-paste without looking at something.
The author's page had the CSS listed as:
.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;
}
}
The brackets were messed-up and so the browser wasn't interpretting the css properly. Notice the close bracket for .overlay came after .overlay-content.
New Problem:
After fixing this, the overlay showed and scrolled until it hit the bottom of the overlay, then the body underneath picked-up the scrolling. Terence-Hill's answer actually fixed that issue for me for android's built-in browser. it stopped the body scrolling. however, the body scroll issue still seems to be occurring 1) ios safari 2) ios app using webview 3) android app using webview.
any clue why it would work in android default browser but not the other 3?
Try adding:
body:not(.hide-overlay) {
overflow: hidden;
}
if you want smooth scrolling on ios use:
.overlay {
overflow-y: scroll; /* has to be scroll, not auto */
-webkit-overflow-scrolling: touch;
}
I have a problem hiding the scrollbar on my page but I still want to be able to scroll.
I know I can use
::-webkit-scrollbar {
display:none;
}
, but its obviously not working on Firefox or other non-webkit browsers.
Ive read a lot of threads explaining how can I accomplish this, but i just cant get it to work.
Can someone help me with this?
Website: http://test.6f.sk/
html {
overflow: -moz-scrollbars-none;
}
or any other element where you want to disable scrollbars
The best answer I know of is to just position the scrollbars out of view:
.crop {
width: 300px;
overflow-x: hidden;
}
.scroller {
width: 300px;
height: 200px;
overflow-y: scroll;
/* Must be ≥ scrollbar width. Scrollbar width varies by
* browser, OS, and config, so don't be precise, choose
* something wider than any scrollbar.
*/
padding-right: 50px;
}
<div class="crop">
<div class="scroller">
1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>10<br>
11<br>12<br>13<br>14<br>15<br>16<br>17<br>18<br>19<br>20<br>
</div>
</div>
Also worth noting that ::-webkit-scrollbar { display: none; } produces buggy behavior in Safari (current version 11.0.1) when used to hide a horizontal scrollbar on a page with an enabled Back button.
I just encountered a strange issue on ie11. I am trying to create a fixed element that will scroll along with window scroll.
$(window).scroll(function() {
var scrollY=$(this).scrollTop();
$('.myelem').css('transform', 'translateY(' + scrollY + 'px)');
});
I have also created a fiddle of this:
https://jsfiddle.net/fyngwnz6/1/
(This is for replicating the issue, I know this particular case could be solved with a fixed element)
The code works flawlessly with no performance issues on every browser, except ie11. When using the scrollbar 'myelem' element scrolls with just a small jitter which becomes more obvious when using the mouse wheel. However, where you can really see the issue is when using the scrollbar buttons. It seems like the render of the scrolling has to finish in order for js to track the scroll.
I saw that there were issues with ie11 and smooth scrolling, but this is not the case here. Is there any kind of solution to this? Am I missing something?
edit: although I have an answer that seems to solve the issue, actually what I am looking for is a solution to elements that have overflow:hidden applies on them and the scroll is taken from an overflown element rather than body scroll; a similar scenario can be found here:
http://www.fixedheadertable.com/
If 'fixed column' is enabled in the example, then clicking on the scrollbars shows the jerkiness in the movement.
It seems like adding height: 100%; and overflow: auto; to the html, body elements removes the IE 11 issue:
JsFiddle Demo
[Edit]: Adding margin: 0; removes double scrollbars.
for edge use:
/*Edge - works to 41.16299.402.0*/
#supports (-ms-ime-align:auto)
{
html{
overflow: hidden;
height: 100%;
}
body{
overflow: auto;
height: 100%;
position: relative;
}
}
/*Ie 10/11*/
#media screen and (-ms-high-contrast: active), (-ms-high-contrast: none)
{
html{
overflow: hidden;
height: 100%;
}
body{
overflow: auto;
height: 100%;
position: relative
}
}
I'm working on a mobile website which has "pages" that have div's which take up the screens full size and you can scroll between each one. The problem is, the window resizes whenever a user scrolls downward because the address bar hides. This causes problems when you scroll to the complete bottom and the address bar then hides.
Is it possible to have the address bar always show on mobile devices?
You can wrap your HTML with div and do something like this: http://jsfiddle.net/DerekL/Fhe2x/show
$("html, body, #wrapper").css({
height: $(window).height()
});
It works on Android and iOS.
The simplest way to achieve this is to scroll in a container, rather than scrolling the document.
E.g.:
<html><body>
<div id="scrollable-content"> ... all your content here ... </div>
</body></html>
html, body {
height: 100%;
}
#scrollable-content {
height: 100%;
overflow-y: scroll;
}
Are you looking at iPhone? I don't know about Android, but on iOS 7 for iPhone it's not possible. One thing you could do is use minimal-ui to have an always-minimized navigation bar, keeping a consistent size for the window:
<meta name="viewport" content="width=device-width, minimal-ui">
http://visuellegedanken.de/2014-03-13/viewport-meta-tag-minimal-ui/
I hope that helps!
Since this problem arises on mobile so use media query and put the position of your body and html as "fixed", Now, you will be see that the search bar does not hide. But for pages which have to be scrolled it will not scroll.
To solve that, use overflow-y: scroll;
#media only screen and (max-width: 500px){
/* to prevent auto hiding of the search bar */
body, html{
height: 100%;
position: fixed;
width: 100%;
overflow-y: scroll;
}
}