Question

I'm currently working on an iPad JS/HTML app, using native scrolling to view a large bar chart svg.

I'm looking to have the labeling along the x and y axes persist along the top and left of the graph. Basically, the graph should slide around freely underneath the labels, which themselves will only move in the appropriate direction (eg, the x axis header will shift its labels over as you scroll left, and the y axes labelings for vertical scrolling)

I currently have some css to do this, but the native scrolling moves a lot faster than my javascript to sync the panels up. There's sort of this elastic interplay as one element is dragged faster than the other. It all plops into the correct place once the scroll animation stops, but the interaction looks pretty janky when scrolling is going on.

Is there a better way to tackle this problem? Are their other events I could tap into? Is there a way to force multiple scrollable divs to react to the same scroll event without manual js position calculations? Or is the lag unavoidable due to native scrolling being offloaded to the gpu?

/* CSS */
.fixedaxis {
    position:absolute;
    top: 0px;
    left: 0px;
    background: blue;
}

#chartheader {
    z-index:5; 
}

#sidebar {
    z-index:6;
}

    .scroll {
        overflow: auto;
        -webkit-overflow-scrolling: touch;
    }



/* relevant html */

    <div id="content" style="position: relative;">
        <div id="chartheader" class="fixedaxis"></div>
        <div id="sidebar" class="fixedaxis"></div>
        <div class="scroll">
            <section id="barchart">
            </section>
         </div>
     </div>

/*javascript */

var scroller = $('.scroll');
var tableHeader = $('#chartheader');
var sidebar = $('#sidebar');
scroller.on('scroll', function() {
    console.log("scrolling: " + this.scrollLeft);
    tableHeader.css('left', (-1 * this.scrollLeft) + 'px');
    sidebar.css('top', (-1 * this.scrollTop) + 'px');
});
Was it helpful?

Solution

So it turns out, you can't. At least, not at this point in time.

According to the Apple Developer docs https://developer.apple.com/library/ios/#documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html

You receive events for scroll when the user's finger is on the screen, and one final scroll event once momentum stops. If the content is gliding around between those two periods, you receive no events.

What we ended up doing is making the sidebar content semi-transparent on the second scroll event, and then making it opaque again after a timeout, with an allowance for repositioning if a single "momentum-end" scroll event fired again.

Something along the lines of:

           var lastTimeout;
           var numScrolls = 1;
           var startTop = 0;

           function(event) {
                var elem = event.target;
                startTop = startTop || elem.scrollTop;

                if (lastTimeout) {
                    clearTimeout(lastTimeout);
                } else if (numScrolls == -1) {
                   /* I've omitted some short circuit logic for other scrolling cases
                    * but that's why we're going off of -1 here 
                    */

                    // stray scroll from native scroll end
                    $labels[0].scrollTop = elem.scrollTop;
                    $labels.css('opacity', '1');
                    startTop = null;
                }


                // wait for two consecutive scrolls
                if (numScrolls > 0) {
                    $labels.css('opacity', '0.3');
                }

                ++numScrolls;
                lastTimeout = setTimeout(function() {
                    console.log(elem.scrollTop);
                    $labels[0].scrollTop = elem.scrollTop;
                    $labels.css('opacity', '1');
                    lastTimeout = null;
                    numScrolls = -1;
                    startTop = null;
                }, 1000);
            };
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top