http://slides.com/tylergraf/mobile-web-winners/live

Tyler Graf

Front-end at Familysearch.org

The Mobile Web

Creating Experiences for Winners

win•ner - 'winər/

noun

A person or thing that acquires a successful result in a contest, conflict, or other endeavor.

 

(See also: Leonardo Dicaprio) 

What makes the best mobile web experience?

Native-like Experience

  • polished ui (nice animations, usability, etc.)
  • Performance (60fps)
  • Instant touch feedback
  • No page loading
  • Local data (unless being fetched)
  • Remote connection not required
  • push
  • full screen

Jakob Nielsen

 

Response Times: The 3 Important Limits

1993

What are the 3 limits?

Limits

Determined by Human Perception

100

milliseconds

1000

milliseconds

10

seconds

Reaction Times

100 ms

About the limit for having the user feel that the system is reacting instantaneously, no special feedback is necessary except to display the result.

1000 ms

About the limit for the user's flow of thought to stay uninterrupted, even though the user will notice the delay. Normally, no special feedback is necessary during delays of more than 100ms but less than 1 second, but the user does lose the feeling of operating directly on the data.

10 s

About the limit for keeping the user's attention focused on the dialogue. For longer delays, users will want to perform other tasks while waiting for the computer to finish, so they should be given feedback indicating when the computer expects to be done. Feedback during the delay is especially important if the response time is likely to be highly variable, since users will then not know what to expect.

Animations

Why animate?

  • Polish

  • Context

Animations should never inhibit the user experience

Rule #1

Animation Length

Should depend on:

  • Uniquness
  • Frequency
Frequent ≤ 300ms
Unique ≥ 1s (go for it)

!Winner

Performance

60fps

Inertia Scrolling

How?

nav {
  overflow-x: scroll; /* has to be scroll, not auto */
  -webkit-overflow-scrolling: touch;
}

Target Size

Icons

=

Large Target Size

=

Events

Touch Events

  • touchstart
  • touchmove
  • touchend
  • click
  • dblclick
  • mousedown
  • mousemove
  • mouseup
  • click
  • dblclick

Mouse Events

300ms Delay

Why?

ng-tap

(function(angular) {
  'use strict';

  var directives = angular.module('app.directives', []);

  directives.directive('ngTap', function() {
    return function(scope, element, attrs) {
      var tapping;
      tapping = false;
      element.bind('touchstart', function(e) {
        element.addClass('active');
        tapping = true;
      });
      element.bind('touchmove', function(e) {
        element.removeClass('active');
        tapping = false;
      });
      element.bind('touchend', function(e) {
        element.removeClass('active');
        if (tapping) {
          scope.$apply(attrs['ngTap'], element);
        }
      });
    };
  });
})(angular);

https://gist.github.com/mhuneke/4026406

Cache Data

When you can

(function() {
'use strict';

  var ngModule = angular.module("PeopleCache", ['angular-cache']);

  ngModule.factory('PeopleCache',['CacheFactory', function(CacheFactory){
    function options(){
      if (!CacheFactory.get('peopleCache')) {
	CacheFactory.createCache('peopleCache', {
          deleteOnExpire: 'aggressive',
          recycleFreq: 60000,
          storageMode: 'sessionStorage',
          storagePrefix: 'fs'
        });
      }
      return CacheFactory.get('peopleCache');
    }
    return options;
  }]);

})();
(function(){
  'use strict';

  var ngModule = angular.module('PeopleController', ['PeopleCache']);

  ngModule.controller('PeopleController', ['$scope','PeopleCache', 'PeopleService',
    function($scope, PeopleCache, PeopleService) {
      var peopleCache = PeopleCache();

      $scope.dataList = [];

      if(peopleCache.get('people')){
        $scope.dataList = _.sortBy(peopleCache.get('people'), 'name');
      } else {
        PeopleService.getPeople
          .then(function(data){
            peopleCache.put('people', data);

            $scope.dataList = _.sortBy(data.people, 'name');
          });
      }
      
    }]);
})();

No Connection Required

App Cache

CACHE MANIFEST
# 2010-06-18:v2

# Explicitly cached 'master entries'.
CACHE:
/favicon.ico
index.html
stylesheet.css
images/logo.png
scripts/main.js

# Resources that require the user to be online.
NETWORK:
*

# static.html will be served if main.py is inaccessible
# offline.jpg will be served in place of all images in images/large/
# offline.html will be served in place of all other .html files
FALLBACK:
/main.py /static.html
images/large/ images/offline.jpg
<html manifest="http://www.example.com/example.mf">
  ...
</html>

Fully Implemented

Deprecated from HTML5

Service Worker

What's a service worker?

Multiple Libraries

Polymer

Push

Web Sockets!

  • Meteor.js

  • RethinkDB

Full Screen

Full Screen API

function toggleFullScreen() {
  var doc = window.document;
  var docEl = doc.documentElement;

  var requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullScreen || docEl.msRequestFullscreen;
  var cancelFullScreen = doc.exitFullscreen || doc.mozCancelFullScreen || doc.webkitExitFullscreen || doc.msExitFullscreen;

  if(!doc.fullscreenElement && !doc.mozFullScreenElement && !doc.webkitFullscreenElement && !doc.msFullscreenElement) {
    requestFullScreen.call(docEl);
  }
  else {
    cancelFullScreen.call(doc);
  }
}

http://www.html5rocks.com/en/mobile/fullscreen/

caniuse?

Launch in Full Screen

Requires user action

iOS

