Category Archives: iScroll

Creating Slideshows with iScroll 5

05 Jul 2015

iScroll has features that make it useful beyond just scrolling – among many uses it can be used to create slide shows. I created one today at work and am sharing the entire process here. I already had certain parts of this done for other parts of my application so it took me about 10 minutes to get everything wired up. Since this example starts from ground zero it will take a little longer.

Start with your layout

This demo will have four slides and we’ll need them in an unordered list, so we will start with this:

<ul>
	<li><img src="images/image_1.jpg" height="177" width="284" /></li>
	<li><img src="images/image_2.jpg" height="177" width="284" /></li>
	<li><img src="images/image_3.jpg" height="177" width="284" /></li>
	<li><img src="images/image_4.jpg" height="177" width="284" /></li>
</ul>

iFramed below is a page with the above code:

Ok, well, thats a start. We’ll be making a horizontal scroller so we need to style this list into a horizontal line using this CSS:

li {
	list-style-type: none;
	margin: 0px;
	padding: 0px;
	float:left;
}
ul {
	width: 1136px;
	margin: 0px;
	padding:0px;
}

A couple things to note:

  • Notice the UL width – since I’m essentially using it as the “wrapper” for the LI elements I need to set the width to equal the total width of all of the slides so that the list items can layout out in the desired manner.
  • I’m floating the list elements instead of setting their display to “inline-block” to be friendlier with old IE

So then, below is the result (scrolling in the iFramed example is on, thus the horizontal scroll bar):

Setup for iScroll

Next we need to setup everything for iScroll which means that we need to create a wrapper around our horizontal thumbnail list and then style it appropriately. Note these three things:

  1. As mentioned I’ve set the width of the UL element so that its width equals the total width of all of the slides since it is the element that will be scrolled ***inside*** of the wrapping div.
  2. The wrapping div has an id of “slideshow” and is styled such that its “viewport” is the same dimensions as a single slide. Note also that it’s overflow is set to “hidden”. If the UL element didn’t have its width set the wrapping div would have forced each slide to stack which is not what we want – we want the slides to lay in a single row.
  3. Within the CSS note the position:relative lines on both the UL and the #slideshow – these are necessary so that iScroll can do the calculations needed to get everything to work. If you forget about this your iScroll wont work at all.
<head>
<style type="text/css">
	li {
		list-style-type: none;
		margin: 0px;
		padding: 0px;
		float:left;
	}
	
	ul {
                position: relative;
		width: 1136px;
		margin: 0px;
		padding:0px;
	}
	
	#slideshow{
		position: relative;
		height: 177px;
		width: 284px;
		overflow: hidden;
	}
</style>

</head>
<body>
	<div id="slideshow">
		<ul>
			<li><img src="images/image_1.jpg" height="177" width="284" /></li>
			<li><img src="images/image_2.jpg" height="177" width="284" /></li>
			<li><img src="images/image_3.jpg" height="177" width="284" /></li>
			<li><img src="images/image_4.jpg" height="177" width="284" /></li>
		</ul>
	</div>
</body>

The following is the result:

Add iScroll

Unlike prior versions iScroll 5 has its own website from which you can download the library at http://iscrolljs.com/. Below is an example of adding it to a page.

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

Next we need to initialize the iScroll on our “slideshow” wrapper. We can do so once the page has been loaded like so (jQuery users can substitute jQuery’s document.ready instead). More information on initializing iScroll can be found here.

That’s it – we currently have a functional iScroll – not yet a slideshow, but we’re getting there. Below is the javascript that made it happen followed by the iFramed example.

Of note here is the iScroll configuration object found on line 7. The scrollX property’s “true” value tells iScroll to scroll horizontally.

<script type="text/javascript" src="js/iscroll.js"></script>
<script type="text/javascript">
  ;(function(ns){
	var _iscroll = null;
	ns.init = function(){
		_iscroll = new IScroll('#slideshow',{
			scrollX: true
		});
	}
  })(this.slideshow = {});
  (function(){
	if(window.addEventListener){
		window.addEventListener('load',slideshow.init,false);
	} else{
		window.attachEvent('onload',slideshow.init); //IE
	}
  })();
