Sunday, June 26, 2011

Implications of iOS 5 webkit-overflow-scrolling

When news of iOS 5 improvements first came out, we were told that Mobile Safari would support fixed position elements and some rudimentary overflow:scroll improvements. I wrote about why, even with these improvements, custom scrolling would still be required. Fortunately for all of us, Apple had some better news to share when the second beta of iOS 5 came out. Finally, we can have native style scrolling in webapps with some simple CSS.

.scrollable {
  overflow-y: scroll;
  -webkit-overflow-scrolling: touch;
}

This improvement will be a very powerful tool for mobile web developers. It will make it significantly easier for just about any developer to throw together a really slick looking application. What was previously hundreds of lines of complex JavaScript to animate the page is now 2 simple lines of CSS.

What else will change?

Scrolling Quality

Up until now, almost every scrolling library has tried as hard as possible to replicate iOS native scrolling. As far as I can tell, the only reason that Scrollability even exists is because Joe Hewitt figured he could do a better job of replicating native scrolling than iScroll. With this now natively supported by Mobile Safari, we can all stop worrying about these discrepancies and focus on real work :)

Something less talked about is the rending and GPU overhead that is caused by custom scrolling implementations. Often all of the transforms introduces a lot of flashing in the application when content is re-rendered. Scrolling also gets slower the larger the DOM gets. Presumably with the native support all of these problems will go away, and the user will just have a consistently smooth scrolling experience.

Pull down to refresh

Two weeks ago I wrote about the various pull to refresh implementations on the internet and why I wasn't happy with any of them. Since then, Mobile Gmail pushed an update that includes a really slick pull to refresh widget. Every pull to refresh library that I know of right now is implemented as an add-on to some scrolling library. This is because the pull to refresh components have a high level of interaction with the scrolling components. It is likely that everyone is going to have to rewrite their pull to refresh code to work in the absence of custom scrolling.

I think it won't be at all difficult to rework the widget to work properly, but we might see some applications take a bit longer to migrate to the built in scrolling because of issues like this.

Sticky table headers

Sticky table headers could be a little more troublesome depending on exactly how the built in scrolling is implemented in Mobile Safari. For an example of what I am talking about, check out the menu page on Mobile Gmail, how the list section headers stay fixed at the top and animate away as another header approaches. Like pull to refresh components, sticky table headers implementation requires tight integration with the custom scrolling library it is built on top of.

In Mobile Gmail, the positions of the sticky headers are recomputed on every touchmove event, and on a repeating interval if momentum is in progress. The same can easily be done with the built in scrolling if the browser publishes accurate scroll events. If not, we can maybe estimate when the page might be flinging based on touch events.

Tap to top

Tap to top is a standard gesture recognized by most (all?) iOS native applications that will scroll the current view to the top. When a user taps the top bar in Mobile Safari, the page will scroll to the top and the URL bar will become visible. Ideally the overflow:scroll element  on the page will also be scrolled to the top. Mobile Gmail and Scrollability both support this gesture by listening to scroll events, and determining if the scroll event was from the user dragging the page or tapping the top (based on time of last touch event anywhere in the document).

It is possible that the new built in scrolling will natively support scrolling the overflow:scroll areas to the top, but that seems unlikely. If they in fact do not, then devs might have to use some hacks to continue supporting a tap to top feature. We can still detect the tap to top gesture just as easily, however you can't apply a webkit-transition to the scrollTop feature (now required to change the scroll position). It might be necessary to change the transform to do something like this:

function tapToTop(scrollableElement) {
  var currentOffset = scrollableElement.scrollTop
  
  // Animate to position 0 with a transform.
  scrollableElement.style.webkitTransition =
      '-webkit-transform 300ms ease-out';
  scrollableElement.addEventListener(
      'webkitTransitionEnd', onAnimationEnd, false);
  scrollableElement.style.webkitTransform =
      'translate3d(0, ' + (-currentOffset) +'px,0)';
    
  function onAnimationEnd() {
    // Animation is complete, swap transform with
    // change in scrollTop after removing transition.
    scrollableElement.style.webkitTransition = 'none';
    scrollableElement.style.webkitTransform =
        'translate3d(0,0,0)';
    scrollableElement.scrollTop = 0;
  }
}

Tuesday, June 14, 2011

Pull down to refresh with JavaScript for iOS mobile Safari

