Mobile Zone is brought to you in partnership with:

Ariya is a passionate engineer interested in bleeding-edge technologies. He has been involved in various large projects, from KDE to WebKit. These days, his focus is mostly on software craftsmanship around web technologies. His (little) spare time is spent running the projects PhantomJS (headless WebKit) and Esprima (JavaScript parser). Ariya is a DZone MVB and is not an employee of DZone and has posted 55 posts at DZone. You can read more from them at their website. View Full User Profile

JavaScript Kinetic Scrolling: Part 1

12.12.2013
| 5861 views |
  • submit to reddit

Flick list, with its momentum effect and elastic edge, becomes a common user-interface pattern since it was made popular by Apple iPhone a few years ago. Implementing this pattern using HTML and JavaScript seems to be a daunting task for many web developers. In this series, I will uncover the mystery of kinetic scrolling via several easy-to-digest code examples.

Before we go crazy and apply those fancy effects, it is important to set a solid foundation. Hence, the first part will deal only with an exemplary implementation of a basic drag-to-scroll technique (no momentum, no edge bouncing). The concept and also some parts of the code will be reused in the rest of the series. If you want to follow along and get the full code, check out the repository github.com/ariya/kinetic.

scroll

Here is the game plan. Let us assume that the view (a DOM element) we want to scroll is quite large. Being viewed on the limited device screen, it is as if the screen acts as a viewport window. Scrolling the content is a matter of translating the view while keeping the viewport fixed. In order to translate the view correctly (using CSS3 transform), we need to capture the user interaction with the view. As the user taps and then drags up/down the view, we need to control the offset accordingly to give the illusion that the view follows his finger’s movement.

For this tutorial, the view contains the common Lorem ipsum text (generated via lipsum.com). Also notice that the scrolling is only in the vertical direction. To give an idea, try to load the demo ariya.github.io/kinetic/1 on your modern smartphone. It has been tested on Android 4.3 (Chrome, Firefox), Android 2.3 (Kindle Fire), and iOS 6 (Mobile Safari).

basicscroll

Since we do not want the browser to handle and interpret user gesture natively, we need to hijack it. This is achieved by installing the right event listeners (for both mouse and touch events), as illustrated below. The handlers tap, drag, and release have the most important role in the implementation of this scrolling technique.

view = document.getElementById('view');
if (typeof window.ontouchstart !== 'undefined') {
    view.addEventListener('touchstart', tap);
    view.addEventListener('touchmove', drag);
    view.addEventListener('touchend', release);
}
view.addEventListener('mousedown', tap);
view.addEventListener('mousemove', drag);
view.addEventListener('mouseup', release);

Initializing some state variables is also an important step. In particular, we need to find the right bounds (max and min) for the view’s scrolling offset. Because our view will occupy the whole screen, innerHeight is used here. In a real-world application, you might want to use the computed (style) height of the view’s parent element instead. As you will see shortly, pressed state is necessary to know when the user drags the list.

max = parseInt(getComputedStyle(view).height, 10) - innerHeight;
offset = min = 0;
pressed = false;

If you try the demo, you will notice a subtle scroll indicator. Intentionally, this is placed on the left side of the view. This way, you will notice immediately that this is not the browser’s native scrollbar. That indicator needs to be placed anywhere between the topmost and bottommost of the screen, hence the need for a relative factor (will be used later). Bonus point: where does that hardcoded value 30 comes from?

indicator = document.getElementById('indicator');
relative = (innerHeight - 30) / max;

Since we want to adjust the view’s position using CSS3 transform, we need to figure out the right style property to use. A comprehensive detection can be employed, but the following simple approach already works quite reliably.

xform = 'transform';
['webkit', 'Moz', 'O', 'ms'].every(function (prefix) {
    var e = prefix + 'Transform';
    if (typeof view.style[e] !== 'undefined') {
        xform = e;
        return false;
    }
    return true;
});

Before we see the actual event handlers, let us take a look at two important helper functions.

Published at DZone with permission of Ariya Hidayat, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)