Professional Internet Addict • Game Enthusiast • Tech Creator
Professional Internet Addict • Game Enthusiast • Tech Creator

Six Things I Learned About iOS Safari's Rubber-Band Scrolling

In order to fix a scrolling problem in Safari on iOS, I was forced to create my own simple test page!
Home Blog Six Things I Learned About iOS Safari's Rubber-Band Scrolling

Please note that this blog post was published on June 2015, so depending on when you read it, certain parts might be out of date. Unfortunately, I can't always keep these posts fully up to date to ensure the information remains accurate.

In order to fix a recent scrolling problem in Safari on iOS, I was forced to create my own simple test page and then apply all the information and combinations I could find on the internet to try to resolve the issue.

Below is a brief overview of what I discovered during this process.

My Test Page

My test page was fairly basic. It consisted of mock data in a static div to represent the "page", along with a fixed div to represent an overlaid "menu" above the page, like this.

A screenshot of an opened HTML side menu on iOS Safari

The full source code for the page can be found at the bottom of this blog post.

Understanding the Default Behavior

The first thing I did was try to understand the default behavior of Safari on iOS. The most notable observation was that when you scrolled all the way to the bottom of the menu, the page's body exhibited a "rubber-band effect" - a hard-coded feature in Safari.

an animated screenshot of the problematic rubber band effect in ios safari at the bottom of the page

The same thing happened when scrolling to the very top of the menu.

an animated screenshot of the problematic rubber band effect in ios safari at the top of the page

However, the first problem I noticed was that if you continuously scroll to the very top or bottom, Safari begins to show rendering glitches in both the background page and the menu.

I'm not sure why, but I suspect it has something to do with the rubber-band feature.

an animated screenshot of the glitchy ios safari problem at the bottom of the page

Understanding What the "-webkit-overflow-scrolling" CSS Rule Does

The next thing I did was apply the -webkit-overflow-scrolling CSS rule to the menu, which enables momentum-based scrolling.

As shown in the image below, the scrolling continues even after I swipe with my finger.

showcasing the momentum-based scrolling problem on ios safari

But it also adds a rubber-band effect when scrolling to the very top or bottom, as shown below.

showcasing the momentum-based scrolling problem on ios safari with the rubber band effect

The User Needs to Wait Until the Rubber-Band Effect Finishes

During my testing, I also noticed that while the rubber-band effect is in progress, the user cannot shift focus to another element until the animation has fully completed.

In the example below, I first scroll the background page and then quickly try to scroll the menu, but I end up continuing to scroll the background page instead.

showcasing that the background page is scrolled instead on ios safari

However, if I wait a moment for the effect to finish, I can then start scrolling the menu.

showcasing the wait for the rubber band effect to finish on ios safari

How to Prevent the Background Page from Scrolling

During my testing, I also noticed that while the rubber-band effect is in progress, the user cannot shift focus to another element until the animation is completely finished.

Based on this answer from Stack Overflow, you can ensure that elements with disable-scrolling do 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 apply the disable-scrolling class to the page div:

<div class="page disable-scrolling">

This kind of works, but more extensive testing shows that the background page can still sometimes scroll when you reach the very top or bottom of the menu.

showcasing that the background still is being scrolled

How to prevent the over scrolling to the background page

By applying this fix, it becomes possible to prevent "overscrolling". This means that when an element is scrolled to its very top or bottom, the scrolling does not continue into the body.

As a result, the body is prevented from scrolling, which also stops the rubber-band effect from occurring.

function removeIOSRubberEffect(element) { element.addEventListener("touchstart", function() { var top = element.scrollTop, totalScroll = element.scrollHeight, currentScroll = top + element.offsetHeight; if (top === 0) { element.scrollTop = 1; } else if (currentScroll === totalScroll) { element.scrollTop = top - 1; } }); } removeIOSRubberEffect(document.querySelector(".scrollable"));

What this JavaScript snippet does is prevent the element from ever reaching the very top or very bottom by keeping it just 1 pixel away.

If the scroll position never reaches the absolute top or bottom, overscrolling can never occur.

demo of preventing overscrolling by never going to the very top or to the very end

How to Prevent Overscrolling to the Background Page

If you want to remove the rubber-band effect from the menu-either for aesthetic reasons or because, as in my case, it caused additional rendering glitches-simply remove the -webkit-overflow-scrolling CSS rule.

However, as you can see below, you also lose the smooth scrolling momentum.

full demo if preventing rubberbanding on ios safari

Entire Page Source Code

Here is the full source code for the page.

<!-- License MIT, Author Special Agent Squeaky (specialagentsqueaky.com) --> <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="minimum-scale=1.0, width=device-width, maximum-scale=1.0, user-scalable=no, initial-scale=1"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <style> .page{ font-size: 24px; overflow: scroll; } .menu{ position: fixed; top: 0; bottom: 0; left: 0; width: 80%; background: gray; z-index: 1; font-size: 10px; overflow: scroll; /* uncomment to get smooth momentum scroll, but also a rubber band effect */ /*-webkit-overflow-scrolling: touch;*/ } .menu-item{ padding: 10px; background: darkgray; font-size: 24px; } </style> </head> <body> <div class="menu scrollable"> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> <div class="menu-item">hello world</div> </div> <div class="page disable-scrolling"> Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. </div> <script> 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(); } }; function removeIOSRubberEffect(element) { element.addEventListener("touchstart", function() { var top = element.scrollTop, totalScroll = element.scrollHeight, currentScroll = top + element.offsetHeight; if (top === 0) { element.scrollTop = 1; } else if (currentScroll === totalScroll) { element.scrollTop = top - 1; } }); } removeIOSRubberEffect(document.querySelector(".scrollable")); </script> </body> </html>

Written by Special Agent Squeaky. First published 2015-06-10. Last updated 2015-06-10.

📺 Watch Squeaky's latest video!

How to a Add Simple Real-Time Subtitles to Your Live Stream