iPhone-Style Alphabetical Contact List with HTML, CSS and JavaScript (jQuery)

Posted by & filed under Front-End Development, Labs.

NOTE: This page references a deprecated version of the iOSList plugin. For the current version of iOSList, visit http://brianhadaway.github.io/iOSList/

Front-end developers are often called on to build UI elements that aren’t entirely original. Whether the cause of this phenomenon is a lack of creativity on the part of designers or a particular affinity for certain UI elements among the web-browsing public, ripping-off drawing inspiration from others’ ideas is common in web design. One of the most prominent sources of such “inspiration” over the past few years is Apple’s Aqua interface. Thanks to this glossy, semi-transparent, gradient-laden design, front-end and Flash developers have spent several years worth of billable hours creating simulated reflections and configuring cover flow components for clients insistent upon having sites that “look more web 2.0″.

Thanks to the advent of iOS devices, there’s a new set of Apple-designed UI elements that designers are using as inspiration. In fact, there’s already an open source project, iUI, that aims to provide “a more ‘iPhone-like’ experience in your WebApps” using HTML, CSS and Javascript. The iUI project not only imitates the look of the iOS interface, but it also seeks to emulate the subtleties of the experience of using an iPhone, iPad or iPod. One such subtlety, that I haven’t seen successfully recreated in HTML, CSS and JavaScript is the alphabetical lists seen in Contacts and Music (among other places) in the iPhone, iPad and iPod. To test my mettle, I set out to recreate that seemingly-simple UI component.

One of the most prominent places alphabetical lists are used is in Music and Contacts. In case you aren’t familiar, here’s what it looks like in Music:

The heading for the current list section (A) remains stationary as the user scrolls through the section.

As the adjacent section (B) approaches the top of the list container, the current section (A) and header scroll off screen.

Once the header for the new section (B) reaches the top of the list container, its position becomes fixed and it remains in place until the user scrolls to a new section.

The Concept

The main challenge to overcome is the need to reposition the current section’s header every time the list’s scroll event fires. For instance, while A is the current section, its header must appear to remain fixed at the top of the list until it “collides” with the next section’s header (B). Once A’s header “collides” with B’s header, A’s header becomes fixed to the bottom of its section where it remains until the bottom of the section A exceeds the height of its header. Performing these calculations as the scroll event fires (inconsistently across browsers) is taxing and on users’ machines and results in the header flickering as the list scrolls.

Given this challenge, it is necessary to resort to some sleight of hand. I can create a “fake header” that will remain fixed at the top of the list. This header will remain visible until I need to show headers A and B collide at which time I will hide the fake header, show the animation then update the text in the fake header and unhide it. This alleviates the need to reposition the current section’s header every time the list’s scroll event fires resulting in a better-performing, flicker-free iPhone-style contact list.

(1) Fake Header visible (2) Fake Header hidden, 'A' header positioned to bottom of its parent, (3) Fake Header visible with text updated to 'B'

The Markup

This is the perfect use of the under-appreciated definition list element (DL). By using a DL, I can use its child element DT as the header and DD as the list item. There’s only one issue with this approach. I need to keep the elements associated with a certain heading grouped so I can measure groups’ height and position. I could wrap each group in a DIV, but that would be semantically incorrect (DL should only have DT and DD as children), so I’ll use individual DL elements for each group. I wrap my DL elements in a DIV with the ID of “list1” and the class of “listContainer”. This will be the scrolling container and will allow multiple list instances in one page.

The JavaScript

The logic in the JavaScript for this component is quite simple. I’ve already discussed the logic to some extent in “The Concept” above, so let’s talk about optimizing the script.

When using jQuery, developers often fall into the trap of repeatedly accessing the same DOM element. Without exercising care performance will tank so I want to call methods that access the DOM as infrequently as possible. The obvious way of doing this is by caching elements and wrapped sets. Another way I reduce DOM access method calls is by operating on cached element properties instead of repeatedly retrieving those properties from the DOM. For instance, every time the scroll event fires I need to determine which element is at the top of the list and which element and its previous sibling. To do this efficiently, I reference cached element properties stored in an array instead of accessing the DOM elements’ properties. This drastically increases performance.

