Creating a Mobile Touch Slide Panel with JQuery

Creating a Mobile Touch Slide Panel with JQuery

by Zack Grossbart on January 28, 2011

Fork me on GitHub

Horizontal Slide View

This is a JavaScript implementation of a mobile style Horizontal Slide View.

Use your finger to drag it left and right to all of the items in the view.

Since this is JavaScript it works on every mobile device with touch support

and

it does a good job with pictures

Each cell of the slider is HTML so you can put anything you want in it

The slider works just like the native application sliders

It snaps each cell into place and supports momentum

Try dragging fast and watch the items fly by

Want to know how it works?

keep reading

This bar is boring on a computer, but it comes alive on a mobile device. Grab your iPad or Android device and take a look.

Drag your finger and the items move with you. They follow your speed and keep your momentum. Play with it a little. I’ll wait.

Get the
source code

The sliding touch panel only shows up on mobile platforms. With a mouse it feels clunky, but sliding with your finger just feels right.

This article shows you how to implement a sliding touch panel in JavaScript. jQuery is the only dependency of the touch slider. The rest is pure JavaScript and HTML. It runs fast, feels natural, and works on every mobile device with touch support.

It all starts with a grid

The sliding panel is a set of div tags: two containers and a div for each cell. The first step is laying them all out in a simple grid.