</script>

Swipe with your finger or your mouse to see the iScroll work.

Configuring iScroll

Ok, now we start getting into the things that will make our slideshow really work. First, lets make iScroll behave a little differently, lets make it snap to each slide instead of scrolling past slides when you swipe. iScroll has 2 properties that we can pass via a configuration object that will tell it to a) snap to an element and b) not overscroll past the element. They are snap and momentum where the former’s value will be set to li since we are snapping to those elements and for the later we’ll use a value of false as we want to disable overswiping.

The adjusted script:

<script type="text/javascript" src="js/iscroll.js"></script>
<script type="text/javascript">
  ;(function(ns){
	var _iscroll = null;
	ns.init = function(){
		_iscroll = new IScroll('#slideshow',{
			scrollX: true
			snap:'li',
			momentum:false
		});
	}
  })(this.slideshow = {});
  (function(){
	if(window.addEventListener){
		window.addEventListener('load',slideshow.init,false);
	} else{
		window.attachEvent('onload',slideshow.init); //IE
	}
  })();
</script>

Give it a try with these new settings:

SPECIAL NOTE: All these examples are iFramed – so if you do a click-drag with a mouse and drag outside of the iFrame then things get kind of weird. This is normal. The explanation is that since you click-dragged outside of the iFrame itself the window within the iFrame does not know that you released the mouse button. Remember: This is a tutorial with “tightly iFramed” examples. To properly experience these examples try to not be so dramatic with your click-swipes 😉

Add next / previous buttons

Slideshows typically have back and next buttons. Lets add them to our slideshow – we’ll start with some basic layout, we’ll have a left and right button on top of the slide show. Each button is a div and will be positioned over the slideshow on the left and right edges.

Here’s the CSS that accomplishes this followed by the HTML:

	#btnleft, #btnright {
		position:absolute;
		top: 70px;
		height: 40px;
		width: 36px;
		z-index: 100;
		background-size:contain;
	}
	#btnleft{
		background-image: url(images/slideshow_left_arrow.png);
		left: 8px;
	}
	#btnright{
		background-image: url(images/slideshow_right_arrow.png);
		left: 255px;;
	}

And the new html – the button divs are added after the slideshow wrapper:

	<div id="slideshow">
		<ul>
			<li><img src="images/image_1.jpg" height="177" width="284" /></li>
			<li><img src="images/image_2.jpg" height="177" width="284" /></li>
			<li><img src="images/image_3.jpg" height="177" width="284" /></li>
			<li><img src="images/image_4.jpg" height="177" width="284" /></li>
		</ul>
	</div>
	<div id="btnleft"></div>
	<div id="btnright"></div>

This is the result:

Add event handlers to the buttons

You should have noticed by now that I’ve made no mention of jQuery thus far and in fact avoided it for the onload event that inits our iScroll. I didn’t want to use jQuery since it would mean using a big fat library to do trivial things. I know everyone likes jQuery so I’ll make a jQuery version that you can download at the end of this article. In the meantime your take-aways should be how to use iScroll not necessarily how to use jQuery for event delegation or whatever. That said, there are more iSrcoll things to learn which we will get to in a moment.

Back on topic, we need to wire up the “back” and “next” buttons. I’ll first wrap everything in a div and then apply the listeners to that wrapping element. The following code does this for us, click the example that follows the code sample to see it work.

The updated init function:

	ns.init = function(){
		_iscroll = new IScroll('#slideshow',{
			scrollX: true,
			snap:'li',
			momentum:false
		});
		// add event listeners
		if (window.addEventListener){
			document.getElementById('slideshow_wrapper').addEventListener('click',ns.doNav,false);
		} else{
			window.attachEvent('onload',slideshow.init); //IE
		}
	};
	ns.doNav = function(e){
		alert('clicked ' + e.target.id);
	};

