Web&Co
2016
Gonzalo Ruiz de Villa
@gruizdevilla
@gft_es
Experiencias de GFT
BBVA Cells
Bankia AAD
Mapfre GAIA
Banco Sabadell
Angular
Actualmente estamos creando definiendo las nuevas arquitecturas basadas en Angular en tres clientes (tres en España y uno en Brasil)
GFT & the community
Agenda
- HTTP 2.0
- Progressive Web Apps
- Physical Web
- Frameworks
- Polymer
- Angular
- Material Design
- RAIL
HTTP/2
a.k.a. h2
Hay que cambiar ya
HTTP 0.9 - 1991
(GET, POST, HEAD)
HTTP 1.0 - 1996
(PUT, DELETE, LINK, UNLINK)
HTTP 1.1 - 1997
(OPTIONS, HOST Header, Keep-Alive)
¿Optimizaciones?
Minimizar, contatenar, gzip, sprites, vulcanizar, inline, ...
¿O más bien hacks?
HOL Blocking
(Head Of Line Blocking)
un recurso cada vez
https://http2.golang.org/gophertiles?latency=30
Metadatos:
Repetición de cabeceras
GET /index.html
User-Agent: Mozilla/5.0 ...
Cookie: __atuvc=4%7C49; ...
Referer: http://www.3pi ...
GET /script.js
User-Agent: Mozilla/5.0 ...
Cookie: __atuvc=4%7C49; ...
Referer: http://www.3pi ...
GET /styles.css
User-Agent: Mozilla/5.0 ...
Cookie: __atuvc=4%7C49; ...
Referer: http://www.3pi ...
HPACK
Compresión de cabeceras para http
SPDY - 2012
http/2 - 2015
Técnicas que ya NO aplican:
-
inlining
-
concatenación
-
sprites
-
vulcanización
Técnicas que ya NO gracias a HPACK:
-
SHARDING
-
CDN
Técnicas que ya NO aplican:
-
SHARDING
-
CDN
Aún hay más
que puedes tener en cuenta al construir una aplicación
PUSH
Sigue siendo necesario:
- GZIP/Deflate
- First Render
- DNS Lookup
- Cache-Control
CANIUSE
¿AHORA? SI
¿POR QUÉ? #PERFMATTERS
¿CÓMO?
PROGRESSIVE
WEB APPS
Offline web apps
Y otros intentos:
- WinJS
- WebOS
- ReactNative
- Chrome Package Apps
Fricción
Tradeoff:
- cambia el deployment y el modelo de uso
- no funcionan con un enlace
Si no puedes enlazar algo no forma parte de la web
- 25 apps usadas mensualmente por un usuario
- 100+ sitios webs visitados por un usuario medio con Chrome sobre Android
In a consumer mobile app, every step you make a user perform before they get value out of your app will cost you 20% of users.
http://blog.gaborcselle.com/2012/10/every-step-costs-you-20-of-users.html
¿que es lo que echamos de menos? Fundamentalmente:
- funcionar offline
- aparecer en la pantalla de inicio
- mensajes push
¿Es lo mismo que un bookmark?
example.com/
vs
example.com/date/title
Service worker
Manifest start_url debe funcionar siempre
TLS
El sitio debe tener un origen seguro
Engament
Dos visitas de 5+ minutos
Manifiesto
Con las propiedades adecuadas
Las progressive web apps:
Son parte de la web real
Forman parte de una iniciativa de estandarización
Y son muy adecuadas para móviles económicos, típicos de mercados emergentes
Web Manifest
Incluye metadatos para una Progressive Web App, como:
- iconos
- descripción
- colores
- ...
El manifiesto se referencia en <head>
<link rel="manifest" href="/manifest.json">
Media querys
@media (display-mode: standalone) {
h3:after {content: "¡En modo standalone!"}
}
{
"name": "Progressive Web App Demo",
"short_name": "Demo",
...
"start_url": "/simple-demo/?home=true"
}
Manifest generator
Manifest validator
A considerar:
Refresh, atrás y adelante
Compartir la UI, copiando un enlace
onbeforeinstall Event
Deep linking
Instant Loading
App Shell
+
Service Workers
Los Service Workers permiten eliminar la red de la ecuación
(en visitas posteriores)
1s de retraso de la carga de una pagina
11% páginas vistas menos
16% de reducción en satisfacción del cliente
http://www.radware.com/Products/FastView/
Proporcionar una experiencia de carga instantánea y consistente
¿Qué es la App Shell?
HTML: /shell
+
JavaScript: /app.js
+
Estilos: /styles.css
¿Qué es un Service Worker?
Una buena analogía es la de un controlador aéreo
Web page
Service Workers
Network
Cache
Ciclo de vida de un Service Worker
- No hay Service Worker
-
Evento install
<head>
<style>
/* estilos en linea */
</style>
</head>
<body>
<!-- punto de inserción de la plantilla -->
<script src="app.js">
</body>
/shell
Ciclo de vida de un Service Worker
- No hay Service Worker
-
Evento "install"
-
Evento "activate" (limpieza de cache)
-
Espera
-
Evento "fetch"
-
Volver a 4
-
Stop
Dos librerías para facilitar la vida
- sw-precache
- sw-toolbox
https://developers.google.com/web/showcase/case-study/service-workers-iowa
Dos librerías para facilitar la vida
- sw-precache (precache de recursos todavía no pedidos)
- sw-toolbox (para facilitar todo lo demás)
Notificaciones PUSH
Alcance y engagement
Las experiencias actuales consiguen aumentar el tráfico un 50%
¿Cómo funciona?
Service Workers
Service Worker
Web server
Service Worker
Web Server
Push Service
navigator.serviceWorker.ready.then(function(serviceWorkerRegistration) {
serviceWorkerRegistration.pushManager.subscribe()
.then(function(subscription) {
// The subscription was successful
return sendSubscriptionToServer(subscription);
})
.catch(function(e) {
//Permission denied or an error occurred
});
});
Service Worker
Web Server
Push Service
self.addEventListener('push', function(event) {
event.waitUntil(
fetch('/notification.json').then(function(response) {
return response.json();
}).then(function(data) {
return self.registration.showNotification(
data.title,
{
body: data.body,
icon: data.iconUrl,
tag: data.tag
}
);
});
);
});
Importante
Urgente
"Usa mi aplicación"
Comienza una emisión online
Ha llegado una domiciliación
Los valores caen por debajo de un límite
Foco en ventanas existentes
clients.matchAll({
type: 'window'
})
.then(function(clients) {
if (clients.length > 0) {
clients[0].postMessate({naviegateTo:url})
} else {
return clients.openWindow(url);
}
});
WEB OF THINGS
Physical Web
Web Bluetooth
Device
Service
Characteristic
navigator.bluetooth.requestDevice({
filters: [{
services:['battery_service']
}]
})
navigator.bluetooth.requestDevice({
filters: [{
services:['battery_service']
}]
})
.then(device => {
log('Connection to ' + device.name);
return devie.connectGATT();
})
.then(gattServer => gattServer.getPrimaryService('battery_service'))
.then(service => service.getCharacteristic('battery_level'))
.then(characteristic => characteristic.readValue())
.then(buffer => {
let batteryLevel = buffer.getUint8(0);
log('> Battery Level is' + batteryLevel + '%');
})
characteristic.startNotifications().then(() => {
characteristic.addEventListener(
'characteristicvaluechanged',
handleNotifications
);
});
function handleNotifications(event) {
let buffer = event.target.value;
//...
}
Notificaciones
bluetoothDevice.request()
.then(() => bodySensorLocation.read());
Polymer element:
platinum-bluetooth
<platinum-bluetooth-device services-filter='["battery_service"]'>
<platinum-bluetooth-characteristic
service='battery_service'
characteristic='battery_level'>
</platinum-bluetooth-characteristic>
</platinum-bluetooth-device>
<span>{{bodySensorLocation}}</span>
bluetoothDevice.request()
.then(() => bodySensorLocation.read());
Polymer element:
platinum-bluetooth
<platinum-bluetooth-device services-filter='["battery_service"]'>
<platinum-bluetooth-characteristic
service='battery_service'
characteristic='battery_level'>
</platinum-bluetooth-characteristic>
</platinum-bluetooth-device>
<span>{{bodySensorLocation}}</span>
BLE Peripheral Simulator
Se necesita un servicio de descubrimiento sobre la barra de direcciones
1. Descubrir
2. Recuperar y ordenar
https://paradas.bus/p123
https://paradas.bus/p123
https://paradas.bus/p123
https://paradas.bus/p123
El puente entre la web y los dispositivos físicos
Es solo una web
Una web por dispositivo
Sin interrupciones
¿Son códigos QR?
¿SPAM?
Eddystone
UID
TLM
URL
Escenarios
- Web Bluetooth
- Classic Web
- Cloud Passthrough
Frameworks
POLYMER
WEBCOMPONENTS
- Custom Elements
- Templates
- HTML Imports
- Shadow DOM
WEBCOMPONENTS
<my-tabstrip>
<my-tab>
Home
</my-tab>
<my-tab>
Services
</my-tab>
<my-tab>
Contact Us
</my-tab>
<my-tabstrip>
¿QUÉ ES POLYMER?
Una librería "opinionated" para construir nuevos webcomponents
<link rel="import"
href="bower_components/polymer/polymer.html">
<script>
// register a new element called proto-element
Polymer({
is: "proto-element",
// add a callback to the element's prototype
ready: function() {
this.textContent = "I'm a proto-element. Check out my prototype!"
}
});
</script>
<!DOCTYPE html>
<html>
<head>
<script src="bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
<link rel="import" href="proto-element.html">
</head>
<body>
<proto-element></proto-element>
</body>
</html>
<link rel="import"
href="bower_components/polymer/polymer.html">
<dom-module id="dom-element">
<template>
<p>I'm a DOM element. This is my local DOM!</p>
</template>
<script>
Polymer({
is: "dom-element"
});
</script>
</dom-module>
<link rel="import"
href="bower_components/polymer/polymer.html">
<dom-module id="picture-frame">
<template>
<!-- scoped CSS for this element -->
<style>
div {
display: inline-block;
background-color: #ccc;
border-radius: 8px;
padding: 4px;
}
</style>
<div>
<!-- any children are rendered here -->
<content></content>
</div>
</template>
<script>
Polymer({
is: "picture-frame",
});
</script>
</dom-module>
<!DOCTYPE html>
<html>
<head>
<script src="bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
<link rel="import" href="picture-frame.html">
</head>
<body>
<picture-frame>
<img src="images/p-logo.svg">
</picture-frame>
</body>
</html>
<link rel="import"
href="bower_components/polymer/polymer.html">
<dom-module id="name-tag">
<template>
<!-- bind to the "owner" property -->
This is <b>{{owner}}</b>'s name-tag element.
</template>
<script>
Polymer({
is: "name-tag",
ready: function() {
// set this element's owner property
this.owner = "Daniel";
}
});
</script>
</dom-module>
<link rel="import"
href="bower_components/polymer/polymer.html">
<!-- import the iron-input custom element -->
<link rel="import"
href="bower_components/iron-input/iron-input.html">
<dom-module id="editable-name-tag">
<template>
<p>
This is a <strong>{{owner}}</strong>'s editable-name-tag.
</p>
<!-- iron-input exposes a two-way bindable input value -->
<input is="iron-input" bind-value="{{owner}}"
placeholder="Your name here...">
</template>
<script>
Polymer({
is: "editable-name-tag",
properties: {
owner: {
type: String,
value: "Daniel"
}
}
});
</script>
</dom-module>
POLYMER CATALOG
- Fe: Iron Elements: Polymer core elements
- Md: Paper elements: Material Design elements
- Go: Google Web Components: For Google Api's and services
- Au: Gold Elements: e-commerce elements
- Ne: Neon elements: Animation and special effects
- Pt: Platinum elements: Offline, Push and more
- Mo: Molecules: wrappers de librerías terceras
Angular
AngularJS
Angular 2
Web
Mobile Web
Android
iOS
Windows
Mac
Linux
- pensado para construir aplicaciones del tamaño que sea
- mobile first
- rendimiento brutal
- sencillo y expresivo
- inmutabilidad
- inyección de dependencias jerárquica
- integración con webcomponents
- i18n
- server side rendering
- web workers
- aplicaciones nativas, de escritorio
- routers jerárquicos y paralelos
- PWA
- Angular CLI
import {Component, bootstrap} from 'angular2/angular2';
import {TodoList} from './todoList.ts';
@Component({
selector: 'app',
directives: [TodoList],
template: `
<h1>Todo App</h1>
<todo-list></todo-list>
`
})
class AppComponent {
constructor() {
}
}
bootstrap(AppComponent);
import {Component, CORE_DIRECTIVES} from 'angular2/angular2';
import {TodoItem} from './todoItem.ts';
@Component({
selector: 'todo-list',
template: `
<ul>
<todo-item
*ng-for="#todo of todos"
[todo]="todo"></todo-item>
</ul>
`,
directives: [CORE_DIRECTIVES, TodoItem]
})
export class TodoList {
public todos: Array<any>;
constructor() {
this.todos = TODOS;
}
}
var TODOS = [
{
content: 'Discuss new feature',
isCompleted: false
},
{
content: 'Fix issue',
isCompleted: false
}
]
import {Component} from 'angular2/angular2';
@Component({
selector: 'todo-item',
properties: ['todo'],
template: `
<li class="todo-item">
{{todo.content}}
</li>
`
})
export class TodoItem {
}
export class TodoService {
private _todos;
constructor() {
this._todos = todos;
}
getTodos() {
return this._todos;
}
completed(todo) {
todo.isCompleted = todo.isCompleted ? false : true;
}
delete(todo) {
var index = this._todos.indexOf(todo);
this._todos.splice(index, 1);
}
add(content: string) {
var todo = {
content: content,
createdAt: new Date(),
isCompleted: false
}
this._todos.push(todo);
}
}
var todos = [
{
content: 'Write Angular 2 Blog',
createdAt: new Date(),
isCompleted: false
},...
]
...
import {TodoService} from './todoService.ts';
...
export class TodoList {
constructor(private todoService: TodoService) {
}
}
import {Component, CORE_DIRECTIVES} from 'angular2/angular2';
import {TodoService} from './todoService.ts';
@Component({
selector: 'todo-item',
properties: ['todo'],
directives: [CORE_DIRECTIVES],
styles: [
`
.todo-item.completed{
text-decoration: line-through;
}
`
],
template: `
<li [ng-class]="getTodoItemClass(todo)">
{{todo.content}}
-
<a (click)="todoService.completed(todo)">Completed</a>
-
<a (click)="todoService.delete(todo)">Delete</a>
</li>
`
})
export class TodoItem {
constructor(private todoService: TodoService) {
}
getTodoItemClass(todo) {
return {
completed: todo.isCompleted === true,
"todo-item": true
}
}
}
@Component({
selector: 'modal',
...
template: `
<div>
<h1 #inicio tabindex="-1">Título modal</h1>
...
<div (focus)="inicio.focus()" tabindex="0"></div>
</div>
`
})
export class Modal {
//...
}
Ventana modal accesible con control de foco de teclado
- Native UX - no compromises
- Native Performance
- Native UI components
- 100% Access to Native Platform API
Material Design
Un mundo 3D
Movimientos auténticos
Al percibir la naturaleza tangible de un objeto ayuda a entender como manipularlo. El movimiento de un objeto transmite esta naturaleza: es ligero o pesado, flexible o rígido, grande o pequeño.
El movimiento no es solo algo bonito, proporciona información de relaciones espaciales, funcionalidad e intencionalidad del sistema.
Interacciones responsivas
Interacciones responsivas
Acción radial
Transiciones significativas
Jerarquía temporal
Detalles y más detalles
EJEMPLOS
Compromisos en web
Cada vez menos
RAIL
Delay | User reaction |
---|---|
0-100ms | Instantáneo |
100-300ms | Retraso vagamente perceptible |
300ms-1s | Retraso perceptible, mantiene foco |
1s+ | Cambio de contexto mental |
10s+ | Ya vol |
60 fps
Reponse
Animation
Idle
Load
Response
100ms percepción de respuesta instantánea
Animación
16 ms para realizar el trabajo antes de cada frame
8~10 ms en la práctica
Idle
50ms para responder a la interacción de un usuario
Load
1s para que el usuario no se despiste
Response | Animation | Idle | Load |
---|---|---|---|
Tap to paint menos de 100ms |
Cada frame se pinta en menos de 16ms | Usar el tiempo ocioso para programar trabajo de forma proactiva | Satisfacer los objetivos de "Response" en la carga |
Drag to paint en menos de 16ms | Cada bloque de trabajo en 50ms | Conseguir pintar algo con sentido en menos de 1000ms |
RAIL pone al usuario en el centro
¡GRACIAS!
Web&Co @ GFT
By Gonzalo Ruiz de Villa
Web&Co @ GFT
- 3,718