iPhone Scrollbars with iScroll

By  on  

Since we've had web browsers and JavaScript, we've been intent on replacing native browser functionalities, and for many reasons. Whether it be that the native look or functionality is ugly, doesn't work the same across browsers, or isn't as feature-rich as it should be, we've always pushed the browser's limits to do better. One functionality we've tried desperately to emulate is scrollbars. iScroll, a fairly new scrolling lib, has done an exceptional job in emulating scrolling both within desktop and mobile browsers. iScroll also allows for scrolling of elements with overflow on older versions of Mobile Safari. Let's have a look at iScroll!

The HTML

iScroll requires a two-DIV pattern for declaring where iScroll will be used. The first DIV is the wrapper, the second DIV is the scrollable area:

<div id="wrapper">
	<div id="scroller">
		<div style="padding:15px 30px;"> <!-- padding for content -->
		
			<!-- content goes here -->
			
		</div>
	</div>
</div>

iScroll will create and inject the scrollbar within the wrapper DIV. The content is held within the scroller DIV.

The CSS

The CSS is where iScroll can get a bit fuzzy. For iScroll to work optimally, both the wrapper and scroller DIVs need to be positioned absolutely and be styled to widths of 100%:

#wrapper {
	position:absolute;
	z-index:1;
	top:0; 
	bottom:0; 
	left:0;
	width:100%;
	overflow:auto;
}

#scroller {
	position:absolute; z-index:1;
/*	-webkit-touch-callout:none;*/
	-webkit-tap-highlight-color:rgba(0,0,0,0);
	width:100%;
	padding:0;
}

As a result, the third DIV of the structure needs the contain enough padding-right to keep the text and scrollbar far enough way from each other. Be sure to position those elements properly or iScroll wont work at all (as I found out the hard way)!

The JavaScript

The most obvious piece of using iScroll is including its .js file:

<script type="text/javascript" src="iscroll/src/iscroll.js"></script>

With iScroll now available within the page, the next step is creating the iScroll instance that suits the needs of your desired usage. The most simple of uses provides only the wrapper ID:

var scroller = new iScroll('wrapper');

Awesome; the page's native scrollbar disappears and is replaced by an iOS-style scrollbar! But like every good JavaScript lib, iScroll provides a host of features that allow you to customize your scrollbar. Options include setting flags for bounce, momentum, fade and hide settings, and whether scrollbars should be allowed both vertically and horizontally. Here's another example of how you can create a pull-to-refresh scrollbar:

var myScroll,
	pullDownEl, pullDownOffset,
	pullUpEl, pullUpOffset,
	generatedCount = 0;

function pullDownAction () {
	setTimeout(function () {	// <-- Simulate network congestion, remove setTimeout from production!
		var el, li, i;
		el = document.getElementById('thelist');

		for (i=0; i<3; i++) {
			li = document.createElement('li');
			li.innerText = 'Generated row ' + (++generatedCount);
			el.insertBefore(li, el.childNodes[0]);
		}
		
		myScroll.refresh();		// Remember to refresh when contents are loaded (ie: on ajax completion)
	}, 1000);	// <-- Simulate network congestion, remove setTimeout from production!
}

function pullUpAction () {
	setTimeout(function () {	// <-- Simulate network congestion, remove setTimeout from production!
		var el, li, i;
		el = document.getElementById('thelist');

		for (i=0; i<3; i++) {
			li = document.createElement('li');
			li.innerText = 'Generated row ' + (++generatedCount);
			el.appendChild(li, el.childNodes[0]);
		}
		
		myScroll.refresh();		// Remember to refresh when contents are loaded (ie: on ajax completion)
	}, 1000);	// <-- Simulate network congestion, remove setTimeout from production!
}

function loaded() {
	pullDownEl = document.getElementById('pullDown');
	pullDownOffset = pullDownEl.offsetHeight;
	pullUpEl = document.getElementById('pullUp');	
	pullUpOffset = pullUpEl.offsetHeight;
	
	myScroll = new iScroll('wrapper', {
		useTransition: true,
		topOffset: pullDownOffset,
		onRefresh: function () {
			if (pullDownEl.className.match('loading')) {
				pullDownEl.className = '';
				pullDownEl.querySelector('.pullDownLabel').innerHTML = 'Pull down to refresh...';
			} else if (pullUpEl.className.match('loading')) {
				pullUpEl.className = '';
				pullUpEl.querySelector('.pullUpLabel').innerHTML = 'Pull up to load more...';
			}
		},
		onScrollMove: function () {
			if (this.y > 5 && !pullDownEl.className.match('flip')) {
				pullDownEl.className = 'flip';
				pullDownEl.querySelector('.pullDownLabel').innerHTML = 'Release to refresh...';
				this.minScrollY = 0;
			} else if (this.y < 5 && pullDownEl.className.match('flip')) {
				pullDownEl.className = '';
				pullDownEl.querySelector('.pullDownLabel').innerHTML = 'Pull down to refresh...';
				this.minScrollY = -pullDownOffset;
			} else if (this.y < (this.maxScrollY - 5) && !pullUpEl.className.match('flip')) {
				pullUpEl.className = 'flip';
				pullUpEl.querySelector('.pullUpLabel').innerHTML = 'Release to refresh...';
				this.maxScrollY = this.maxScrollY;
			} else if (this.y > (this.maxScrollY + 5) && pullUpEl.className.match('flip')) {
				pullUpEl.className = '';
				pullUpEl.querySelector('.pullUpLabel').innerHTML = 'Pull up to load more...';
				this.maxScrollY = pullUpOffset;
			}
		},
		onScrollEnd: function () {
			if (pullDownEl.className.match('flip')) {
				pullDownEl.className = 'loading';
				pullDownEl.querySelector('.pullDownLabel').innerHTML = 'Loading...';				
				pullDownAction();	// Execute custom function (ajax call?)
			} else if (pullUpEl.className.match('flip')) {
				pullUpEl.className = 'loading';
				pullUpEl.querySelector('.pullUpLabel').innerHTML = 'Loading...';				
				pullUpAction();	// Execute custom function (ajax call?)
			}
		}
	});
	
	setTimeout(function () { document.getElementById('wrapper').style.left = '0'; }, 800);
}