And the updated HTML with a DIV element with id “slideshow_wrapper” wrapping everything:

	<div id="slideshow_wrapper">
		<div id="slideshow">
			<ul>
				<li><img src="images/image_1.jpg" height="177" width="284" /></li>
				<li><img src="images/image_2.jpg" height="177" width="284" /></li>
				<li><img src="images/image_3.jpg" height="177" width="284" /></li>
				<li><img src="images/image_4.jpg" height="177" width="284" /></li>
			</ul>
		</div>
		<div id="btnleft"></div>
		<div id="btnright"></div>
	</div>

Click the arrows….

What I’ve done is set things to capture the click events as they bubble up the DOM to the event listeners (a technique referred to as event delegation).

Add logic to the navigation buttons

Our buttons will advance the slideshow either forward or backward. To do this we need to be able to determine the direction the user wants to go. If you clicked the navigation buttons in the above example you can see that the alert contained the id of the clicked element, so I’ll use that to determine which button was clicked. Below is the modified doNav function that keys in on the id’s of our buttons:

    ns.doNav = function(e){
	if (e.target.id === 'btnleft'){
	    // move the sideshow to the left
	} else {
	    // move the slideshow to the right
	}
    };

The next thing we need is a way of advancing the slideshow in one direction or another. iScroll provides a method for this purpose called next() to advance the slideshow forward and prev() to advance the slideshow to a previous slide. There is also goToPage() but I’ll stick with the previous two methods – you can of course opt to use the later method to take advantage of the easing property that it exposes.

This is all we need to get the navigation buttons working. See the code below followed by the working example.

    ns.doNav = function(e){
	if (e.target.id === 'btnleft'){
	    iscroll.prev(); // scroll left
	} else {
	    iscroll.next(); // scroll right
	}
    };

Dimming the navigation buttons

One thing that you might think is missing is disabling the buttons in some way to reflect that you are either at the beginning or end of the slideshow. By digging into the iScroll object we can find some properties that will make it easy for us to do this.

First we will need to know what the current slide number is. That information is held in the iScroll object that can be referenced via our private _iscroll variable. Log your iScroll object to the console and note the currentPage property. Expand it and you will see the following:

currentPage:{
    pageX:0, // this holds the slide number for horizontal iScrolls
    pageY:0, // this holds the slide number for vertical iScrolls
    x:-0,
    y:0
}

As you can see the pageX property holds the slide number for horizontal scrollers. Given that fact we would access the current slide number by doing this:

var slideNumber = _iscroll.currentPage.pageX;

Next we need to know how many slides there are. Of course we know that there are 4 but iScroll also holds this for us in the iScroll object’s pages property. Lets save it for later use:

var slideLength = _iscroll.pages.length;

Below is a function that dims the navigation buttons when they are no longer usable due to the slideshow being either at the beginning or end of the slide list (if you are using jQuery you will want to add or remove css classes to the elements instead of the in-line editing that I’m doing):

    // show or dim the navigation buttons
    function _hideShowNavButtons(e){
	var slideNumber = _iscroll.currentPage.pageX;
	var slideLength = _iscroll.pages.length - 1;
	// show or hide the next and previous buttons according to where we are in the slideshow
	document.getElementById('btnleft').style.opacity = slideNumber === 0 ? .3 : 1;
	document.getElementById('btnright').style.opacity = slideNumber === slideLength ? .3 : 1;
    }

To make the above work we need to fire it when the slideshow has stopped scrolling – thus we’ll hook into iScroll’s scrollEnd event. We’ll do this within the init() function which has been modified below:

    // init the app
    ns.init = function(){
	_iscroll = new IScroll('#slideshow',{
	    scrollX: true,
	    snap:'li',
	    momentum:false
	});
		
	// add scrollend event to iscroll
	_iscroll.on('scrollEnd',_hideShowNavButtons); // NEW LINE!! dim or show the buttons on "scrollEnd"
		
	// add event listeners
	if (window.addEventListener){
	    document.getElementById('slideshow_wrapper').addEventListener('click',ns.doNav,false);
	} else {
	    window.attachEvent('onload',slideshow.init); //IE
	}
    };

