blog.christoffer.me

Hi, my name is Christoffer and I am an Internet addict.

2015-06-10 19:20

Six things I learnt about iOS Safari's rubber band scrolling

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

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

  • 0 My test page
  • 1 Understanding the default behavior
  • 2 Understanding what "-webkit-overflow-scrolling" CSS rule does
  • 3 The user needs to wait until the rubber band effect is finished
  • 4 How to prevent the background page to scroll
  • 5 How to prevent the background page to scroll
  • 6 Remove the rubber band effect to fix additional rendering issues
  • 7 Entire page source

0 My test page

My test page was pretty basic. It consisted of mock data in a static div to represent the "page", while having a fixed div to represent an overlaying "menu" above the "page", looking like this.

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

1 Understanding the default behavior

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

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

However the first problem I noticed was that if you continuously scroll to the very top or bottom, Safari starts to show some rendering glitches, both in the background page and in the menu. I am not sure why, but I think it has something to do with the rubber band feature.

2 Understanding what "-webkit-overflow-scrolling" CSS rule does

The next thing I did was to apply the -webkit-overflow-scrolling CSS rule on the menu, which enables momentum-based scrolling. As seen in the image below, the scrolling keeps going even though I only swipe with my finger.

But it also adds a rubber band effect when scrolling to the very top and bottom of the, as seen below.

3 The user needs to wait until the rubber band effect is finished

During my testing, I also noticed that when the rubber band effect is in progress, the user cannot shift focus on to another element, until the actual rubber band animation is entirely complete.

Below I am first scrolling the background page, and then quickly try and scroll the menu, but I just keep on scrolling the background page.

However, if I wait a short while until the effect finishes, I can then start to scroll the menu.

4 How to prevent the background page to scroll

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">

This kinda works, but more extensive testing still shows that the background page sometimes gets scrolled as well when scrolling to the very top or bottom of the menu.

5 How to prevent the over scrolling to the background page

By applying this fix it is possible to prevent "over scrolling", meaning if an element gets scrolled to the very top or bottom, the scrolling does not continue over to the body, forcing the body to scroll, hence preventing the rubber band effect.

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 it that it prevents a given element by never be at the very top or the very bottom, but only 1 pixel away. If the scrolling never gets to the very top or the very bottom, then there can never be any over scrolling.

6 Remove the rubber band effect to fix additional rendering issues

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

But as you see below, you also loose the smooth scrolling momentum.

7 Entire page source

<!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>

Search the site

About Christoffer

Christoffer is a software and web developer, with primary expertise in the Java, JavaScript and Dart programming languages.

Always interested in learning new and exciting new technologies and solutions within software- and web-development and the Internet, while suffering from the classic "I can't stop thinking" syndrome.

Currently working as a Front End Web Engineer at Netset, while working on his own ideas and projects via his own software company during his spare time.

Please keep in mind that any opinions expressed here are Christoffer's own opinions and does not necessarily reflect those of his employer, or any other companies, organizations, groups or individuals.

Previous posts