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)
Response Times: The 3 Important Limits
1993
Determined by Human Perception
milliseconds
milliseconds
seconds
About the limit for having the user feel that the system is reacting instantaneously, no special feedback is necessary except to display the result.
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.
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 should never inhibit the user experience
Should depend on:
Frequent ≤ 300ms
Unique ≥ 1s (go for it)
60fps
nav {
overflow-x: scroll; /* has to be scroll, not auto */
-webkit-overflow-scrolling: touch;
}
(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
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');
});
}
}]);
})();
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>
What's a service worker?
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/
Requires user action
<!-- 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
{
"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">
(slow clap out)
tylergraf@gmail.com
@tylergraf
https://slides.com/tylergraf/mobile-web-winners