ios_scrollview_effect

Introduction

Previously, I’ve written a post on iOS ScrollView Gallery Effect that I’ve re-created through a plugin found here. http://nooshu.com/recreate-iphone-swipe-effect-using-jquery The idea wasn’t as complex as it sounds, but it does require some configurations to pull it off.

Skills required:

  • LESS. Instead of CSS, I’ll be using LESS JS. http://lesscss.org/
  • Basic knowledge of how iOS ScrollView works.
  • Javascript and HTML

The Idea

Ok, so the basic idea of an Image Gallery should be familiar with most people. You throw in a bunch of images, it changes and it also reflect the number of slides there are. But on top of the basic functionality, we wanted to see how we can create the iOS effect. Let’s list down the basic features we’re going to work on:

  • Display slides base on an array of images.
  • Slide indicator that is created dynamically and respond to change of slides.
  • Bouncing effect on the gallery and swiping motions.
  • Adding necessary styles for an IOS look and feel.

HTML Structure

Since we would like to approach a more dynamic structure, our DOM would be really easy and simple.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- Primary View containing scrollable images -->
      <div id="gallery" class="viewWrapper">
        <div id="indicator" class="view">
          <ul class="slides-indicator">
          </ul> 
        </div> <!-- end of slide indicator wrapper -->
 
        <div class="contentSize scrollView">         
          <div class="scrollView-content">
 
          </div> <!-- end of scrollView-content -->
          <div class="tint black"></div> 
        </div> <!-- end of scrollView -->
 
      </div>

The idea is to just have a main outermost wrapper that holds everything, then a holder for all the slide indicators and a content wrapper. If you had worked on iOS before, you would notice how similar the structure is in iOS ScrollView. Content wrapper is the one user will be scrolling and also holding all the images.

Notice that I’ve added a div with “tinting” effect. You don’t really have to follow this, unless you wish to create the look and feel of iOS textured background. We then need to import all the dependency scripts:

1
2
3
4
5
     <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
     <script>window.jQuery || document.write('<script src="js/vendor/jquery-1.9.1.min.js"><\/script>')</script>
     <script src="js/vendor/jquery.overscroll.min.js"></script>
     <script src="js/vendor/easing.js"></script>
     <script src="js/script.js"></script>

These scripts are provided in the source code by the original creator, except for the last script which we’re building it now.

CSS Styles

You might see a ton of other classes and functions that I’ve setup in style.less. You don’t have to follow, as they are just like a bootstrapping template for me to get started quickly. To get started, we define a couple of properties to setup our containers. You can decide your color theme, font family and sizes and resolution, but for this tutorial, I’m applying a 1440 x 900 resolution. (not using any text examples though)

1
2
3
4
5
6
@width: 1440px;
@height: 810px;
 
@fontfamily: 'Helvetica', 'Myriad Pro', 'Georgia';
@fontsize: 14px;
@fontcolor: #eee;

For LESS users, this should look familiar. Basically, I define two sizing properties that I can use throughout the css document. For those who are not familiar with LESS, this is just one of benefits on why you should start using.

If a project requires you to make changes now and then, or you wish to apply the same template for different projects, all you had to do is simply change the values of these properties, and it affects all your stylesheets that inherits them.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
body, html {
  background: #222;
  font-family: @fontfamily;
  font-size: @fontsize;
  color: @fontcolor;
  overflow: hidden;
}
 
body.body {
  position: relative;
  width: @width;
  height: @height;
  overflow: hidden;
}

These should all be pretty straight forward. Only exception is the overflow:hidden property in the body class. Let’s start creating the structure of our scrollView. We first need a tinting for this tutorial, because the copy of texture I had is a grey fabric, while iOS had a darker based fabric. (Again, you don’t have to mimic it if you don’t wish to)

A class .view is used for generic containers that holds some other elements – In iOS, every element is termed as a view. We then proceed on to set the properties of our scrollView container.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.tint { 
  width: @width;
  height: @height;
}
 
div.view {
  display: block;
  width: @width;
  height: @height;
  z-index: 2;
}
 
div.scrollView {
  overflow: hidden;
  -ms-touch-action:none;
  z-index: 2;
}

