Prevent iOS bounce without disabling scroll ability
Asked Answered
C

11

44

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.

Couthie answered 27/4, 2015 at 11:54 Comment(0)
P
48

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;
}
Ptomaine answered 27/4, 2015 at 11:58 Comment(6)
This fix seems to be outdated. bouncefix.js doesn't seem to work as well.Bierman
If you guys could put the version you are having issues with then it may help with the fix being updated.Ptomaine
any update on the issue? i tried the above code, it doesnt work on ios 9.3.5Aide
works in ios12 with added position: relative to bothHexagon
this worked for me. I had a slider with horizontal scroll that kept causing a slight bounce.Marmite
Woohoo! This was exactly what I needed. The ability to find answers to my problems on SO never ceases to amaze me. I love this community.Jocasta
A
29

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.

Adipose answered 27/6, 2017 at 23:25 Comment(10)
+1 for getting rid of a little vertical wobble that was happening with James Campbell's answer. -1 for non-semi-colons and non-CSS. :(Prestigious
@Prestigious your imagination must be broken! Enjoy the CSS and semi-colons.Adipose
Much obliged! :)Prestigious
I've tried a lot of solutions for this issue without much success, but this one looks pretty darn solid. Thank you!Pickford
Things were looking bleak! :) Same for me, was all out of ideas until I tried this. Was Angular for me, list inside tabs causing scrolling issues for fixed bottom bar. Many thanks...Appointor
this breaks inner fixed elements behavior - they are terribly shaky all the way while scrollingFloats
@Floats thanks for the caveat. I'll add it to the postAdipose
Working on an Angular 7 project. I was testing on an iPod (6. gen) and noticed that the pages was extremely elastic. When swiping from left to write. I could see previous pages behind the current. Not good. This fixed the problem. THANK YOU!!! :-D <3 This is the power of sharing. Saved me so much time.Heresy
@Heresy Thank you for the extra information. Glad it helped you!Adipose
thanks. I've been struggling with that for quite a while.Eliseoelish
H
18

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

Homunculus answered 2/10, 2016 at 20:41 Comment(13)
Thank you so much! This worked like a charm! I was using Inobounce and it was giving me such a headache when combining with different horizontal scrolling groups. With this solution the look and feel in my web app is like a real native app. And even without using javascript hacks... :D Cheers - take my upvote! :) Hint use calc(100% - 0.1px) to avoid actually seeing the 1px. (y)Roue
Jeremy, I like that solution. Very simple. However I run into a problem with it. If the initial scroll position of the content is 0 and I drag DOWN the content there will be no (on ios) bounce effect and the whole scroll state is broken, I can not scroll down nor up. It works only if I initially scroll UP and THEN down. Don't you experience such a problem?Epicedium
@AlexanderJorias I've the same problem. It seems that unfortunately, the header should have page's scrollbar on top of it. Which seems awkward for Cordova apps. :-/Banyan
@IvanBorisenko any solution yet? :)Epicedium
@AlexanderJorias <body 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> </body>Banyan
At least it not hangs in place but moves with the whole page. In order to prevent that, I sure it's possible to parse event params in ontouchmove handler of the scrollable element and disable default processing when the list on the beginning or end.Banyan
@AlexanderJorias see my answer https://mcmap.net/q/370109/-prevent-ios-bounce-without-disabling-scroll-ability.Banyan
@Ivan thanks, I will try. inobounce.js can help to prevent the overscroll to body, however it fails if you want to scroll horizontalle and your content height is the same as the container height, though. In general we run into this problem because we embed a web app via a wkwebview.Epicedium
@AlexanderJorias inobounce.js using almost the same techniques but it was not working for me (I don't need to prevent bouncing of the scrolled list, only removing the scrollbar in order to allow app look as native as possible).Banyan
@AlexanderJorias please see the discussion and my answer on css-tricks.com/forums/topic/…Banyan
You just saved me a whole afternoon of debugging @Jeremy Whitt Real great work!!!!!Arianaariane
@Jeremy Whitt How do I use document.onscroll together with this CSS?Arianaariane
@jeremy check the <body> tag to be opened tag, after </head>Grafting
C
7

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

Coremaker answered 6/10, 2017 at 2:51 Comment(0)
B
3

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.

Bernardo answered 4/12, 2019 at 9:34 Comment(2)
I also had to add width: 100% under html to retain the pre-fixed layout.Postiche
Works for me without the "position" stuffs. Thanks!Banderilla
T
2

You just need to add a CSS property overscroll-behavior: none to your body

body {
  overscroll-behavior: none
}

More details are in this article

Trickle answered 19/11, 2020 at 9:13 Comment(3)
Jan 2021. Had no effect for this tag either on html or body on iPad IOS 14.3, chrome and safari both.Orientalism
iOS still doesn't support it: caniuse.com/css-overscroll-behaviorInstar
It does now, as of September 2022Defroster
G
2

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.

Glasgo answered 14/1, 2023 at 17:50 Comment(2)
It Dose not work in my case here what problem i face drive.google.com/file/d/14vZEBiPn1BYxnpJtWq5nFd5wqJpU29ZF/viewMalcommalcontent
You need to use overscroll-behavior: none; in the parent of overflowing child to resolve the issue.Glasgo
C
1

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;
}
Claraclarabella answered 11/6, 2021 at 8:23 Comment(0)
B
0

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.

Banyan answered 20/1, 2017 at 11:29 Comment(1)
And it looks impossible to enable scrollbars on Android 4.2 for overflow: auto at all. Shame. developersday.wordpress.com/2012/08/07/…Banyan
B
0

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 });
        
Behre answered 12/12, 2020 at 22:14 Comment(0)
E
0

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>
Epaulet answered 27/4, 2021 at 8:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.