The CSS

Much of the list’s CSS is used to adhere (loosely) to a visual design but some of the styles are instrumental to the functionality. View the source of the sample to see what’s going on with the CSS. It’s pretty simple.

Download the Demo and Source or fork it on GitHub

18 Responses to “iPhone-Style Alphabetical Contact List with HTML, CSS and JavaScript (jQuery)”

  1. EricG

    Quite impressive!
    However, when I scroll very ‘fast’, an error is being thrown.

    ioslist.js:Line 69; cannot read property ‘list’ of undefined.
    This happens when I put focus on the list, and do for example Ctrl-End, or when I use my mouse to pick the scrollbar and drag it fast below (like a jump I guess).

    Good job though :)

    Reply
  2. Tanel

    I’ve tried out different jQuery solutions to achieve the same effect and the one presented here is hands down the best one I’ve found so far performance wise. Thanks!

    Reply
  3. Tanel

    I have a question tho: would it be possible to redesign the script in a way that it would allow to have content with dynamic height (hide/show parts) inside? Like rebuild the height cache on the fly? A simple y/n answer would suffice so I’d know whether to investigate this further or not…

    Reply
    • Brian

      @Tanel – Yes. You might experience a small decrease in performance but rebuilding the height cache on the fly seems feasible.

      Reply
  4. Matt W

    Excellent work Brian, but how would you go about adding momentum when the user’s touch is released during a scroll? Thank you :)

    Reply
  5. greg

    This is a pretty nice approach too.
    The problem with your “fake” header is, that you can’t use semi-transparent headers, as it would be in iOS.. right?

    I only flew over the code, but I will look at it later.

    I wrote something similar.
    You can take a look at it here: (Webkit browsers only… Safari Chrome)

    iPhone 4, iOS 5 Version

    Desktop, Safari/Chrome Version

    Latest DEV-Version

    (it’s the complete iPhone UI)
    ..if you click on the “Music App” and then on the “Songs”-Tab, you will see the persistent headers list.
    (also works with the letter scrollbar on the side)

    I am basically setting 3 classes (prev, current, next) for the current element and it’s closest ancestors.
    The lists are separate ul-elements with a h2 as header.
    then I define the behavior of the headers.
    The current header have position:fixed, the rest of the headers are position:static.
    It works perfectly on Desktop Browsers (Safari/Chrome)
    and has a small flickering on iOS 5, iPhone 4, when scrolling back up.

    Reply
  6. Dwight

    Is there any way to access the user’s contact list on the iphone so you could load their contact list into your own UI?

    Reply
  7. Aleksey

    Some BUG:
    if we have only 2 groups with many elements and when scrolling to end

    while((elems[i].listOffset - currentTop) <= 0) {

    ...

    i++
    }

    “while” trying get undefined element of array

    And found FIX:

    while((elems[i].listOffset - currentTop) = elems.length) break;
    }

    Reply
  8. Dayton

    I totally randomly found this post Googling for an IOS ui framework. Turns out I’m making an iPhone app at my new gig and I need exactly this. It’s a small internet. I too dig the often neglected dl tag which is the correct approach for this type of list. I’m going to use this with PhoneGap and iUI and I’ll contribute my fixes if I come across any hiccups.

    Reply
  9. Matthew Haynes

    This look really promising – thank for all the time and effort you have put in – I hope to use this UX feature on a image gallery to show the prices of the garments in the shots featured.

    Reply
  10. Dan Ford

    To answer Matt W’s question (albeit an old one), I was able to add momentum by adding the following to the .ListWrapper css element:

    -webkit-overflow-scrolling: touch;

    The only issue now is that on iOS devices, after scrolling, it takes a second or two for the fake header to update to the proper header. I’m not seeing the issue on Android.

    Reply
  11. Gail

    I’m experiencing a bug with this. W is the last letter in my DLs and at page load, the fakeheader displays A like it should. But once you scroll it displays W and doesn’t change back at all. Any ideas?

    Thanks again for the awesome script!

    Reply

Leave a Reply

  • (will not be published)

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>