Cover Flow in JavaScript and CSS 3-D

Cover Flow in JavaScript and CSS 3-D

Presented at HTML5 Developer Conference Autumn 2014.

With the support for buttery-smooth GPU-accelerated 3-d effect with CSS, modern browsers allow an implementation of a stunning fluid and dynamic user interface. To ensure that the performance is still at the 60 fps level, certain best practices needs to be followed: fast JavaScript code, usage of requestAnimationFrame, and optimized GPU compositing.

This talk aims to show a step-by-step guide to implement the infamous Cover Flow effect with JavaScript and CSS 3-D. We will start with the basic principle of custom touch-based scrolling technique, the math & physics behind momentum effect, and the use of perspective transformation to build a slick interface. Don’t worry, the final code is barely 200 lines!

0284b8950e0f4a57bcc092d4dbb98d97?s=128

Ariya Hidayat

October 21, 2014
Tweet

Transcript

  1. 4.
  2. 10.

    No Native Browser Scrolling 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); Intercept mouse events Intercept touch events
  3. 12.

    Tap vs Release function tap(e) { pressed = true; reference

    = ypos(e); e.preventDefault(); e.stopPropagation(); return false; } function release(e) { pressed = false; e.preventDefault(); e.stopPropagation(); return false; }
  4. 13.

    Drag: Delta + CSS Transform if (pressed) { y =

    ypos(e); delta = reference - y; reference = y; scroll(offset + delta); } function scroll(y) { offset = (y > max) ? max : (y < min) ? min : y; view.style[xform] = 'translateY(' + (-offset) + 'px)'; }
  5. 14.

    Scroll Threshold delta = reference - y; if (delta >

    2 || delta < -2) { reference = y; scroll(offset + delta); }
  6. 15.

    User taps the screen pressed = true (touch) reference =

    20 (view) offset = 100 User drags 7 px up (touch) y = 13 (touch) delta = 7 (view) offset = 107 translate the document by 7 px User releases finger pressed = false
  7. 20.

    Steady touchstart Pressed Manual Scrolling Automatic Scrolling touchend timer tick

    touchmove touchend and zero speed touchend and positive speed timer tick decelerate to zero speed touchstart
  8. 22.

    Manual Scrolling Automatic Scrolling Track touch position Scroll the view

    Compute the “launch” velocity Scroll the view Reduce the velocity
  9. 23.

    Continuous Velocity Tracking function tap(e) { pressed = true; reference

    = ypos(e); velocity = amplitude = 0; frame = offset; timestamp = Date.now(); clearInterval(ticker); ticker = setInterval(track, 100); e.preventDefault(); e.stopPropagation(); return false; } Call track() 10 times/second Rate throttling
  10. 24.

    Velocity = Change of Position function track() { var now,

    elapsed, delta, v; now = Date.now(); elapsed = now - timestamp; timestamp = now; delta = offset - frame; frame = offset; v = 1000 * delta / (1 + elapsed); velocity = 0.8 * v + 0.2 * velocity; } instantaneous velocity ~ 100 ms optional filter
  11. 29.

    Scroll offset at a given time Final scroll offset Amplitude

    (how far the final scroll offset is) Time constant
  12. 30.

    amplitude = 0.7 * velocity; target = offset + amplitude;

    requestAnimationFrame(autoScroll); Setup for Automatic Scrolling
  13. 31.

    function autoScroll() { var elapsed = Date.now() - timestamp; scroll(target

    - amplitude * Math.exp(-elapsed / timeConstant)); requestAnimationFrame(autoScroll); } Continuous Automatic Scrolling
  14. 33.

    The flick (=launch velocity) determines how far the list will

    stop It does not impact when the list will stop scrolling 1 2
  15. 34.
  16. 39.

    As Simple As... function autoScroll() { var elapsed = Date.now()

    - timestamp; scroll(target - amplitude * Math.exp(-elapsed / timeConstant)); target = Math.round(target / snap) * snap; requestAnimationFrame(autoScroll); }
  17. 40.

    As Simple As... function autoScroll() { var elapsed = Date.now()

    - timestamp; scroll(target - amplitude * Math.exp(-elapsed / timeConstant)); target = Math.round(target / snap) * snap; requestAnimationFrame(autoScroll); } Integer multiples of snap
  18. 42.
  19. 43.
  20. 46.

    function scroll(offset) { var slow = Math.round(offset / 2); var

    fast = Math.round(offset); ... } snap = screen width
  21. 50.