Lastly, we need to code the “Previous” button so that it is dimmed to begin with. I hardcode that into the init() function via this line (as before use jQuery or your favorite library instead as needed):

// set the default state for the left/previous button
document.getElementById('btnleft').style.opacity = .3;

Here is the result:

We’re done! You could of course add a lot more features such as notes or slide numbering, etc. Right-click and inspect the iFrame holding the final example above or see the complete source printed out below.

<!DOCTYPE html>
<html><head><title></title>

<style type="text/css">
    li {
	list-style-type: none;
	margin: 0px;
	padding: 0px;
	float:left;
    }
	
    ul {
	position: relative;
	height: 177px;
	width: 1136px;
	margin: 0px;
	padding:0px;
    }
	
    #slideshow{
	position: relative;
	height: 177px;
	width: 284px;
	overflow: hidden;
    }
    #btnleft, #btnright {
	position:absolute;
	top: 70px;
	height: 40px;
	width: 36px;
	z-index: 100;
	background-size:contain;
    }
    #btnleft{
	background-image: url(images/slideshow_left_arrow.png);
	left: 8px;
    }
    #btnright{
	background-image: url(images/slideshow_right_arrow.png);
	left: 255px;
    }
</style>

<script type="text/javascript" src="js/iscroll.js"></script>
<script type="text/javascript">
;(function(ns){
    // hold a reference to the iScroll object
    var _iscroll = null;
	
    // init the app
    ns.init = function(){
	_iscroll = new IScroll('#slideshow',{
	    scrollX: true,
	    snap:'li',
	    momentum:false
        });
		
        // add scrollend event to iscroll
        _iscroll.on('scrollEnd',_hideShowNavButtons);
		
	// set the default state for the left/previous button
	document.getElementById('btnleft').style.opacity = .3;
		
	// add event listeners
	if (window.addEventListener){
	    document.getElementById('slideshow_wrapper').addEventListener('click',ns.doNav,false);
	} else {
	    window.attachEvent('onload',slideshow.init); //IE
	}
    };
	
    // handle navigation button clicks
    ns.doNav = function(e){
	if (e.target.id === 'btnleft'){
	    _iscroll.prev(); // scroll left
	} else {
	    _iscroll.next(); // scroll right
	}
    };
	
    // show or hide the navigation buttons
    function _hideShowNavButtons(e){
	var slideNumber = _iscroll.currentPage.pageX;
	var slideLength = _iscroll.pages.length - 1;
	// show or hide the next and previous buttons according to where we are in the slideshow
	document.getElementById('btnleft').style.opacity = slideNumber === 0 ? .3 : 1;
	document.getElementById('btnright').style.opacity = slideNumber === slideLength ? .3 : 1;
    }
	
})(this.slideshow = {});

;(function(){
    if (window.addEventListener){
	window.addEventListener('load',slideshow.init,false);
    } else {
	window.attachEvent('onload',slideshow.init); //IE
    }
})();
</script>
</head>
<body>
    <div id="slideshow_wrapper">
	<div id="slideshow">
	    <ul>
		<li><img src="images/image_1.jpg" height="177" width="284" /></li>
		<li><img src="images/image_2.jpg" height="177" width="284" /></li>
		<li><img src="images/image_3.jpg" height="177" width="284" /></li>
		<li><img src="images/image_4.jpg" height="177" width="284" /></li>
	    </ul>
	</div>
	<div id="btnleft"></div>
	<div id="btnright"></div>
    </div>
</body>
</html>

Managing Multiple iScrolls

28 Jan 2015

i sometimes find myself working on a project where the views require more than a single iScroll. When that happens i always need a method of keeping track of them and destroying them properly as I switch between my views. What I describe here is a simple way to accomplish this.

The first thing to do is to create an array to hold all of my iScroll objects:

