48

I've been working on a mobile webapp as of late. I'm optimizing mobile-first, focusing specifically on iOS for the iPhone right now. I don't want the precise look of a native app, but I think that the feeling of being native is of absolute importance. I've made the markup and CSS to reflect this idea, leaving me with this (annotated to better understand the problem I'm having later on):

Webapp markup

This all works problem-free, and it also has the advantage of having a native feel, with static header and footer, and a scrollable inner view (thanks to -webkit-overflow-scrolling: touch).

As anyone who has used iOS for more than 5 minutes will know, when you scroll up or down, you get some nice momentum scrolling. Also, when you hit the top of a list, you get a nice 'bounce' effect:

List bouncing in Settings.app

I feel that this helps to define the feeling of iOS, and such a small detail can go a long way. Luckily, when you are below the top of a list in a scrollable element in a webapp, and scroll past the top, you get the same effect. This is the desired behavior in action:

Scrolling down and up in a webapp produces this bouncing effect

However, when you are at the top of the list, and try to recreate the same bouncing behavior a the top of the list (à la Setting.app, seen above), we get the following behavior, which is not desired: Scrolling down produces the bouncing effect for the entire chrome of the app

I've seen some similar questions on Stack Overflow, but these all opt for disabling bouncing. I'm wondering if it's at all possible to keep bouncing, but always make it occur on body section section#main, not on webapp's chrome. I'm not using jQuery, so I'd prefer answers to be in straight-up JavaScript (bonus points for a CSS solution, though).

Here's a GitHub repo with all of the code (Sinatra, HAML, and Sass; current branch is so), or a JSFiddle with broken images and links, but shows the issue in question on an iPhone (best to add to homescreen to test).

Community
  • 1
  • 1
Kyle Lacy
  • 2,278
  • 1
  • 21
  • 29
  • 3
    A CSS solution would be ideal. I really want a good answer to this question, so I'm going to add a bounty to it. – LandonSchropp Jan 08 '13 at 05:33
  • Did you have a look at [jQtouch](http://www.jqtouch.com/) already? I use an older version of the [Datazombies](https://github.com/DataZombies/jQTouch) for almost 2 years now, and it looks exactly like what you are asking for. If that is kind of overkill, you can take a look at [iScroll](http://cubiq.org/iscroll-4) – bart s Jan 08 '13 at 06:59
  • @barts I just checked out jQtouch, and it doesn't seem to rectify the issue at hand. It might just be me, but I tried the demo on my iPhone, and it seemed to have the same issue. Also, I've seen iScroll before, and it's always felt 'off' to me. The momentum logic is always a *liiiitle* bit off. Even if it were 1:1 with iOS' logic, it wouldn't feel right on other platforms. – Kyle Lacy Jan 11 '13 at 23:16
  • I have posted a CSS solution for this here: http://stackoverflow.com/questions/15732466/mobile-overflowscroll-and-overflow-scrolling-touch-prevent-viewport-bounce/18827652#18827652 The key was to wrap the content in three -webkit-overflow-scrolling: touch divs. I have tested the solution in iOS7 GM too. – romiem Sep 16 '13 at 12:15

4 Answers4

22

OLD INFO: I've solved this: http://www.hakoniemi.net/labs/scrollingOffset/nonbounce.html

NEW INFO: This is now a jQuery plugin that can be found from here: http://www.hakoniemi.net/labs/nonbounce/.

There are several issues, like losing the zooming capability when this is applied or it's dynamic updating isn't working fluently.

But now the simplest way is to define: <div class="nonbounce">...</div> and then $.nonbounce();

Samuli Hakoniemi
  • 18,740
  • 1
  • 61
  • 74
  • 2
    If I'm not mistaken, your small library opts for **disabling** scrolling when going past an element's 'boundaries'. Sorry if it wasn't clear enough, but I was asking for a means of **allowing** the rubber-banding on an element, but not on the body. – Kyle Lacy Jan 11 '13 at 23:11
  • This does seem to work. Can you go into a little more detail about how you accomplished this? – LandonSchropp Jan 13 '13 at 23:37
  • Correct me if I'm not understanding your approach correctly. It looks like you're disabling `touchstart` events on whatever element is passed into the `nobounce` function. Why are you also disabling `touchmove` events when the content is at the top and bottom? – LandonSchropp Jan 13 '13 at 23:48
  • Did you ever post any more information on the topic? Of the solutions I've seen, this seems to be the best. – Kyle Lacy Jan 19 '13 at 06:29
  • @zvona Great. Though one issue remains: If the scrollable content is on top, and we try to scroll up again to make the "scrollable content" bounce (not the body) nothing happens. But this might be a os thing. Though, I think it could be enabled by simply telling the list to set itself at 1px when scrolling to the top (instead of 0px). Then the list could still be bouncy, right? Maybe room for an update ? =D – cocoa coder Apr 17 '13 at 17:50
  • Would it also be possible to make this work by class, not only id? – cocoa coder Apr 17 '13 at 18:08
  • I'll keep that in mind and will refactor + update this when I have time. – Samuli Hakoniemi Apr 18 '13 at 07:47
  • The answer for "touchmove" / "disabling the scrollable content bounce" is that the document/window bounces if one tries to scroll up when on top of the content or when on bottom tries to scroll down. – Samuli Hakoniemi Apr 18 '13 at 19:57
  • 1
    This looks like a good idea, but when I implement it it just disable scrolling everywhere. I don't know if I'm doing something wrong. – marcgg May 09 '13 at 22:23
  • @zvona Hi, I'm looking at using this plugin, does exactly what I need it to. But it seems to disable the bouncing on the `div` I want scroll able (so its just a plain scroll). On the demo on the site it seems to bounce. Any ideas? – Ruddy May 20 '14 at 09:50
  • Here's the link to the actual js file: https://gist.github.com/justVitalius/7143040#file-nonbounce-js – whyoz Jun 04 '15 at 19:31
  • Maybe I did something wrong, but this plugin forced me to use two fingers to scroll and prevent single finger scrolling.. – Jacob Raccuia Nov 23 '15 at 04:10
5

I ran into the same problem and came up with this solution:

http://demo.josefkjaergaard.com/stackoverflow/element_scroll/index.html

Basically I prevent the scroll-content from being in its maximum positions.This prevents the body-scroll from being activated.

It works but it does create a little snap at the end of the easing. With a little more work this behavior could probably be hidden by offsetting the content in opposite direction.

  • This got me as the closest so far. Any updates on that 1 2px "snap" that happens? – sheriffderek Feb 17 '14 at 23:59
  • I love this kind of logic. Rather than preventing the result of a condition (e.g., page bounce when a div is scrolled to the top), you prevent the condition. Fantastic. I have tried a few different "solutions". This is the best I have come across. – Alan M. Sep 16 '14 at 20:26
  • Why: document.getElementById("content").scrollHeight - $("#content").height(); vs document.getElementById("content").scrollHeight - document.getElementById("content").height(); or var content = document.getElementById("content") -> and then using the variable? – AGDM Jan 25 '17 at 23:14
1

Here are some useful resources I found on the subject:

philfreo
  • 41,941
  • 26
  • 128
  • 141
  • 1
    Please don't just link to a bunch of other websites. An explanation of what each one is and why it is relevant would be very helpful. Otherwise, you just lead users down a link clicking rabbit hole. – Joseph Yancey May 29 '15 at 18:57
0

How about this pseudo-code:

document.body.addEventListener("ontouchstart", function(event) {
  if(document.getElementById("main").scrollTop > 0) return;
  event.preventDefault();
  event.stopPropagation();
}, false);
Kernel James
  • 3,752
  • 25
  • 32
  • 1
    From what I can tell, this code simply opts for disabling scrolling beyond the top. Sorry if I didn't make it clear enough, but this was the behavior I didn't want. Instead, I was hoping to keep the rubber-banding on the child element (`#main`), but disable it on the `body`. – Kyle Lacy Jan 12 '13 at 18:49