HTML5, CSS3, jQuery, JSON, Responsive Design...

Smooth scrolling, sortable web list for iPhone!

Michael Brown   June 20 2012 11:21:23 PM
Update 2 April 2016

How times change.

Four years ago, this stuff felt so cutting edge.  Now it just looks laughable!  With the advent of frameworks like React Native, the days of trying to impersonate native look and feel using just HTML, JavaScript and CSS are well and truly over.

Following Facebook's own React Native Tutorial, I was able to set up an iPhone list in under an hour.  And it's silky smooth, with inertial scrolling, top and bottom bounce: the works!  It feels just like native, because under the covers, that's exactly what it is.  More to come on this in a later post...






I've been working on a mobile web app that will use a jQueryUI's .sortable() functionality to enable drag and drop sorting of that list.   Sound easy enough, but I ran into several issues that I was, thankfully, able to work around.

Below is a picture of what I'm aiming for.  (Click to see it bigger.)  Note how row 3 is being dragged over row 5 by my very own finger!

Image:Smooth scrolling, sortable web list for iPhone!


Version 1 - Simple code, crap scrolling!

So here's Version 1.  Grab one of the labels with your finger (or mouse if not on a touchscreen) and you can drag and drop to resort the list.


The code's simple enough: an unordered ul list (id = listItems) inside two divs: an inner div (id = scroller) and an outer div (id = wrapper).    You can see that I've fixed position header and footer elements (also divs) surrounding them.  This is where you'd put your buttons and titles on a mobile app.  The UL list is made sortable like so:

$("#itemsList").sortable({
handle : "label",
axis : 'y',
disabled : false
});