<!-- Allow web app to be run in full-screen mode. -->
    <meta name="apple-mobile-web-app-capable"
          content="yes">

    <!-- Make the app title different than the page title. -->
    <meta name="apple-mobile-web-app-title"
          content="iOS 8 web app">

    <!-- Configure the status bar. -->
    <meta name="apple-mobile-web-app-status-bar-style"
          content="black">

    <!-- Set the viewport. -->
    <meta name="viewport"
          content="initial-scale=1">

    <!-- Disable automatic phone number detection. -->
    <meta name="format-detection"
          content="telephone=no">

    <!-- ICONS -->

    <!-- iPad retina icon -->
    <link href="https://placehold.it/152"
          sizes="152x152"
          rel="apple-touch-icon-precomposed">

    <!-- iPad retina icon (iOS < 7) -->
    <link href="https://placehold.it/144"
          sizes="144x144"
          rel="apple-touch-icon-precomposed">

    <!-- iPad non-retina icon -->
    <link href="https://placehold.it/76"
          sizes="76x76"
          rel="apple-touch-icon-precomposed">

    <!-- iPad non-retina icon (iOS < 7) -->
    <link href="https://placehold.it/72"
          sizes="72x72"
          rel="apple-touch-icon-precomposed">

    <!-- iPhone 6 Plus icon -->
    <link href="https://placehold.it/180"
          sizes="120x120"
          rel="apple-touch-icon-precomposed">

    <!-- iPhone retina icon (iOS < 7) -->
    <link href="https://placehold.it/114"
          sizes="114x114"
          rel="apple-touch-icon-precomposed">

    <!-- iPhone non-retina icon (iOS < 7) -->
    <link href="https://placehold.it/57"
          sizes="57x57"
          rel="apple-touch-icon-precomposed">

    <!-- STARTUP IMAGES -->

    <!-- iPad retina portrait startup image -->
    <link href="https://placehold.it/1536x2008"
          media="(device-width: 768px) and (device-height: 1024px)
                 and (-webkit-device-pixel-ratio: 2)
                 and (orientation: portrait)"
          rel="apple-touch-startup-image">

    <!-- iPad retina landscape startup image -->
    <link href="https://placehold.it/1496x2048"
          media="(device-width: 768px) and (device-height: 1024px)
                 and (-webkit-device-pixel-ratio: 2)
                 and (orientation: landscape)"
          rel="apple-touch-startup-image">

    <!-- iPad non-retina portrait startup image -->
    <link href="https://placehold.it/768x1004"
          media="(device-width: 768px) and (device-height: 1024px)
                 and (-webkit-device-pixel-ratio: 1)
                 and (orientation: portrait)"
          rel="apple-touch-startup-image">

    <!-- iPad non-retina landscape startup image -->
    <link href="https://placehold.it/748x1024"
          media="(device-width: 768px) and (device-height: 1024px)
                 and (-webkit-device-pixel-ratio: 1)
                 and (orientation: landscape)"
          rel="apple-touch-startup-image">

    <!-- iPhone 6 Plus portrait startup image -->
    <link href="https://placehold.it/1242x2148"
          media="(device-width: 414px) and (device-height: 736px)
                 and (-webkit-device-pixel-ratio: 3)
                 and (orientation: portrait)"
          rel="apple-touch-startup-image">

    <!-- iPhone 6 Plus landscape startup image -->
    <link href="https://placehold.it/1182x2208"
          media="(device-width: 414px) and (device-height: 736px)
                 and (-webkit-device-pixel-ratio: 3)
                 and (orientation: landscape)"
          rel="apple-touch-startup-image">

    <!-- iPhone 6 startup image -->
    <link href="https://placehold.it/750x1294"
          media="(device-width: 375px) and (device-height: 667px)
                 and (-webkit-device-pixel-ratio: 2)"
          rel="apple-touch-startup-image">

    <!-- iPhone 5 startup image -->
    <link href="https://placehold.it/640x1096"
          media="(device-width: 320px) and (device-height: 568px)
                 and (-webkit-device-pixel-ratio: 2)"
          rel="apple-touch-startup-image">

    <!-- iPhone < 5 retina startup image -->
    <link href="https://placehold.it/640x920"
          media="(device-width: 320px) and (device-height: 480px)
                 and (-webkit-device-pixel-ratio: 2)"
          rel="apple-touch-startup-image">

    <!-- iPhone < 5 non-retina startup image -->
    <link href="https://placehold.it/320x460"
          media="(device-width: 320px) and (device-height: 480px)
                 and (-webkit-device-pixel-ratio: 1)"
          rel="apple-touch-startup-image">

https://gist.github.com/tfausak/2222823

Chome Full Screen Manifest

{
  "short_name": "Kinlan's Amaze App",
  "name": "Kinlan's Amazing Application ++",
  "icons": [
    {
      "src": "launcher-icon-2x.png",
      "sizes": "96x96",
      "type": "image/png"
    },
    {
      "src": "launcher-icon-3x.png",
      "sizes": "144x144",
      "type": "image/png"
    },
    {
      "src": "launcher-icon-4x.png",
      "sizes": "192x192",
      "type": "image/png"
    }
  ],
  "start_url": "/index.html",
  "display": "standalone",
  "orientation": "landscape"
}

https://developers.google.com/web/updates/2014/11/Support-for-installable-web-apps-with-webapp-manifest-in-chrome-38-for-Android?hl=en

<link rel="manifest" href="/manifest.json">

Native-like Experience

  • polished ui
  • Performance (60fps)
  • Instant touch feedback
  • No page loading
  • Local data (unless being fetched)
  • Remote connection not required
  • push
  • full screen

Questions?

Love you guys.

(slow clap out)

tylergraf@gmail.com

@tylergraf

https://slides.com/tylergraf/mobile-web-winners

The Mobile Web: Creating Experiences for Winners

By Tyler Graf

The Mobile Web: Creating Experiences for Winners

  • 1,179