Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Smooth Animation on Mobile Web

Smooth Animation on Mobile Web

Presented at Velocity Conference 2014: http://velocityconf.com/velocity2014/public/schedule/detail/34995.

With powerful smartphone platforms and high-performance web browsers, there is no more excuse for laggy UI animations on a mobile web application. The “best practice” list of client-side mobile optimization is pretty well-known: use optimized JavaScript, requestAnimationFrame, event throttling, GPU acceleration, and so on. In this talk, those best practices will be put to the test in a series of practical user interface patterns, from a silky smooth kinetic scrolling example to the use of CSS 3-D to clone the infamous Cover Flow effect.

Ariya Hidayat

June 26, 2014
Tweet

More Decks by Ariya Hidayat

Other Decks in Programming

Transcript

  1. 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
  2. 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; }
  3. 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)'; }
  4. Scroll Threshold delta = reference - y; if (delta >

    2 || delta < -2) { reference = y; scroll(offset + delta); }
  5. 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
  6. 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
  7. Manual Scrolling Automatic Scrolling Track touch position Scroll the view

    Compute the “launch” velocity Scroll the view Reduce the velocity
  8. 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
  9. 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
  10. Scroll offset at a given time Final scroll offset Amplitude

    (how far the final scroll offset is) Time constant
  11. amplitude = 0.7 * velocity; target = offset + amplitude;

    requestAnimationFrame(autoScroll); Setup for Automatic Scrolling
  12. function autoScroll() { var elapsed = Date.now() - timestamp; scroll(target

    - amplitude * Math.exp(-elapsed / timeConstant)); requestAnimationFrame(autoScroll); } Continuous Automatic Scrolling
  13. The flick (=launch velocity) determines how far the list will

    stop It does not impact when the list will stop scrolling 1 2
  14. 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); }
  15. 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
  16. function scroll(offset) { var slow = Math.round(offset / 2); var

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