ariya.io About Talks Articles

JavaScript Kinetic Scrolling: Part 5 (Cover Flow Effect)

4 min read

In the fifth part of this JavaScript kinetic scrolling tutorial, a demo of the attractive Cover Flow effect (commonly found in some Apple products) using the deadly combination of kinetic scrolling and CSS 3-D will be shown. The entire logic is implemented in ~200 lines of JavaScript code.

The cool factor of Cover Flow is in its fluid animation of the covers in the 3-D space. If you haven’t seen it before, you miss a lot! To get the feeling of the effect, use your browser (preferably on a smartphone or a tablet) to visit ariya.github.io/kinetic/5. Swipe left and right to scroll through the cover images of some audiobooks.

coverflow

My first encounter to this Cover Flow effect was 6 years ago as I implemented this with pure software in C/C++. It ran quite well even on a lowly hardware (such as this 200 MHz HTC Touch). Of course, with GPU-accelerated CSS animation, there is no need to write an optimized special renderer for this. For this example, we manipulate the transformation matrix directly using CSS Transform feature. To follow along, check the main code in coverflow.js.

There are several key elements in this demo. First, we will need a smooth scrolling with the right acceleration and deceleration using the exponential decay technique which was covered in Part 2. Also, stopping the scrolling at the right position is necessary (Part 3, see the snap-to-grid code) as we need to have all the cover images in the perfect arrangement, not halfway and not being stuck somewhere.

When you look at the code, the scrolling via mouse or touch events impacts the value of the variable offset. In fact, the code for doing that is line-by-line equivalent to what has been covered in the previous parts. On a normal circumstances, this offset is always an integer multiply of 200 (the cover image size, in pixels). When the user makes a touch gesture to drag the cover or when it is decelerating by itself, then the value could be anything. This is where we need to apply tweening to give the right transformation matrix for each cover image.

Such a decoupling results in a modular implementation. All the events (touch, mouse, keyboard, timer) only deal with the change in offset, it has no knowledge as to how the value will be useful. The main renderer, which is the scroll() function, knows nothing on how that offset value is computed. Its responsibility is very limited: given the offset, compute the right matrix for every single cover. This is carried out by going a loop through the stack of the covers, from the front-most image to the ones in the far background, as illustrated in this diagram. Note how the loop also serves to set the proper z-index so that the center image becomes the front cover and so on.

Every image is initially centered on the screen. After that, the right translation is in the X axis is computed based on the relative position of the image. For a depth perception, there will be an additional translation in the Z axis, making the cover going farther from the screen. Finally the image is rotated (in the Y axis) with a positive angle and a negative angle for the left and right side, respectively. The 3-D visual comes from the perspective of the main div element which is the parent of every single image element.

stacking

The entire scroll() is about 40 lines. If the explanation above is still confusing, it is always fun to step through the code and watch the variables as it goes through one cover to another. The code is not crazily optimized, it is deliberately chosen to be as clear as possible without revealing too much. Having said that, with the most recent iOS or Android phones, the effect should be pretty smooth and there is not any problem achieving over 30 fps most of the time. You will get some minor extra frames (particularly useful for older devices) if the reflection effect is removed.

Since this demo is intended to be educational, I left a few exercises for the brave readers. For example, the tweening causes only one cover to move at a time. If you are up for a challenge, see if you can move two covers, one that is going to the front and one that is going to the back. In addition, the perspective here is rather a cheat since it applies on the parent DOM element (as you can witness, these transformed images have the same vanishing point). Applying an individual perspective and Y-rotation requires wrapping every img element with its container. Of course, the 3-D effect does not have to be like Cover Flow as you could always tweak the matrix logic so that it resembles e.g. MontageJS Popcorn instead.

Once you leave the flatland, there is no turning back!

Related posts:

♡ this article? Explore more articles and follow me Twitter.

Share this on Twitter Facebook