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