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

Building HTML5 Tablet Apps

Building HTML5 Tablet Apps

Co-presented with Mike Bollinger at MinneWebCon 2012

Sam Kirchmeier

May 07, 2012
Tweet

More Decks by Sam Kirchmeier

Other Decks in Programming

Transcript

  1. Building HTML5 Tablet Apps for iOS and Android MINNEWEBCON 2012

    @skirchmeier @mikebollinger
  2. None
  3. HTML5 apps kinda suck. The elephant in the room:

  4. Native vs. HTML5

  5. Native -- Expensive to develop HTML5 + Cheaper to develop

    -- One platform at a time + Snappy + Refined (true native feel) + Full featured -- Limited features -- Unrefined -- Slower + All platforms at a time
  6. Reasons

  7. Money 1

  8. Time 2

  9. Audience 3

  10. Control 4

  11. Money 1 Time 2 Audience 3 Control 4

  12. 5 Design Patterns T A B L E T S

      H A V E   F L E X I B L E  
  13. Money 1 Time 2 Audience 3 Control 4 Flexible Design

    Patterns 5
  14. Change the game.

  15. Examples

  16. Best Buy http://bby-uv.appspot.com/ Financial Times http://app.ft.com Proof http://proofwhisky.com

  17. Best Buy http://bby-uv.appspot.com/ Financial Times http://app.ft.com Proof http://proofwhisky.com iOS Only

    >! iOS Only >!
  18. Frameworks

  19. jQuery Mobile jQTouch Sencha Touch

  20. Native Wrappers

  21. PhoneGap Titanium

  22. Our Custom App

  23. None
  24. http://livefront.com/explore

  25. How do we build this?

  26. Step 1: Travel the world

  27. Step 2: HTML (sorry)

  28. <!DOCTYPE html> <html> <head> ... </head> <body> <div id="container"> <div

    id="splash" class="page"> ... </div> <div id="content" class="page"> ... </div> </div> </body> </html>
  29. <!DOCTYPE html> <html> <head> ... </head> <body> <div id="container"> <div

    id="splash" class="page"> ... </div> <div id="content" class="page"> ... </div> </div> </body> </html>
  30. <!DOCTYPE html> <html> <head> ... </head> <body> <div id="container"> <div

    id="splash" class="page"> ... </div> <div id="content" class="page"> ... </div> </div> </body> </html>
  31. <!DOCTYPE html> <html> <head> ... </head> <body> <div id="container"> <div

    id="splash" class="page"> ... </div> <div id="content" class="page"> ... </div> </div> </body> </html>
  32. <!DOCTYPE html> <html> <head> ... </head> <body> <div id="container"> <div

    id="splash" class="page"> ... </div> <div id="content" class="page"> ... </div> </div> </body> </html>
  33. <!DOCTYPE html> <html> <head> ... </head> <body> <div id="container"> <div

    id="splash" class="page"> ... </div> <div id="content" class="page"> ... </div> </div> </body> </html>
  34. <!DOCTYPE html> <html> <head> ... </head> <body> <div id="container"> <div

    id="splash" class="page"> ... </div> <div id="content" class="page"> ... </div> </div> </body> </html>
  35. <div id="content" class="page"> <div id="map"></div> <div id="photos"> <div id="slides-container"> <div

    class="slide"></div> <div class="slide"></div> <div class="slide"></div> </div> <section id="slide-info"> <h1></h1> <div id="slide-details"> <p></p> <p class="location"></p> </div> </section> </div> </div>
  36. <div id="content" class="page"> <div id="map"></div> <div id="photos"> <div id="slides-container"> <div

    class="slide"></div> <div class="slide"></div> <div class="slide"></div> </div> <section id="slide-info"> <h1></h1> <div id="slide-details"> <p></p> <p class="location"></p> </div> </section> </div> </div>
  37. <div id="content" class="page"> <div id="map"></div> <div id="photos"> <div id="slides-container"> <div

    class="slide"></div> <div class="slide"></div> <div class="slide"></div> </div> <section id="slide-info"> <h1></h1> <div id="slide-details"> <p></p> <p class="location"></p> </div> </section> </div> </div>
  38. <div id="content" class="page"> <div id="map"></div> <div id="photos"> <div id="slides-container"> <div

    class="slide"></div> <div class="slide"></div> <div class="slide"></div> </div> <section id="slide-info"> <h1></h1> <div id="slide-details"> <p></p> <p class="location"></p> </div> </section> </div> </div>
  39. <!DOCTYPE html> <html> <head> ... </head> <body> <div id="container"> <div

    id="splash" class="page"> ... </div> <div id="content" class="page"> ... </div> </div> </body> </html>
  40. Techniques

  41. Transitions Touch Events Hardware (GPS) Work Offline Sizing Distribution

  42. Transitions Touch Events Hardware (GPS) Work Offline Sizing Distribution

  43. Transitions Two ways to animate: 1. jQuery (.animate) 2. CSS

    (-webkit-transform)  
  44. Transitions Two ways to animate: 1. jQuery (.animate) 2. CSS

    (-webkit-transform)  
  45. Transitions What can we do with CSS?

  46. Transitions -webkit-transform translate scale skew rotate -webkit-animation

  47. Transitions -webkit-transform translate scale skew rotate -webkit-animation

  48. Transitions <!DOCTYPE html> <html> <head> ... </head> <body> <div id="container">

    <div id="splash" class="page"> ... </div> <div id="content" class="page"> ... </div> </div> </body> </html>
  49. Transitions -webkit-transform: translate3d(100%, 0, 0); div#splash div#content

  50. Transitions -webkit-transform: translate3d(100%, 0, 0); div#splash div#content -webkit-transition: all 600ms

    ease-in-out; -webkit-transform: translate3d(0, 0, 0);  
  51. Transitions Touch Events Hardware (GPS) Work Offline Sizing Distribution

  52. Touch Events How touch events work

  53. Touch Events Touch Multi-Touch Gesture iOS P P P Android

    2.3 P Android 3 P P Android 4 P P
  54. Touch Events Touch Multi-Touch Gesture iOS P P P Android

    2.3 P Android 3 P P Android 4 P P
  55. Touch Events touchstart touchmove touchend touchcancel

  56. Touch Events slides.bind(touchstart, function (e) { if (touchId !== null)

    { e.stopPropagation(); e.preventDefault(); } else { var touch = e.originalEvent.touches[0]; touchId = touch.identifier; touchStart(touch.clientX); } });
  57. Touch Events slides.bind(touchstart, function (e) { if (touchId !== null)

    { e.stopPropagation(); e.preventDefault(); } else { var touch = e.originalEvent.touches[0]; touchId = touch.identifier; touchStart(touch.clientX); } });
  58. Touch Events slides.bind(touchstart, function (e) { if (touchId !== null)

    { e.stopPropagation(); e.preventDefault(); } else { var touch = e.originalEvent.touches[0]; touchId = touch.identifier; touchStart(touch.clientX); } });
  59. Touch Events slides.bind(touchstart, function (e) { if (touchId !== null)

    { e.stopPropagation(); e.preventDefault(); } else { var touch = e.originalEvent.touches[0]; touchId = touch.identifier; touchStart(touch.clientX); } });
  60. Touch Events slides.bind(touchmove, function (e) { var touch = e.originalEvent.touches[0];

    if (touch.identifier == touchId) { touchMove(touch.clientX); } });
  61. Touch Events slides.bind(touchend, function (e) { var touch = e.originalEvent.changedTouches[0];

    if (touch.identifier == touchId) { touchEnd(touch.clientX); } });
  62. Touch Events function touchEnd(x) { ... if (Math.abs(deltaX) > threshold)

    { swipe(direction); } else if (Math.abs(deltaX) > Config.slideshowSwipeDistanceThreshold && touchDuration < Config.slideshowSwipeDurationThreshold) { swipe(direction); } else { snapBack(); } }
  63. Touch Events function touchEnd(x) { ... if (Math.abs(deltaX) > threshold)

    { swipe(direction); } else if (Math.abs(deltaX) > Config.slideshowSwipeDistanceThreshold && touchDuration < Config.slideshowSwipeDurationThreshold) { swipe(direction); } else { snapBack(); } }
  64. Touch Events function touchEnd(x) { ... if (Math.abs(deltaX) > threshold)

    { swipe(direction); } else if (Math.abs(deltaX) > Config.slideshowSwipeDistanceThreshold && touchDuration < Config.slideshowSwipeDurationThreshold) { swipe(direction); } else { snapBack(); } }
  65. Touch Events function touchEnd(x) { ... if (Math.abs(deltaX) > threshold)

    { swipe(direction); } else if (Math.abs(deltaX) > Config.slideshowSwipeDistanceThreshold && touchDuration < Config.slideshowSwipeDurationThreshold) { swipe(direction); } else { snapBack(); } }
  66. Touch Events function touchEnd(x) { ... if (Math.abs(deltaX) > threshold)

    { swipe(direction); } else if (Math.abs(deltaX) > Config.slideshowSwipeDistanceThreshold && touchDuration < Config.slideshowSwipeDurationThreshold) { swipe(direction); } else { snapBack(); } }
  67. Touch Events <div id="content" class="page"> <div id="map"></div> <div id="photos"> <div

    id="slides-container"> <div class="slide"></div> <div class="slide"></div> <div class="slide"></div> </div> <section id="slide-info"> <h1></h1> <div id="slide-details"> <p></p> <p class="location"></p> </div> </section> </div> </div>
  68. Touch Events

  69. Touch Events div.slide div.slide div.slide div#slides-container

  70. Touch Events div.slide div.slide div.slide div#slides-container

  71. Touch Events Gotcha #1: Android browser

  72. Touch Events Gotcha #1: Android browser

  73. Touch Events Gotcha #1: Android browser <img> vs. background-image

  74. Touch Events Gotcha #2: Mobile Safari Bounce $(document).bind('touchmove’, function(event){ event.preventDefault();

    });
  75. Touch Events Gotcha #3: Testing on Desktop var hasTouch =

    'ontouchstart' in document.documentElement; var touchstart = hasTouch ? 'touchstart' : 'mousedown'; var touchmove = hasTouch ? 'touchmove' : 'mousemove'; var touchend = hasTouch ? 'touchend' : 'mouseup';
  76. Touch Events Gotcha #4: Click Events are Slow Click Event

    vs. Touch Event
  77. Transitions Touch Events Hardware (GPS) Work Offline Sizing Distribution

  78. Hardware (GPS) JS - iOS JS - Android PhoneGap Accelerometer

    P P Compass P P Gyroscope P P Camera P Contacts P Local Storage P P P Geolocation P P P Notifications P
  79. Hardware (GPS) JS - iOS JS - Android PhoneGap Accelerometer

    P P Compass P P Gyroscope P P Camera P Contacts P Local Storage P P P Geolocation P P P Notifications P
  80. Hardware (GPS)

  81. Hardware (GPS) function displayCurrentLocation() { if (navigator.geolocation) { navigator.geolocation.getCurrentPosition( function

    (position) { var lat = position.coords.latitude; var lng = position.coords.longitude; ... // Display lat/lng } ); } };
  82. Transitions Touch Events Hardware (GPS) Work Offline Sizing Distribution

  83. Work Offline Local Storage

  84. Work Offline Gotcha: 5mb

  85. Work Offline var hasLocalStorage = (function () { try {

    localStorage.setItem('test', 'test'); localStorage.removeItem('test'); return true; } catch(e) { return false; } })();
  86. Work Offline var localStoragePhotoData = null; if (hasLocalStorage) { localStoragePhotoData

    = localStorage.getItem(key); } if (localStoragePhotoData === null) { if (hasLocalStorage) { // Save photo data to localStorage. var photoDataString = JSON.stringify(PhotoData); localStorage.setItem(key); ... } } else { // Use photo data cached in localStorage. var photoDataObject = JSON.parse(localStoragePhotoData); ... }
  87. Work Offline var localStoragePhotoData = null; if (hasLocalStorage) { localStoragePhotoData

    = localStorage.getItem(key); } if (localStoragePhotoData === null) { if (hasLocalStorage) { // Save photo data to localStorage. var photoDataString = JSON.stringify(PhotoData); localStorage.setItem(key); ... } } else { // Use photo data cached in localStorage. var photoDataObject = JSON.parse(localStoragePhotoData); ... }
  88. Work Offline var localStoragePhotoData = null; if (hasLocalStorage) { localStoragePhotoData

    = localStorage.getItem(key); } if (localStoragePhotoData === null) { if (hasLocalStorage) { // Save photo data to localStorage. var photoDataString = JSON.stringify(PhotoData); localStorage.setItem(key); ... } } else { // Use photo data cached in localStorage. var photoDataObject = JSON.parse(localStoragePhotoData); ... }
  89. Work Offline var localStoragePhotoData = null; if (hasLocalStorage) { localStoragePhotoData

    = localStorage.getItem(key); } if (localStoragePhotoData === null) { if (hasLocalStorage) { // Save photo data to localStorage. var photoDataString = JSON.stringify(PhotoData); localStorage.setItem(key); ... } } else { // Use photo data cached in localStorage. var photoDataObject = JSON.parse(localStoragePhotoData); ... }
  90. Transitions Touch Events Hardware (GPS) Work Offline Sizing Distribution

  91. Sizing Support different screens Handle rotation Hide browser chrome Disable

    zoom Disable other browser features
  92. Sizing Support different screens Easier in HTML than native %

    based layouts background-size: cover; Absolute positioning is often necessary
  93. Sizing Handle rotation $(window).resize(doSomething);

  94. Sizing Handle rotation $(window).resize(reposition);

  95. Sizing Handle rotation Gotcha: You get notification after rotation is

    complete
  96. Sizing Hide browser chrome <meta name="apple-mobile-web-app-capable" content="yes" />

  97. Sizing Disable zoom <meta name="viewport" content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0" />

  98. Sizing Disable other browser features /* turn off default press

    states */ -webkit-tap-highlight-color: transparent; /* disable save image on long press */ -webkit-touch-callout: none; /* disable selection copy/paste */ -webkit-user-select: none; /* disable dragging images */ -webkit-user-drag: none;
  99. Transitions Touch Events Hardware (GPS) Work Offline Sizing Distribution

  100. Distribution

  101. Distribution Two ways to distribute: 1. Web only Reminder to

    bookmark 2. App gallery Custom native wrapper Framework like PhoneGap  
  102. Distribution Two ways to distribute: 1. Web only Reminder to

    bookmark 2. App gallery Custom native wrapper Framework like PhoneGap  
  103. Distribution https://github.com/cubiq/ add-to-homescreen

  104. Distribution

  105. Distribution <link rel="stylesheet" href="styles/ add2home.css"> <script type="application/javascript" src="scripts/external/add2home.js"></script>

  106. Distribution Home Screen Icon

  107. Distribution 144 x 144

  108. Distribution <link rel="apple-touch-icon" href="icon.png"/>

  109. Transitions Touch Events Hardware (GPS) Work Offline Sizing Distribution

  110. Go explore.

  111. Thanks! MINNEWEBCON 2012 @skirchmeier @mikebollinger

  112. APP: http://livefront.com/explore SOURCE CODE: http://github.com/livefront/ minnewebcon2012-explore SLIDES: http://livefront.com/explore/slides.pdf