There's just one problem though: if you're on an iPhone, you may notice that the ordinary scrolling speed (i.e. when you're not sorting the list) is well, ... rubbish!  It's excruciatingly slow.   Where's the springiness and momentum that you would normally associate with iPhone scrolling?  When you flip the list up or down and let go, the scrolling just stops!  That's not what's supposed to happen on smartphones.



Version 2 - Enter iScroll

It seems that's just the way things are on the iPhone web browsers.  But to the rescue comes a little JavaScript library called iScroll.  It uses CSS transform techniques to give you a scrolling experience that pretty damned close to native, IMHO.  Just how good is iScroll?  Well, it seems that Apple themselves have been known to use it.  Good enough?

So here's Version 2 of my sortable list, reworked using iScroll.  Check out the iPhone scrolling performance now; a bit more like it, yes? Flip that list up or down and then let go; it should continue scrolling when you let go, before coming to a gradual stop, just as you'd expect.  If you're on a desktop browser, you can see this by grabbing an an area of the list (although not a list label) then flicking the mouse up or down and releasing.  

The code to enable the iScroll functionality must be bound to the inner div, wrapper, like so:

myApp.myScroll = new iScroll('wrapper')


But there's a fly in the ointment - there had to be, didn't there?  Try dragging a list label to sort the list with the iScroll version.  Oh dear.  It kind of works, but in a weird way.  When you drag an item to resort, the whole list moves with it.  Not good!



Version 3 - Sort Mode

After quite some research - not to mention trial and error -  I ascertained that the thing to do is to disable the iScroll scrolling when I'm drag/resorting the list and then reenable it when I'm finished.

So, here's Version 3 of the app, which does that.  It brings the concept of an Edit Order mode, enabled by the button at the top.  The list is not sortable until you've clicked this button, at which point iScroll is disabled and you're back to the normal, slowwwww scrolling.  I don't think that's an issue, however, because you're not going to need all that extra scroll speed to sort this list.  Click on the Done button and you're back to iScroll speed but can no longer sort the list.  An acceptable compromise, I think.

When making the list sortable, I have to disable iScroll as well as enabling the list sort.  I then do the reverse operation when I'm done sorting.  The code to disable iScroll and enable the list sorting is:

//disable iScroll
myApp.myScroll.disable();

// enable list sort
$("#itemsList").sortable({
handle : "label",
axis : 'y',
disabled : false
});


The code to reenable iScroll and disable list sorting is:

//enable iScroll
myApp.myScroll.enable();

// disable list sort
$("#itemsList").sortable({
disabled : true
});



But there's still problems…

If you scroll a good way down the list before enabling Sort Mode, dragging a list item to the top does not cause it to scroll up correctly.  Originally, I couldn't drag an item down past the bottom or top at all!  I fixed that problem via the following line when changing to Sort Mode:

$("#wrapper").css("overflow", "");


But I'm still stuck with the "can't drag up all the way" problem.  I can drag it a up part of the way, but not all the way to the top of the list.  Also, when switching out of Edit Mode via the Done button, the iScroll scrolling won't always go all the way to the top again.  In other words, when switching between modes the lists seem to "lose their place".



Version 4 - Synching Scroll Modes

I think that this is happening because there are, in fact, two separate scrolling mechanisms at work here: the iScroll one (based on CSS transforms) and the "normal" one (which I'll call "the JS one" from here on).  When I'm using iScroll then obviously the iScroll scrolling mechanism is at work.  When I am in Sort Mode, however, I've disabled the iScroll mechanism so that the JS scrolling mechanism comes into play.  At this point, the JS mechanism has no idea what the iScroll mechanism has been doing in terms of scrolling.  Hence, it's "lost its place".

So, I need to reset it those positions when switching modes.  Let me put you out of the suspense that I know you're all feeling!  Here's the final working Version 4 of the app, which has this position resetting code in place.

What I do in the final version is that before switching modes, I log the current y-axis position from current scrolling mode and then pass that value to the new mode.  Then I do a quick scroll to the top then down again switcheroo, to ensure that the scrolling modes are really in sync.

For iScroll mode, I can get the current y-axis position from the iScroll object's "y" property, like so:

var currentY = $("#wrapper").scrollTop();


If that value is less than zero then the iScroll object is not at its top, so I send it programmatically back up to its top:  

// Send iScroll to the top
myUtils.myScroll.scrollTo(0, 0, 0, false);


Now I can disable the iScroll object and call the code to enable the jQuery.sortable().  Having done that, I now send the JS scrolling mechanism back down to where the iScroll position last was.  I do this via a call to jQuery.scrollTop(x), like so:

// Send normal/JS scroll down to iScroll's last pos
$("#wrapper").scrollTop(0 - currentY);


Phew!!  I'm now in Sort Mode and can drag and drop list items in either direction, and drag them right to the top or bottom of the list.

When coming out of Sort Mode, I go through the same process in reverse.  This time, I get the currentY position from JS scrolling mechanism, by another call to jQuery.scrollTop(), only this time without a parameter.  I then feed that position to the iScroll mechanism to send it back the JS scroll mechanism's last recorded position.

All this happens fast enough that the user shouldn't even see it.  It's clunky, but it does work.
Comments

1Karsten Lehmann  06/20/2012 1:53:47 AM  Smooth scrolling, sortable web list for iPhone!

Excellent work!

I integrated iScroll in an iPad app some days ago. I noticed that the bouncing at the top and bottom does not feel like on a native app, especially for slow scrolling. Have you also done some work in this area?

2Mike Brown  06/20/2012 2:16:03 AM  Smooth scrolling, sortable web list for iPhone!

Karsten,

I'm not sure what you mean; it looks native enough to me!

At the end of the day though, it *isn't* native, and there's only so far you can push this kind of stuff, I think.

Did you try posting your issue to iScroll's Google Group?

https://groups.google.com/forum/?fromgroups#!forum/iscroll

3Karsten Lehmann  06/20/2012 11:55:07 AM  Smooth scrolling, sortable web list for iPhone!

Ok, maybe I'm wrong and it only happened in Firefox.

4Philip Sultanescu  07/15/2012 3:49:36 AM  Smooth scrolling, sortable web list for iPhone!

I think you have 2 mistakes in the codes you put for disabling/enabling the iScroll:

//disable iScroll

myApp.myScroll.enable();

//enable iScroll

myApp.myScroll.disable();

Should be:

//disable iScroll

myApp.myScroll.disable();

//enable iScroll

myApp.myScroll.enable();

5Michael Brown  07/15/2012 4:56:38 AM  Smooth scrolling, sortable web list for iPhone!

@Philip,

Thanks for that. I've corrected the error.

6Tim  07/23/2012 5:19:56 PM  Smooth scrolling, sortable web list for iPhone!

Hey man nice work - I've been trying to get this working for hours and your post has saved me!

I noticed a slight bug in what I have done and on checking it seems it does it in yours too. If you scroll right to the end of the list and let the list bounce off the bottom a couple of times, and then go to edit the order whole list seems to jump around one row.

Let me know if you can replicate.

Cheers,

Tim

7Mike Brown  07/23/2012 6:55:36 PM  Smooth scrolling, sortable web list for iPhone!

@Tim,

Actually, I'm seeing worse than that. When I scroll to the bottom of the list a couple of times (letting it bounce both times), entering Edit Mode jumps to the top of the list again!

Odd it doesn't happen after the first bounce; you have to drag to the bottom twice to cause the error.

I'll take a look when I have a spare moment.

Please post back here if you find a solution yourself.

8Mario Vargas  05/09/2013 6:47:55 AM  iScroll and jQuery UI Sortable

I just wanted to say thank you for the awesome solution to a problem I was struggling with for two days. The solution I was devising was very close to what you came up with, but your solution was far simpler.

My case required me to use horizontal scrolling instead of vertical scrolling, but all I had to do was flip the axis and it worked. I love it. I even created a fallback version for IE8 that uses the horizontal scroll bar (overflow: auto) and integrates with the mousewheel because iScroll doesn't support IE8. Well, both the iScroll and fallback versions support use of the mousewheel.

9Chris  11/02/2013 11:04:40 AM  Smooth scrolling, sortable web list for iPhone!

Hey man - this is great thank you!

My header seems to be scrolling off the page though when I'm in sort mode - even though I am using position:fixed on everything like you (I think).

Did you have to do anything special to keep your header from not scrolling off the page in sort mode?

10Chris  11/04/2013 5:11:49 AM  Smooth scrolling, sortable web list for iPhone!

FYI - I got my header issue fixed - was an overflow issue.

I'm now trying to figure out how to scroll up and down the list while in sort mode - but otherwise this thing is great - thank you again!

11David Bates  05/19/2015 12:41:00 PM  Smooth scrolling, sortable web list for iPhone!

Is there any way to get this to work without adding the concept of changing "modes"?

About