Scrolling in SproutCore: Part 2

written by Tim Evans

Continued from last week’s post “Scrolling in SproutCore”. Tim presents his solution for making SC.ScrollView feel awesome.


I began discussing possible solutions to the problem with Colin Campbell, who pointed me in the direction of Cappuccino as a reference implementation. Cappuccino’s scroll views were buttery smooth and offered all of the benefits that SproutCore needs. So, I began investigating.

This is what I found:

See that blue box in the center of the screen – the one that I pointed an arrow to? This is how Cappuccino applications take scroll events. Normally, you’ll never see this because it has an opacity of 0, and its purpose is to swallow scroll events. Yes. This little element, which I’ll call the scroll catcher for the remainder of this discussion, takes in scroll events and then creates normalized events from it.
Continue reading

Scrolling in SproutCore

written by Tim Evans

Converting native widgets into SproutCore compatible widgets can be an adventure, especially if the browser vendors don’t agree on the events that the widget produces. Scrollable elements are one of those things.

For those of you who find SproutCore’s ScrollView insufferable on various platforms because it’s not scrolling how you expect it to, this should shed some light on how really hard the problem actually is. And, hopefully you’ll be satisfied with the solution that I propose.

First, let me answer the question of why… Why, that is, SproutCore needs its own ScrollView. There are two primary reasons for this: cosmetic and performance.

The cosmetic reason is theming. SproutCore has the ability to skin pretty much everything in your application, including scrollbars. This gives a consistent look and feel across platforms and eases the headache of UI consistency. However, some people want their applications to have the native look and feel. The current implementation of SC.ScrollView doesn’t allow that. This can be a turnoff, especially when mixing-and-matching SC.ScrollView and native scroll views. It creates inconsistency, which leads to bad user interaction.

SproutCore prides itself on being able to render gigantic lists of items without hosing your browser. It does this by incrementally rendering the list. This means that what you see is all there is with SC.ListView. Anything you can’t see is not actually rendered in the DOM. Implementing this requires SproutCore to know about the clipping frame (the rectangle that gives the information on what’s currently visible). To get the clipping frame, SproutCore needs to observe when scrolling happens. Hence, SC.ScrollView.

Now for a quick dig into the internals of how 1.x SC.ScrollView works in “desktop” mode, so when we start looking at our solutions, we know what stuff is changing.

I’m going to take it backwards from where the scrollTop and scrollLeft are set in the DOM being controlled by SC.ScrollView. For those of you who’d like to follow along, I’m looking at the adjustElementScroll function in scroll.js in the desktop framework.

I’m going to break this down, ’cause this function contains the core of what makes SC.ScrollView tick.

1
2
3
4
5
6
7
8
9
  /** @private
    Called at the end of the run loop to actually adjust the scrollTop
    and scrollLeft properties of the container view.
  */
  adjustElementScroll: function() {
    var container = this.get('containerView'),
        content = this.get('contentView'),
        verticalScrollOffset = this.get('verticalScrollOffset'),
        horizontalScrollOffset = this.get('horizontalScrollOffset');

Pretty self-explanatory, continuing on…

1
2
3
4
5
6
7
8
9
10
11
12
    // We notify the content view that its frame property has changed
    // before we actually update the scrollTop/scrollLeft properties.
    // This gives views that use incremental rendering a chance to render
    // newly-appearing elements before they come into view.
    if (content) {
      // Use accelerated drawing if the browser supports it
      if (SC.platform.touch) {
        this._applyCSSTransforms(content.get('layer'));
      }
 
      if (content._viewFrameDidChange) { content._viewFrameDidChange(); }
    }

Here’s the first interesting bit. Ignoring the accelerated drawing, which is for the touch version of SC.ScrollView, we have the last line calling content._viewFrameDidChange. Hmm. What does that do?
Continue reading