Since we live in the world of AJAX-driven websites that allow content to come and go, calling the refresh method is all you need to do for iScroll to reevaluate the scrollbar position and size:

// When the AJAX is done, refresh the scrollbar sizing and positioning...
scroller.refresh();

It's also important to point out that iScroll allows zooming and pinching, as well as snapping to elements:

var myScroll = new iScroll('wrapper', {
	/* snap: true, */ 		// Would snap logically
	snap: "p",				// Snaps to each "P" tag
	momentum: false,
	hScrollbar: false,
	vScrollbar: false 
});

Lastly, iScroll-Lite is available for those looking to support only mobile browsers (iScroll allows for desktop support as well). The mischievous part of me would prefer iOS-style scrollbars instead of native browser scrollbars!

Possibly my favorite part of iScroll is that it's a standalone library; no external JavaScript library is required. iScroll has many configuration parameters so I encourage you to visit the iScroll page to check out everything you can do. Matteo Spinelli's iScroll is an outstanding piece of work; grab iScroll and start controlling your scrollbars today!

Recent Features

Incredible Demos

Discussion

  1. Nice trick. There are some bugs in it though:
    1- in the desktop version, if you scroll inside a scrollable element (not the page itself), the element and the page scroll, when only the element should and the page should hold still.

    2- if you really want the iPhone feeling, the scrollbar is only visible when in use.

    Anyway. Nice JOB!

  2. Nice trick!
    I agree with Bogdan.
    well done!

  3. shams

    amazing david ;)

  4. This is REALLY CPU intensive in Opera, and makes scrolling really unusable. The mobile version works though.

  5. As a side note, iScroll isn’t really needed now with iOS5 as overflow:scroll is now supported by adding -webkit-overflow-scrolling: touch;

  6. For people using Webkit you can also use this technique:

    http://beautifulpixels.com/goodies/create-custom-webkit-scrollbar/

    You can also try to cover it:

    http://mootools.net/forge/p/scrollbarcover

    (It does not work very well in Opera)

    Also one could always steal Facebook JS Scrollbar.

  7. Hi!

    My problem with iScroll is when you have inputs in the screen and you want to enter focus inside one with touch. iScroll captures that touch and disables the focus. Fails with inputs, selects…

    Anyone with the same problem? Any solution?

    Regards

  8. Ok, I find a solution :D

    onBeforeScrollStart: function (e) {
    			var target = e.target;
    			while (target.nodeType != 1) target = target.parentNode;
    
    			if (target.tagName != 'SELECT' && target.tagName != 'INPUT' && target.tagName != 'TEXTAREA')
    				e.preventDefault();
    		}
    
  9. Very interesting article, will definitely use this scroll bar in my projects

  10. César

    Hello all.
    I try to change:

    li.innerText = 'Generated row ' + (++generatedCount);
    

    to

    li.innerHTML = ' Generated row ';
    

    The element is added, but the iScroll doesn´t work. The iScroll has two vertical scrollers, the scroller return to top before ends.
    What´s the problem??
    Thanks in advance.

  11. israelpiz

    Hi! well, I´m developing a web app on dreamweaver, and implements iscroll in an special div, so, y tested on mi browser and works perfect, but on the ipad emulator, my scroll desapear after the page load, on code after the pageChange I refresh my scroll,

    any aidea?

    thanks.

  12. Nice, really helpfull.. :)

  13. Thanks very much! I found iScroll’s documentation lacking, but your example was very helpful. Love your blog. Thanks Kindly!

  14. Hiren Mehta

    I’v integrated script, containers which is required to scroll that’s working but iPad default scrolling, zoom-in & zoom-out has been stopped.

    Please advice solution.

  15. It is really helpful. I only want to know how we can auto scroll the div to the end position ?

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!