;(function(ns){

  // holds all iScroll objects
  var _iScroll = [];

})(this.app = this.app || {});

Next you’ll always need a way to know when the user is interacting with an iScroll so for that purpose I created a boolean variable for my controller to check whenever needed.

;(function(ns){

  // holds all iScroll objects
  var _iScroll = [];

  // is the iscroll scrolling?
  var _isScrolling = false;
  
})(this.app = this.app || {});

And then an iScroll configuration object:

;(function(ns){

  // holds all iScroll objects
  var _iScroll = [];

  // is the iscroll scrolling?
  var _isScrolling = false;

  // iScroll configuration
  var _iScrollSettings = {
    mouseWheel: true,
    scrollbars: true,
    fadeScrollbars:true,
    shrinkScrollbars:'scale'
  }

})(this.app = this.app || {});

With that done I then need a function that will create my iScroll objects and push them into the _iScroll array.

;(function(ns){

  // holds all iScroll objects
  var _iScroll = [];

  // iScroll settings
  var _iScrollSettings = {
    mouseWheel: true,
    scrollbars: true,
    fadeScrollbars:true,
    shrinkScrollbars:'scale'
  }

  /* init_iScroll()
   * Creates any number of iscrolls.
   * @type {Function}
   * @param {String} str - the id of the wrapping element whose contents need to scroll
   * @return {} Returns nothing
   */
  ns.init_iScroll = function(str){
    var len = _iScroll.length;
    _iScroll.push(new IScroll(str, _iScrollSettings));
    _iScroll[len].on('scrollStart',function(){
      _isScrolling = true;
    });
    _iScroll[len].on('scrollEnd',function(e){
      setTimeout(function(){
        _isScrolling = false;
      },500);  
    });
  }

})(this.app = this.app || {});

As you can see within init_iScroll() I feed the CSS ID of the element whose contents must be scrolled to the function. The new iScroll is created and a reference is added to the _iScroll array. Next I configure the scrollstart and scrollend events. Looking at these you can see that when they fire they change the value of the _isScrolling flag. The result is that my application knows when scrolling happens and thus can differentiate between a swipe intended to scroll an iScroll “pane” or a touchend event meant for some other UI element. I of course need to create a getter function to expose _isScrolling to other parts of my app.

Finally, when I create new views I have to completely replace the old with the new which itself may contain its own set of iScrolls. So the first thing I need to do is to be able to correctly remove the existing view’s iScrolls and thus release the memory they consume – failing to do so results in a memory leak so this is an important task. The following function destroys all iScroll objects:

  /* destroy_iScroll()
   * Destroys all iscrolls contained within the _iScroll array;.
   * @type {Function}
   * @param {}
   * @return {} Returns nothing
   */
  ns.destroy_iScroll = function(){
    while (_iScroll.length != 0){
      _iScroll[0].destroy();
      _iScroll[0] = null;
      _iScroll.splice(0,1);
    }
  }

Another aspect of managing iScrolls is that you may want to destroy only specific iScrolls within your views and not just arbitrarily delete all of them. With that use-case in mind we need a means of targeting specific iScrolls – this next function will look for an iScroll object based on the ID of its wrapping element and destroy it:

    ns.findAndDestroyIscroll = function(wrapperID){
        // wrapperID cannot have a leading #
        // see if the iscroll exists, if so, then destroy it and remove from the array of iscrolls
        console.log('before',_iScroll);
        for (var i=0;i<_iScroll.length;i++){
            if (_iScroll[i].wrapper.id === wrapperID){
                _iScroll[i].destroy();
                _iScroll[i] = null;
                _iScroll.splice(i,1);
            }
        }
        console.log('after',_iScroll);
    };

And with that you have everything you could want to manage any situation; a method of keeping track of iScoll objects, creating them, destroying all of them or just destroying specific iScrolls. Below is the complete set of code.