These are the custom classes that configures the basic structure of our scrollView. If you’d notice, a z-index has been set for both scrollView and view class – I shall explain on this later. Let’s get a general overview of the css first. Again, we use the technique of overflow:hidden to ensure that additional photo slides are hidden from screen.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
div.viewWrapper {
  position: relative;
  display: block;
  z-index: 1;
 
  div.contentSize {
    position: relative;
    width: @width;
    height: @height; 
    background: url('../images/fabric-texture.png') repeat;
 
    div.scrollView-content {
      position: relative;
      height: @height;
      z-index: 1; 
    }
  }
}

Now you can see that z-index has been used again throughout the other classes. This is to adjust the containers in such a manner where the tint can sit nicely over the textured container, but at the same time it does not cover your photo slides as well. A slideshow without indicators is not a complete slideshow right? So, let’s go ahead and implement one.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
div#indicator {
  position: absolute;
  left: 50%;
  bottom: 10px; 
  height: 30px;
  z-index: 3;
 
  ul.slides-indicator {
    position: absolute;
    left: -50px;
    margin: 14px 0 10px 0;   
    height: 15px;
    list-style: none;
 
    li.slide-dot {
      float: left;
      margin: 0 5px 0 5px;
      width: 6px;
      height: 6px;
      .border-radius(30px);
      background: @black;
 
      &.active { background: @white; }
    }
  } //--- end of slide indicator
}

Your indicator, in most cases, would normally be centered-aligned. But in order to center align your indicators, there’s no default way in doing it except for a css trick that is quite commonly used. First, the outer most container has to be position:absolute and manually shift its left offset by 50%. What this means is, your indicators are displayed somewhere in the beginning center of the screen – See screenshot below.

Screen Shot 2013-07-05 at 2.14.13 PM

Next, set your list of indicators with position:absolute as well, and manually set its left to the left side of the screen. That way, instead of your indicators beginning in the center of the screen, it will now be displayed in the center by offsetting its left with the width of your indicators.

All you had to do next is to shape your indicators however you like, and in this case I had them displayed as dots. I also added its active state style, so it has an indication of which slide the user is on. The last but not least, we shall style the wrapper of each slide photos.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
div.scrollView-content div.slide-wrapper {
  position: relative;
  float: left;
  display: block; 
  width: @width;
  height: @height;
  cursor: -webkit-grab;
 
  div.slide {
    position: relative;
    margin: 0 auto;     
    width: 1024px;
    height: @height;
  }
 
  div.slide:first-child { box-shadow: -2px 1px 20px @black; }
  div.slide:last-child { box-shadow: 2px 1px 20px @black; }
 
}

The .slide-wrapper is just a container holding the slide itself, nothing new here. But notice the two properties configured below for the first-child and last-child. What we are trying to achieve here is to create a shadows only on the side end of the scrollView. Have a look at the screenshot below.

ios_scrollview_effect

With that, you’ve successfully completed the first part of creating a custom scrollView! Let’s move on to the Javascript portion – which makes this amazing.

Javascript

We are going to build on top the basic idea that he has created http://nooshu.com/recreate-iphone-swipe-effect-using-jquery. For most part of the logic, the original creator has worked them out pretty much. But I shall explain through it as well.

The Basic Structure

So, let’s start off with declaring a couple of properties and configurations that we anticipated.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var gallegant = function(){
  // trackers and flags for the methods to work
  var initialPosition = null, // initial position of mouse before drag starts
      distance = 0, // distance of mouse moved from initial position
      previousPosition = { left: 0, top: 0 }, // remembers the last position scrollView has shifted
      direction = "", // flag that indicates which direction mouse has "swiped"
      width = 1024, // width of each slide and how much to shift
      swipeTolerance = 0, // the tolerance for the distance of swipe before scrolling kicks in
      totalOffsetDistance = 0, // the total amount of offset inclusive of previous scrolled images
      lastSlideIndex = 0, // remembers the last slide index user's on before flipping view
 
      defaults = {
        domNode: undefined, // REQUIRED: reference to the domNode of the scrollView
        scrollViewContent: undefined, // REQUIRED: reference to the content in the scrollView       
        areaOfGesture: undefined, // REQUIRED: reference to the node that listens to the events
        arrayOfSlides: [], // REQUIRED: your array of slides to be added
        swipeTolerance: parseInt(width / 3, 10), // PX: can pass a fixed pixel, or formatted one like the default
        gapBetweenSlides: 10,  
        indicatorViewNode: undefined, // OPTIONAL: reference to the slide indicators within scrollView
        numberOfSlides: undefined, // OPTIONAL: set this if you're using indicators and passing in array of slides
      }; // object that holds all the settings and declaratives
}();