createSlidePanel: function(/*string*/ gridid, /*int*/ cellWidth, 
                           /*int*/ padding) {
    var x = padding;
    
    $(gridid).each(function() {
        $(this).css({
            'position': 'relative',
            'left': '0px'
        });
        
        $(this).parent().css('overflow', 'hidden');
        $(this).children('.cell').each(function() {
            $(this).css({
                width: cellWidth + 'px',
                height: '90%',
                position: 'absolute',
                left: x + 'px',
                top: padding + 'px'
            });

            x += cellWidth + padding;
        });

Each cell has a fixed width and padding and we lay them out in a single row. We set the overflow property on the container to hidden so we don’t get scroll bars on the grid.

All layout is based in CSS. The grid’s div tag has a position of relative so we can easily move it to the left and right. As the grid moves we adjust the left attribute in the CSS to position the grid. The panel container doesn’t show any overflow so only the cells in the middle are visible.

CSS easily adjusts the positioning with a single property change and the browser renders it quickly. This straightforward layout lets us quickly get to the fun part: touching it.

Make it touchable

There are three important events for touch support:

ontouchstart is called when you first press your finger onto the screen.

ontouchmove is called every time you move your finger on the screen.

ontouchend is when you take your finger off.

The default action for touching and moving in the browser is to scroll. By overriding this behavior we’ll allow the user to scroll our panel without moving the entire window.

This works well for our widget, but it can easily go bad. Overriding the default behavior is often defying the user’s expectations. If you aren’t clear about what’s happening it quickly becomes a bad user experience.

Event handling is a little tricky because we want to make sure the links and any other widgets within the cells still work. We just want the moving events.

The most interesting binding is ontouchend. This one will control if we follow the behavior of the widget that was clicked on or off the panel.

$(gridid).each(function() {
    this.ontouchend = function(e) {
        e.preventDefault();
        e.stopPropagation();
        
        if (touchslider.sliding) {
            touchslider.sliding = false;
            touchslider.touchEnd($(this), e);
            return false;
        } else {
            /*
               We never slid so we can just return true
               and perform the default touch end
             */
            return true;
        }
    };
    ...

The first line binds the event so we can respond to it. Then we have to figure out if we should allow the default behavior or not. This depends if we’re at the end of a slide or just a widget interaction.

We always prevent the default handling of the event. The default is never what we want here. Android will just scroll the screen, but iPads will hover a little magnifying class control that looks broken for our widget.

Touch the screen

The start of the touch is about recording data.

touchStart: function(/*JQuery* elem, /*event*/ e) {
     jsslide.startX = e.targetTouches[0].clientX;
     jsslide.startLeft = jsslide.getLeft(elem);
     jsslide.touchStartTime = new Date().getTime();
     
},

We record the location and the time the touch happened. The location is important since all touches are relative. The time helps us keep track of momentum.

Mobile devices make these panels feel realistic by adding momentum. It gives you the feeling that the panel has weight. If you drag it fast then it keeps going beyond your stopping point. This is the strange physics of mobile momentum.

In the real world force equals mass times acceleration. We need to figure out that force so we know how much extra to let the panel move when you’re not pushing it. Our fake acceleration is the distance you moved multiplied by the time you spent doing it. The start and end time of the touch motion give us that information. Our widget doesn’t have any real mass, so we fake it. That part comes later.

Move your finger

Once the touch is started we need to worry about moving events. The user could move their finger in any direction so we need to handle them both.

touchMove: function(/*JQuery*/ elem, /*event*/ e) {
     if (!touchslider.sliding) {
         elem.parent().addClass('sliding');
     }
     
     touchslider.sliding = true;
     

     if (jsslide.startX > e.targetTouches[0].clientX) {
         /*
          * Sliding to the left
          */
         elem.css("left", "-" + (jsslide.startX - e.targetTouches[0].clientX - 
                                 jsslide.startLeft) + "px");
         jsslide.slidingLeft = true;
     } else {
         /*
          * Sliding to the right
          */
         var left = (e.targetTouches[0].clientX - jsslide.startX + 
                     jsslide.startLeft);
         elem.css('left', left + 'px');
         jsslide.slidingLeft = false;
     }
}

All positions are negative since the panel starts with a left property of zero. Then we figure out what direction it’s moving in based on the current location and the start of the touch. We don’t need any jQuery animations here since the touch is moving fast enough to simulate movement. Adding any extra effects would just slow things down.

Stop touching the screen

The end of the touch is where things get really interesting. There are a number of different ways the touch could end and we have to handle each of them individually.

Using native transitions

At first I created this using JQuery animations. They worked well, but they were a little choppy. Miller Medeiros left an awesome comment about using WebKit transitions so I gave it a try. They worked really well. I restructued the code a little to work with WebKit transitions and it looks great.

We can handle all animations through a single doSlide function. It sets the left property and specifies the transform.

doSlide: function(/*jQuery*/ elem, /*int*/ x, /*string*/ duration) {
     elem.css({
         left: x + 'px',
         '-webkit-transition-property': 'left',
         '-webkit-transition-duration': duration
     });
}

The tranform property is left and we specify a duration for the transform to take. We need to unset these properties once we start the touch again so we don’t wait a long time for every transition while the touch is moving.

Did they drag too far

During the touch moving they could have dragged the bar before the beginning or after the end of the items. We let them do it so the widget seems responsive, but then give them a gentle reminder by sliding the bar back to the right place.

touchEnd: function(/*JQuery*/ elem, /*event*/ e) {
     if (jsslide.getLeft(elem) > 0) {
         /*
          * This means they dragged to the right past the first item
          */
         touchslider.doSlide(elem, 0, '0.5s');
         elem.parent().removeClass('sliding');
         jsslide.startX = null;
     } 

If they drag to the right past the first item then our panel will be left floating with a large space on the left. In that case we’ll slide it back into place.

Dragging left past the last item works in a similar way. We animate the panel back into place so there isn’t any extra space on the right side.

Slide momentum

When the user slides in the middle we need to consider their slide momentum. This tells us how much farther we should push the panel once the touch ends. If they do a short slow drag then the panel shouldn’t move much extra. If they do a long fast drag then we want a large extra slide. The momentum allows the user to easily make big jumps in the slider without having to perform multiple slides.

Calculating the slide momentum is a little tricky. We need to consider the distance of the slide and how long it took.

slideMomentum: function(/*jQuery*/ elem, /*event*/ e) {
     var slideAdjust = (new Date().getTime() - jsslide.touchStartTime) * 10;
     var left = jsslide.getLeft(elem);
     
     var changeX = 12000 * (Math.abs(touchslider.startLeft) - Math.abs(left));
         
     slideAdjust = Math.round(changeX / slideAdjust);
     
     var newLeft = slideAdjust + left;
     
     /*
      * We need to calculate the closest column so we can figure out
      * where to snap the grid to.
      */
     var t = newLeft % touchslider.colWidth;
     
     if ((Math.abs(t)) > ((touchslider.colWidth / 2))) {
         /*
          * Show the next cell
          */
         newLeft -= (touchslider.colWidth - Math.abs(t));
     } else {
         /*
          * Stay on the current cell
          */
         newLeft -= t;
     }
     
     /*
      * Next we'll slide the panel...
      */

To start we need to know how long the slide took. We’ll get this number in milliseconds. Then we need to know the current location of the panel.

Next we need to know how far they slid: the difference between the absolute values of the position when the slide started and the position when it ends. Then we multiply that by a magic number.

The number of milliseconds the user was actively sliding could be very large, easily over 10,000. The amount they moved will be a pretty small number, typically under 1,000. We have to adjust the time to make it feel like the panel has real weight. There’s some solid math behind why I chose 12,000, but in the end it just makes it feel right.

We divide these two numbers and get the slideAdjust value. Then we apply it to the position of the panel depending on the movement to the left or the right. Now we just need to adjust the panel so they match our grid.

Once we’ve moved the panel, if we’re still in the bounds of the grid, then we want to snap the item to the left-hand side of the grid. This is the process of sliding the grid to the left or the right so there is a whole cell showing on the left side.

We calculated the width of each column when we did our first grid layout. Now we figure out of the panel is closer to the start of the cell or the end of it and adjust accordingly.

This is all ridiculous

I’m giving you a little gold star for making it this far . This article is packed full of math and low-level UI programming. This widget does the kind of work you often find in physics engines just so it can show a simple menu. That’s a telltale sign you’re too far out on the bleeding edge.

I could write a native app to do this. IOS and Android both have horizonal slide view widgets. They did the work for me. It would be easier, but native apps lock you in. There’s no way to write a native application for multiple mobile platforms, but you can make your web application look and feel like a native app.

The only solution for cross-platform mobile applications is HTML and JavaScript. That won’t change for at least the next couple of years. Some applications will always require native code, but most of them could be HTML and JavaScript. The environment isn’t as nice, but the cross-platform support is amazing.

Debugging JavaScript on mobile devices

There are some really nice tools for mobile development. XCode is at the top of my list. HTML doesn’t give you anything close to that level of support, but you can get a nice console.

I define a new function named output:

output: function(/*string*/ msg) {
    if (console) {
        console.info(msg);
    }
}

It prints to the console when it’s available. Safari on the iPad and iPhone have options to enable the console under the settings panel. Android does it a little better.

If I plug my Android device into the USB port of my computer and launch the Android SDK Dalvik Debug Monitor I can see a log of everything happening on my Android.

Browser logs are tagged with the word browser. That makes it pretty easy to find them. This interface isn’t pretty, but it works well and I don’t have to switch windows.

This new mobile world is strange, but mobile web applications are a big part of it. They look good, run fast, and aren’t that hard to work with.

Image credits

The images in this slider were provided by the Apple marketing department, the Android marketing department, iStockPhoto, Fir0002, Bff, Gentaur, and doug8888.