;(function(ns){

  // holds all iScroll objects
  var _iScroll = [];

  // iScroll settings
  var _iScrollSettings = {
    mouseWheel: true,
    scrollbars: true,
    fadeScrollbars:true,
    shrinkScrollbars:'scale'
  }

  /* init_iScroll()
   * Creates any number of iscrolls.
   * @type {Function}
   * @param {String} str - the id of the wrapping element whose contents need to scroll
   * @return {} Returns nothing
   */
  ns.init_iScroll = function(str){
    var len = _iScroll.length;
    _iScroll.push(new IScroll(str, _iScrollSettings));
    _iScroll[len].on('scrollStart',function(){
      _isScrolling = true;
    });
    _iScroll[len].on('scrollEnd',function(e){
      setTimeout(function(){
        _isScrolling = false;
      },500);  
    });
  }

  /* destroy_iScroll()
   * Destroys all iscrolls contained within the _iScroll array;.
   * @type {Function}
   * @param {}
   * @return {} Returns nothing
   */
  ns.destroy_iScroll = function(){
    while (_iScroll.length != 0){
      _iScroll[0].destroy();
      _iScroll[0] = null;
      _iScroll.splice(0,1);
    }
  }

  /* findAndDestroyIscroll ()
   * Destroys specific iScrolls based on the ID of the wrapping element.
   * @type {Function}
   * @param {String} wrapperID - the id of the wrapping element NOT preceded by a hash
   * @return {} Returns nothing
   */
  ns.findAndDestroyIscroll = function(wrapperID){
    // see if the iscroll exists, if so, then destroy it and remove from the array of iscrolls
    console.log('before',_iScroll);
      for (var i=0;i<_iScroll.length;i++){
        if (_iScroll[i].wrapper.id === wrapperID){
          _iScroll[i].destroy();
          _iScroll[i] = null;
          _iScroll.splice(i,1);
        }
      }
      console.log('after',_iScroll);
    };
})(this.app = this.app || {});

I hope you find this useful, happy surfing…..

iScroll 4 Hacks

29 Nov 2012

Here are some hacks that I’ve discovered to get the most out of iScroll. The basic customization parameters are documented at www.cubiq.org’s website but you have to look at the cleanly laid out source to find the gems.

The current list of tips:

  • Improved Lock Direction
  • Faster Snap
  • Snap to a new element and only that element
  • Controlling Scroll Bar Position
  • Enabling SELECT, INPUT and TEXTAREA focus
  • Enabling hidden iScroll divs (where the wrapper div has display:none)
  • The bounce of death – non-scrolling content
  • Disappearing Lists
  • iScroll scrolls up when a scrolled element is clicked

Improved Lock Direction

There will come a time when you have two iScrolls on a page – one vertical and one horizontal. You will find that even if you enable “lockDirection” that iScroll will still be touchy. Try to scroll up and the slightest movement left or right will be caught by the horizontal iScroll. Try to swipe left or right and the slightest up or down movement will be caught by the vertical iScroll. Lock direction provides some improvement so be sure to enable it:

...
var myScroll = myScroll = new iScroll('yourWrapper', {lockDirection:true });
..

But to get even better “lockDirection performance” try the following hacks. Locate lines 470 and 473 within iscroll.js. We’ll want to change a number from 5 to 0.

Line 470 before:

...
if (that.absDistX > that.absDistY - 5) {
...

And line 470 after:

...
if (that.absDistX > that.absDistY - 0) { // yes, you can just remove the math too
...

Do the same with line 473. You should now notice that lockDirection with overlapping iScrolls works better than before.

Faster Snap

To enjoy faster element snapping open iscroll.js and scroll to line 437 where you will find this:

...
that.lastScale = scale / this.scale;
..

Subtract 10 from the lastScale value as seen below:

...
that.lastScale = scale / this.scale - 10;
..

Next, go to line 780 and find this line:

...
var deceleration = 0.0006,
...

Change the deceleration value to 0.002 per the next example:

...
var deceleration = 0.002, // 0.0006
...

Done, your snapping should be much “snappier”.

Snap to a new element and only that element

This tip is pretty cool – and simple. You can prevent “over swiping” in a snap-enabled iScroll by disabling momentum in one of two ways. The first approach is the easiest and most simple – and that is to disable momentum when you initialize iScroll, for example:

...
var myScroll = myScroll = new iScroll('introSliderWrapper', {snapThreshold:.5, momentum:false });
..

Note the “momentum:false” in the above configuration object. Momentum will be turned off.

Ok, so this next way of doing it is a **complete** hack and is really only of geeky interest – but if you look at the iScroll source you will see that there is some old code present that is very similar to the code that precedes it.

The new and old “duplicate” blocks are at lines 589 (new, momentum) and 608 (old, no momentum). Each block of code is surrounded by “if” statements that contain the exact same conditional. The “return” statement at the end of the first “if” block prevents the second “if” from running. It is necessary to comment the entire “if” block that starts at line 590 all the way to and including the return statement at line 605. Be aware that if you do this you are in effect creating a “global” change that will impact all iScrolls that you may employ. As a result, its best to stick with the perfectly functional momentum:false flag.

Anyway, to continue, locate the following code at line 589:

...
    // Do we need to snap?
    if (that.options.snap) {
      distX = newPosX - that.absStartX;
      distY = newPosY - that.absStartY;
      if (m.abs(distX) < that.options.snapThreshold && m.abs(distY) < that.options.snapThreshold) { that.scrollTo(that.absStartX, that.absStartY, 200); }
      else {
            snap = that._snap(newPosX, newPosY);
            newPosX = snap.x;
            newPosY = snap.y;
            newDuration = m.max(snap.time, newDuration);
      }
    }

    that.scrollTo(m.round(newPosX), m.round(newPosY), newDuration);

    if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e);
    return;
  }
...

And with it commented out:

...
    // Do we need to snap?
    /*if (that.options.snap) {
      distX = newPosX - that.absStartX;
      distY = newPosY - that.absStartY;
      if (m.abs(distX) < that.options.snapThreshold && m.abs(distY) < that.options.snapThreshold) { that.scrollTo(that.absStartX, that.absStartY, 200); }
      else {
            snap = that._snap(newPosX, newPosY);
            newPosX = snap.x;
            newPosY = snap.y;
            newDuration = m.max(snap.time, newDuration);
      }
    }

    that.scrollTo(m.round(newPosX), m.round(newPosY), newDuration);

    if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e);
    return; */
  }
...

Done.... when swiping you will no longer swipe past the the adjacent snap-to element. As mentioned, this is done easier by adding the momentum attribute to the iScroll configuration object.

Controlling Scroll Bar Position

Enabling scrollbars in iScroll is easy - making sure they are positioned where you want - easy as well as long as you position your div's appropriately. Even if you run into problems, you can hack the scroll bar into position.

To make scrollbars appear just pass a properly formatted configuration object to the iScroll, like so:

...
new iScroll('scrollWrapper',{
   vScrollbar: true, // the vertical scroll bar has been enabled
   momentum:true
});
...

This will make the vertical scrollbar appear - but in some cases the scrollbar may not be aligned with your scrolling content but aligned along the right-edge of the window. Sure, it may work but if you have a multi-column layout then chances are that you may not want the scroll bar "removed" from the scrolling area. The fix here is simple and achieved via CSS - make sure that your "scrollWrapper" div has its position style set to one of the following:

  1. absolute
  2. relative
  3. fixed
  4. inherit (as long as the inherited value is one of the preceeding three values)

Once the above is done then iScroll will properly place the scrollbar along side your content.

What if you can't change your css or don't want to? Then you can attack it with a little jQuery.

iScroll will place the scrollbar divs within the scroll wrapper but without an id. Since the wrapper does have an id we can use jQuery to get the wrapper's children of which the scrollbar is a member. The last child will always be the scroll bar. For example:

...
window.addEventListener('load',function(){
   new iScroll('scrollWrapper',{ // the scrollbar will be the last child within the wrapper
      vScrollbar: true,
      momentum:true
   });

   // lets get the wrapper's children
   var children = $('#scrollWrapper').children();
   // the children array has a length of two, the last one is always the scrollbar
   // position the scroll bar as desired
   children[1].style.height = 350 + 'px';
   children[1].style.top = 0 + 'px';
   children[1].style.left = 190 + 'px';
},false);
...

As you can see from the above its a simple matter to attack the iScroll scroll bar. If you like you could obviously change this:

children[1].style.height = 350 + 'px';

To this:

children[children.length-1].style.height = 350 + 'px';

So that you **always** get the last child as who knows how you're structuring your content.

In addition to this you may not always have a scroll bar present depending on the length of your content. Therefore, we have to check for its existence in some way. In a typical setup the wrapper will only have a single child if the scrollbar is not there. The following check is based on this assumption:

...
window.addEventListener('load',function(){
   new iScroll('scrollWrapper',{ // the scrollbar will be the last child within the wrapper
      vScrollbar: true,
      momentum:true
   });

   // lets get the wrapper's children
   var children = $('#scrollWrapper').children();
   if (children.length > 1){ // if there is more than one child, the scrollbar must be present
      children[1].style.height = 350 + 'px';
      children[1].style.top = 0 + 'px';
      children[1].style.left = 190 + 'px';
   }
},false);
...

Enabling SELECT, INPUT and TEXTAREA focus

iScroll will disable the ability to give focus to certain form elements preferring instead to treat all interactions with the screen as something that must go through iScroll itself. You can get around this by adding the following function to the onBeforeScrollStart event - shown here as a configuration object while instantiating iScroll. You could also just add it to the iScroll source - view source, scroll down until you see the onBeforeStartEvent and add the function there.

...
var scroll = new iScroll('content',
   {scrollbarClass: 'myScrollbar',
    handleClick: false,
    useTransform:true,
    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();
	}
    });
...

Enabling hidden iScroll divs

This isn't a hack per se as iScroll has a method to deal with the issue. In cases where you instantiate an iscroll on a hidden wrapper div and then reveal the wrapper by changing its display style to something other than "none" you will find that the iScroll-ed div will just bounce in its wrapper, refusing to scroll.

The fix is to do the following **after** you have revealed the wrapper - the example uses the refresh and scrollTo methods to enable scrolling:

...
// instantiate a new iScroll
var myScroll = new iScroll('myDiv');

function reveal_iScroll_div(){
   document.getElementById('myDiv').style.display = 'block';
   myScroll.refresh();
   myScroll.scrollTo(0,0,0);
}
...

...

The bounce of death - non-scrolling content

Sometimes your content will just bounce in its container and not scroll at all. There are a couple things that cause this - 1 of them is the section preceding this one, the other is likely to be a simple line of CSS. Make sure that the div that is wrapping your content has a css display value of "block".

Disappearing Lists

Sometimes you'll have an iScroll whose contents are updated according to whatever the user has selected. If you have a long list and quickly scroll through them - and then try to update the contents of the scroll wrapper while your list items are flying by you may find that the list may update with new content but not be visible. That is, if you inspect the DOM you plainly see that your elements are there correctly positioned but they do not display (observed in Android 4.1.2).

It appears that we may need to stop the scrolling before updating the list. Something like this:

...
if (_myScroll){
    _myScroll.scrollTo(0,0,0);
}
// now update the list
...
// _myScroll.refresh() might not work here, the "scrollTo" before the update seems to help
...

iScroll scrolls up when a scrolled element is clicked

(7/15/2016) I just had this particular issue that took me quite a while to figure out - I had inited an iScroll with content that appeared to function correctly, but no matter what buttons I clicked in my scrolled content the result was a) what I programmed the button to do, and b) the iScroll would scroll back to the top (interestingly the scroll bar would NOT scroll to the top).

Turned out that I had instantiated the iScroll twice in quick succession on the same variable - maybe creating some sort of race condition? You would think that one would overwrite the other... but fixing the code so that I created an iScroll instance only once resolved the issue.