I’ve commented for most part of the variable’s purpose. But to sum up the above declaratives, we know a few things that will help our scrollView function properly.

  • The initial position of our mouse before dragging starts.
  • The amount of distance our mouse has moved, offset from initial position.
  • We also need to remember the previous position because if user drags beyond the scrollView, and there’s no more slides left, we need to set the scrollView back to where it was.
  • We need to know which direction user has dragged from.
  • To keep configuration easy and quick, we also track the width of each slide.
  • Just like iOS, we can customize the distance our mouse has dragged, before we even consider it as a “drag to change slide”. We call it swipeTolerance.
  • Although we have various trackers to know where mouse is and how much has been dragged, but we don’t know how much, in total, is the offset distance for the scrollView.
  • Lastly, we just need to remember what’s the last slide index so we can shift the slide indicators as needed.

Next up, we shall create all the various methods that forms up the plugin and its behaviors. Look at the following:

1
2
3
4
5
6
7
8
9
10
11
12
return {
    init: function () {
      // init methods for custom behaviors
    },
 
    initWithSettings: function(settings) {
      if (settings && typeof settings == 'object') {
        defaults = jQuery.extend(defaults, settings);
      }
      // init methods for custom behaviors
      this.init();
    },

What we are doing here is returning an object that contains all the methods we are about to create.

init() is the basic method that you can initialize the plugin. It should contain the basic methods that help you setup the entire structure and behaviors.

initWithSettings() is an extended method where we consider the scenario where user wish to include custom settings of their own. Thereafter, we init the same methods as init(). With that in place, we can then have a method that prepares the slides and layouts as required:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    prepare: function () {
      // ensure that we have a scrollView Content to process
      if (defaults.scrollViewContent && $(defaults.scrollViewContent)) {
        // should user pass in an array of images, we need to process and display them
        // otherwise we assume user preferred to hard code the images in
        if (defaults.arrayOfSlides && defaults.arrayOfSlides.length > 0) {
          for (i in defaults.arrayOfSlides) {
            // display the format of each slides
            $('<div class="image slide-wrapper"><div class="image-wrapper"><div class="slide" style="background: url('+
              defaults.arrayOfSlides[i]
              +') no-repeat;"></div></div></div>').appendTo(defaults.scrollViewContent);
          }
          defaults.numberOfSlides = $(defaults.areaOfGesture).length; // remember the number of slides
        }
        // grab the width of each slide by using the first slide image
        // using innerWidth() because of padding gaps between slides
        width = parseInt($("div.slide-wrapper:first").innerWidth(), 10);
        // set the width of the scrollView Content to hold all the images specified by user
        $(defaults.scrollViewContent).css('width', (defaults.numberOfSlides * width));
      }else {
        console.log("No scrollViewContent provided.");
      }
    },

Okay, this might seem like a huge chunk, but it mostly contains comments. First, we just want to ensure that a scrollViewContent node is provided, before we proceed to prepare it.

We then loop through the given array of images, and create the structure and append it appropriately. The next part should be fairly familiar for people who has iOS programming knowledge.

We first grab the width of one slide, including all paddings etc, and then multiply it by the number of slides we have and set it as scrollViewContent CSS width.

With this, our slides should be pretty much ready. But before we go deeper into the algorithms, we should first build the behaviors and setting up the other elements too. Let’s get the indicators up first:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    setupSlideIndicators: function(index){
      // using the defined indicatorView and numberOfSlides, we calculate and
      // append the dot nodes into the view
      if (defaults.indicatorViewNode && $(defaults.indicatorViewNode)) {
        $(defaults.indicatorViewNode).empty();
        for (var i = 0; i < defaults.numberOfSlides; i++) {
          $(defaults.indicatorViewNode).append('<li class="slide-dot"></li>');
        }
        // if user has visited a slide previously, we use that index instead       
        if (lastSlideIndex > 0) this.setIndicatorActiveByIndex(lastSlideIndex);
        // otherwise we set the first dot indicator as active
        else this.setIndicatorActiveByIndex(0);
      } else { return; } // if user didn't enable indicator
    },

Again, we check to ensure the node exist. The second line was placed in as a preventive measure for repetitive creation of the dots. I had this as I visualize future capabilities that I can build into it.

Anyway, once all the dots are appended we see if there’s any last visited slide, otherwise set it to default, which is 0. Now, how do we set a certain dot as active when user has scrolled to that point? That’s the next method we need to implement:

1
2
3
4
5
    setIndicatorActiveByIndex: function (index) {
      // we remove active class from all children, then add it back to the specified index
      $(defaults.indicatorViewNode).find("li.slide-dot").removeClass("active").end().find("li:eq(" + index + ")").addClass("active");
      lastSlideIndex = index; // remembers the last slide index before user flips view
    },

By using jQuery’s powerful selectors, we can use the index to hook up the exact dots which we need to set active. Lastly, set the variable lastSlideIndex to the current one we just hooked.

Adding to the point of setting up elements, I prefer to do a complete structure. You initialize something, you set its behaviors, you also need to have a method to remove/destroy them. And here’s the method to destroy indicators:

1
     resetIndicatorsIndex: function () { lastSlideIndex = 0; },

Simply reset the lastSlideIndex. Wait, if we are trying to destroy indicators, shouldn’t we also remove the dots too? Yes and no. No because we have already done so when we perform setupSlideIndicators().

So the question then, why not put them together? We need to track lastSlideIndex, no matter what. So scoping them together is obviously a bad idea. Either you create a separate method, or you can do what I did. Now, we are mostly done with the indicators.

Let’s head a little back up where we said we’re going to do behaviors. The reason to do so is so that you know the various touch points that user shall interact with. Let’s have an overview of it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    behavior: function () {
      // private declaratives for condition checks
      var distance = 0,
          tolerance = { min: -50, max: 50 },
          dragStartAt = 0,
          selectedThumbnail = undefined,
          dragEndAt = 0,
          _this = this;
 
      // bind the first event that will create the chain effect
      $(defaults.areaOfGesture).bind("mousedown", function(e){
          // hook the event "mouse move" so we track and respond to it
      });
 
      // bind the events to the areaOfGesture
      $(defaults.areaOfGesture).bind("mouseup", function(e){
        // when user is done scrolling, we need some logic to handle tolerance and scrolling
      });
    },

Some portion of the idea above is based on the original creator, http://nooshu.com/recreate-iphone-swipe-effect-using-jquery. I basically built on top of it and improved some of the behaviors. In here, we’ve identified two key areas where user will actively interact with it.

The first one would be the entire view port, where user will hold the mouse down, and starts dragging. The second one would then be when user releases the mouse and stops scrolling. So, all the variables declared were just made locally for the purpose of the events handling.

At this point, from all the variables that we’ve setup so far, we understood that a few of them are being used occasionally, such as initial position and total offset distance. In this case, a convenient method can be setup so other methods can easily reset it when needed:

1
2
3
    resetOrigin: function () {
      initialPosition = null, totalOffsetDistance = 0;
    },

Again, I would also like to include a method that helps to completely reset/destroy the behaviors and trackers. So, here it is:

1
2
3
4
5
6
7
8
9
10
11
    destroy: function () {
      // reset pointers and indicators
      this.resetOrigin();
      lastSlideIndex = 0, distance = 0, previousPosition.left = 0, previousPosition.top = 0;
 
      $(defaults.areaOfGesture).off();
      // optional: you can have the scrollViewContent reset back to default position
      $(defaults.scrollViewContent).css('marginLeft', 0);
      // optional: removes all event binding to every children
      $(defaults.domNode).find('*').off();
    }

In this method, you turned off and remove all attached events and reset trackers as well as setting the scrollView back to its original position. Now that all these have been setup, the only piece left that we need to finish up after creating all the scrolling methods, is the behaviors. (Event handlers are empty)

So far so good? It can be really confusing at first, but as you move along I hope you can get the general sense of view on how it works. I’m jumping around a lot as I would also like to walk you through the structure properly, instead of introducing the cores right away.

The Core Scrolling Functionality

Moving on, we keep the two touch points in mind, we can setup the core logic of handling scrolling and bouncing effect:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    onScrollViewIsScrolling: function (e) {
      // get the actual position of the mouse relative to the container
      var positionX = e.clientX - $(defaults.domNode).offset().left,
          positionY = e.clientY - $(defaults.domNode).offset().top;
 
      //Set our initial position, mouse movement now relates to this point
      if(initialPosition === null) initialPosition = {left: positionX, top: positionY };
 
      //Relative mouse point
      totalOffsetDistance = initialPosition.left - positionX + previousPosition.left;
      // keeping reference to the distance mouse moved
      distance = initialPosition.left - positionX;
 
      //Check what direction mouse moved
      if (distance < 0) direction = "right"; else direction = "left";     
 
      //Move the scrollView content to point
      $(defaults.scrollViewContent).css({
        marginLeft: -totalOffsetDistance
      });     
    },

The first two lines are pretty straight forward. We keep a reference of the positions of the mouse. Next, we then keep remember these coordinates within an object. Here’s the crucial part – We need to remember the total offset distance, meaning how much of the scrollView has been dragged, and also the distance the mouse has moved.

Why do we need to know? Firstly, Mouse moved distance allows us to detect if user has dragged left or right. Secondly, it allows the swipe tolerance feature to work. As you can see on the next line, we used the distance to understand which direction should our scrollView move.

The next step is to handle the scenario when user stops scrolling completely. The first of it should look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
    onScrollViewHasScrolled: function ($this) {
      // grab the margin offset of the scrollView
      var margin = parseInt($(defaults.scrollViewContent).css("marginLeft"), 10),     
          $next = $this.next(), // find the next image
          $prev = $this.prev(), // find the previous image
          index = $this.index(); // index used in indicators     
 
      //User pulled left
      if(direction === "left"){
        //We have a next element to show
        if($next.length && -distance < -defaults.swipeTolerance){
          $(defaults.scrollViewContent).animate({
            marginLeft: -(width * (index + 1))
          }, 550, "easeOutCirc");
          previousPosition.left = (width * (index + 1));       
          //set the indicator the right dot using index
          this.setIndicatorActiveByIndex(index + 1);
        } else {
          // if user did not scroll to the tolerance limit
          // we bounce the scrollView back to its original position
          // IOS image gallery effect
          $(defaults.scrollViewContent).animate({
            marginLeft: -previousPosition.left
          }, 550, "easeOutCirc");
        }
      }
 
      // if user has pulled right
      if(direction === "right"){
        // we need to ensure it satisfy the condition checks and
        // there's a previous image to reveal
        if($prev.length && distance < -defaults.swipeTolerance){
          $(defaults.scrollViewContent).animate({
            marginLeft: -(width * (index - 1))
          }, 550, "easeOutCirc");
          // when animated, we need to update the tracker
          // so we know how much scrollView has shifted
          previousPosition.left = (width * (index - 1));
          //set the indicator the right dot using index
          this.setIndicatorActiveByIndex(index - 1);
        } else {
          // if user did not scroll to the tolerance limit
          // we bounce the scrollView back to its original position
          // IOS image gallery effect
          $(defaults.scrollViewContent).animate({
            marginLeft: -previousPosition.left
          }, 550, "easeOutCirc");
        }
      }
    },

Whoa! That’s a big chunk of code! Worry not, it’s almost identical behavior for both if-else. The first few lines should also be straight forward, it’s all grabbing of info that will help set us up for what’s going to happen next. We have two parts to this, the first handles the scenario where user drags left:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
     //User pulled left
     if(direction === "left"){
        //We have a next element to show
        if($next.length && -distance < -defaults.swipeTolerance){
          $(defaults.scrollViewContent).animate({
            marginLeft: -(width * (index + 1))
          }, 550, "easeOutCirc");
          previousPosition.left = (width * (index + 1));       
          //set the indicator the right dot using index
          this.setIndicatorActiveByIndex(index + 1);
        } else {
          // if user did not scroll to the tolerance limit
          // we bounce the scrollView back to its original position
          // IOS image gallery effect
          $(defaults.scrollViewContent).animate({
            marginLeft: -previousPosition.left
          }, 550, "easeOutCirc");
        }
     }

What happens here is that we first ensure there’s still some slides left and also if it satisfy the swipe tolerance. When these conditions are true, we will animate to shift the scrollView to pull to the left.

This is where it can potentially get very confusing. In iOS, swiping to the left means “show me the next slide” and swiping to the right means the opposite. What’s different here is, in normal slideshow clicking left actually meant “show me the previous slide” and clicking right meant the opposite of it. So while scripting this, you would have to constantly remind yourself of the nature of gestures.

We also need to set the trackers to remember the previous position of it, so that onScrollViewIsScrolling() can use it for accurate calculation. When scroll is done, we want to notify users that slide has moved to which index, by updating the indicators we’ve setup previously.

Lastly, we need to handle the scenario where user failed to pass the swipe tolerance, we bounce the slides back. Here, we also make use of the previous position variable to help make this happen.

Now as you’ve expected, swiping right is just the exact opposite of the above method. Let’s run it through quickly:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
     // if user has pulled right
     if(direction === "right"){
        // we need to ensure it satisfy the condition checks and
        // there's a previous image to reveal
        if($prev.length && distance < -defaults.swipeTolerance){
          $(defaults.scrollViewContent).animate({
            marginLeft: -(width * (index - 1))
          }, 550, "easeOutCirc");
          // when animated, we need to update the tracker
          // so we know how much scrollView has shifted
          previousPosition.left = (width * (index - 1));
          //set the indicator the right dot using index
          this.setIndicatorActiveByIndex(index - 1);
        } else {
          // if user did not scroll to the tolerance limit
          // we bounce the scrollView back to its original position
          // IOS image gallery effect
          $(defaults.scrollViewContent).animate({
            marginLeft: -previousPosition.left
          }, 550, "easeOutCirc");
        }
     }

If you make a comparison, the only changes that occur here is from a “-” to a “+”. This is the last part of the scrolling logic! It’s quite simple actually, just confusing at some point when gestures meet directions and the addition of bouncing back when user didn’t drag enough.

Finally, the last piece of puzzle in this plugin is to connect these methods to the event handler respectively. Let’s get the onScrollViewIsScrolling() in first:

1
2
3
4
5
     // bind the first event that will create the chain effect
     $(defaults.areaOfGesture).bind("mousedown", function(e){
          // hook the event "mouse move" so we track and respond to it
          $(this).bind("mousemove", _this.onScrollViewIsScrolling);
     });

As simple as that. Since mouse events are fired automatically, all we had to do is connect the dots together by using jQuery’s bind() method. Next would be the slightly more lines of code for handling “end of scroll” scenario. Let’s have a look:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
     // bind the events to the areaOfGesture
     $(defaults.areaOfGesture).bind("mouseup", function(e){
        // when user is done scrolling, we need some logic to handle tolerance and scrolling
        // we want to make sure it satisfy the condition for scroll tolerance before animating
        if (totalOffsetDistance >= tolerance.max && totalOffsetDistance > 0 || totalOffsetDistance <= tolerance.min && totalOffsetDistance < 0) {
          // removes the event binding so it does not affect the next movement
          $(this).unbind("mousemove", _this.onScrollViewIsScrolling);
          //Animate the scrollView
          _this.onScrollViewHasScrolled($(this));         
        } else {
          // if it's not scrolling, user must have tapped instead
          // flip over to reveal what's behind
          $(defaults.scrollViewContent).css({
            marginLeft: -previousPosition.left
          });
          if (defaults.domNode) { $(defaults.domNode).trigger('onViewDetail'); }
        }
        //Reset the pointers
        _this.resetOrigin();
     });

We check to make sure the swipe tolerance is met, by comparing our offset distance with our minimum and maximum distance. When this condition is satisfied, we unbind() the event that handles scrolling, so it doesn’t conflict the next scrolling occurrence. In the case when it didn’t meet the tolerance check, we adjust the scrollView back to its original place.

Lastly, we reset the origins pointer. Every “end of scroll” completes a cycle, so trackers setup to know a certain position, should also be reset to prepare for the next cycle to occur.

Setting it up

The final setup (I promise!) is initialize the plugin on your HTML side. The following is an example:

1
2
3
4
5
6
7
8
9
$(document).ready(function() {
          gallegant.initWithSettings({
            domNode: $('#gallery'),
            scrollViewContent: $('#gallery').find('div.scrollView-content'),
            areaOfGesture: 'div.slide-wrapper',
            arrayOfSlides: ['images/1.jpg', 'images/2.jpg', 'images/3.jpg', 'images/4.jpg'],
            indicatorViewNode: $('#gallery').find('div#indicator ul.slides-indicator'),
          });
});

Of course, init() works too. But you would then have to insert your images into the defaults first before running. Start putting in your images and see the magic happens!

Conclusion

Well, that’s quite a lot of info to deal with! Take your time and always feel free to refer back to this tutorial and demo, and hopefully it points you towards the right direction.

This is just a basic implementation of iOS scrollView concept, onto the web. And I think it does a pretty good job when displaying huge hi-res images – it just looks amazing!

I believe iOS has more to offer in terms of their UI and gesture related experience. It provides us great understanding to enhance User Experience in the web world. I’m sure there are many more things we can still learn and adopt to improve websites and become more user-centric.