I was recently looking at the new Twitter mobile webapp and was checking out their new features. They have now included fixed toolbars, custom scrolling, and pull to refresh. Their scrolling seems OK but apps like mobile Gmail and anything using Sencha Touch still feels way better. However their pull to refresh seemed really bad, it has a lot of flickering (like whenever the arrow changes from up to down), the loading state has no spinner, and the revealing / hiding of it is really jarring. It seems like it was quickly done and that the implementer had a hard time getting it to work properly in cooperation with the custom scrolling code.


Sencha pull to refresh

It seemed like this could be done better, so I set out to see if I could find better examples. The first example I found was here: http://www.codetick.com/demos/refresh/index.html


In this example, things are not much better. The scrolling is significantly better, there is no flickering or jarring hiding/showing, but there is also no loading state with a spinner, as soon as you release the new content appears and the widget is hidden. Another small problem I found with this example is that the arrow always spins counter clockwise, whereas it should spin counter clockwise when moving to the up orientation, and vice versa on the way down. This problem could be easily fixed though, here is an example code snippet of how the arrow orientation could be updated properly.

/**
 * @param {Element} arrowEl The HTML element containing the
 *     arrow image.
 * @param {PullState} pullState One of PEEKING, REVEALING,
 *     REFRESHING.
 */
function onPullStateChanged(arrowEl, pullState) {
  var label, rotation, refreshing;
  if (pullState == PEEKING) {
    rotation = 180;
    label = 'Pull down to refresh.';
  } else if (pullState == REVEALING) {
    rotation = 0;
    label = 'Release to refresh.';
  } else {
    label = 'Refreshing.'
    refreshing = true;
  }

  if (refreshing) {
    arrowEl.style.display = 'none';
  } else {
    arrowEl.style.WebkitTransform = '(0,0,0) ' +
        'rotate(' + rotation + 'deg)';
    arrowEl.style.display = 'block';
  }
}

The styling of the widget is also pretty wrong. The centering is done not of just the text, but the text and the arrow, whereas the text should be centered and the arrow should be to the left of the text wherever it is. Surprisingly, it seems that many developers are happy with this component, but I think it pales in comparison to the native libraries available and that we should try to do better for the mobile web.


Cubiq pull to refresh?

It seems that the cubiq scrolling library has included a pull to refresh component. This is explained here: http://cubiq.org/iscroll-4. Unfortunately I couldn't find any demos except for this youtube video
http://www.youtube.com/watch?v=Js_EjlH-UbQ

From the video, it looks like this implementation is in much better state then the Twitter implementation or the Sencha third party plugin, but it looks like it is still in the early stages of development.

Overall from what I can tell there are no good examples of pull to refresh in a mobile web app today. Hopefully something will be released soon that demonstrates how polished it can really be, and in a way that other developers can easily use to incorporate into their own applications.

Tuesday, June 7, 2011

Why custom scrolling is still required with iOS 5 overflow:scroll

Apple just announced a bunch of improvements that would ship with mobile Safari in iOS 5. You can find most of them here. I was primarily interested in the bits about position: fixed and overflow: scroll, as I have authored my own custom scrolling code for iOS devices (and look forward to not needing it anymore).

It seems like a lot of people have gotten all excited about overflow: scroll and position: fixed, thinking that these improvements are going to make custom scrolling unnecessary. However the way that Apple has developed these features does not solve all the same problems that custom scrolling does.

Custom scrolling solves three problems.

  1. Gives the webapps a fixed elements (like top/bottom toolbars)
  2. Faster scrolling (because default safari scrolling is slower than native apps)
  3. Independent scrollable areas

Points 1 and 2 are intended to give webapps a more native feel. Great for those that believe they should be able to deliver top quality apps to their users through the web. Point 3 is the lesser thought about issue that is required for something like Gmail's two pane UI.


What issues does the new features address? Really only number 1 (giving webapps fixed elements). This can now be achieved by using position:fixed. Number 2 will still be an issue because the rest of the application will scroll by a regular webpage... maybe not a show stopper for many developers. You would think that number 3 would be solved by the new overflow:scroll support, however the overflow:scroll comes without scrollbars, or inertial momentum. In fact the only improvement for overflow:scroll seems to be that you can scroll with one finger now instead of 2.

This is quite disappointing. In the Android world, the browser has supported position:fixed as of 2.2 (Froyo), which solves issue 1. Issue 2 has never actually been an issue on Android because the browser scrolls fast by default. Issue 3 is resolved on Android in version 3.0, with the introduction of overflow:scroll. The PlayBook also supports position:fixed and overflow:scroll. This leaves Apple pretty far behind in terms of giving developers the tools they need to build rich layouts and interactions into their mobile webapps.