[HTML5] TP pour développeur migrant sur HTML5
Introduction
Suite a mon article expliquant que HTML5 allait devenir le Golden Language pour faire du B2C, il me fallait réagir et vous proposer un post sur le sujet!
Ce billet est a destination des développeurs .NET/XAML souhaitant passer sur tout ce qui gravite autour de HTML5.
Le but sera de concevoir une sorte de “home” a la Windows 8, avec des tuiles pour afficher les derniers billets d’un flux RSS, avec version pour mobile/tablette ainsi qu’une version offline et Windows 8.
Mais pour bien commencer, je vous recommande de regarder cette vidéo pour avoir une vue d’ensemble de ces langages:
Rendez vous des Experts - Découverte de HTML5
Fonctionnalité et design
Le but de cette application va être de créer un site d’agrégation:
- Un design métro, avec un fond avec photo aléatoire
- Une compatibilité IE10/Mozilla/Chrome/Tablette iPad et Android
- La possibilité d’ajouter la page en favoris, favicon and co
- Deux box, l’une présentant les flux RSS de mes deux blogs
- L’autre étant des liens vers projets et autres
- Le tout dans des tuiles animées en CSS3
- Version offline et stockage des articles hors ligne
- Utilisation d’un maximum de features HTML5
- Version spéciale compilée Windows 8 (sera l’objet d’un autre poste)
- Version optimisée pour téléphone avec Media Queries et KendoUI
- Utilisation d’un max de Framework du moment comme LESS/Knockout.js and co
- Consommation de toutes les données sur un service Node.js
http://julien.dollon.net/image.axd?p...umb2_thumb.jpg
Développement
Création de la structure de la page en HTML5
La première chose que je vais réaliser, est la structure de la page. HTML5 apporte son lot de nouvelles balises comme <header>, <nav> et bien d’autres mais cela ce comporte comme des divs.
Ma page HTML5 va ressembler à cela:
http://julien.dollon.net/image.axd?p...umb3_thumb.png
La structure reste simple: un header, un footer et au milieu un contenu représenté par une bullet liste.
Le header prendra cette forme:
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
<header id="headertop">
<div id="backgroundtitle">
<img src="Assets/images/logo.png" />
</div>
<div id="title">
<img src="Assets/images/logo2.png" /><label> Home</label></div>
<div id="loginbox">
<div id="username"></div>
</div>
</header> |
Le footer, tout aussi simple:
Code:
1 2 3 4 5
|
<footer id="bottomfooter">
<div id="plusmenu">
<img src="Assets/images/heart.png" /></div>
</footer> |
Et au milieu, une bullet liste, avec pour chaque petites tuiles une <div>:
Code:
1 2 3 4 5 6 7 8 9 10 11
|
<div id="box3">
<nav class="navitems , secondnavitems">
<header class="headerclass">Projects</header>
<ul>
<li>
<div class="box">
<div class="image"><img src="" alt="" /></div> <div class="nomapp">Blog</div>
</div>
</li>
//etc... |
Organisation de la page avec CSS3
Avant de commencer, je vous conseil vivement de travailler avec LESS. LESS est un framework JS ou un compilateur CSS permettant d’améliorer le langage. Il est désormais possible de déclarer des variables, faire des conditions, de l’héritage… Pour l’exploiter plusieurs solutions: soit en JavaScript, qui se chargera au runtime de “compiler” le CSS. Soit en utilisant un outil qui le fera avant la publication du site. Si vous êtes sous Visual Studio, une extension existe: Dotless mais j’ai un petit faible pour http://crunchapp.net/.
http://julien.dollon.net/image.axd?p...1%5D_thumb.png
Le CSS3 apporte son lot de nouveautés très appréciables.
J’ai eu l’occasion d’en utiliser quelques unes:
Importation de font
Code:
1 2 3 4 5 6 7
|
@principalfont: Segoe UI;
@font-face
{
font-family:@principalfont;
src: url('../Font/SEGOEUI.TTF');
} |
Personnalisation d’une scrollbar
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
::-webkit-scrollbar {
width: 12px;
background-color:Gray;
}
::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.5);
} |
Utilisation des animations
Code:
1 2 3 4 5 6 7 8
|
.anim {
-webkit-animation: monanim 80s infinite;
-moz-animation: monanim 80s infinite;
-ms-animation: monanim 80s infinite;
-o-animation: monanim 80s infinite;
animation: monanim 80s infinite;
} |
Sélection de contrôles et états
Code:
1 2 3 4 5 6 7 8 9
| #username:hover
{
opacity: 1;
}
.navitems > ul > li:nth-child(3n) > div
{
background-color: #db532b;
} |
Utilisation de Box pour aligner les éléments
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
#site
{
display: box; /* not supported by IE9, use table if we want IE support */
box-orient:horizontal;
box-pack:left;
box-align:top;
/* Firefox */
display:-moz-box;
-moz-box-orient:horizontal;
-moz-box-pack:left;
-moz-box-align:top;
/* Safari and Chrome */
display:-webkit-box;
-webkit-box-orient:horizontal;
-webkit-box-pack:left;
-webkit-box-align:top;
/* futur IE10, that's sucks */
display: -ms-box;
} |
Transformations 3D pour simuler un effet tilt:
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13
|
.tilt {
-webkit-transition: -webkit-transform 0.1s linear;
-webkit-transform: rotate3d(20, 100, 0, -20deg);
-moz-transition: -moz-transform 0.1s linear;
-moz-transform: rotate3d(20, 100, 0, -20deg);
-o-transition: -o-transform 0.1s linear;
-o-transform: rotate3d(20, 100, 0, -20deg);
-ms-transition: -ms-transform 0.1s linear;
-ms-transform: rotate3d(20, 100, 0, -20deg);
transition: transform 0.1s linear;
transform: rotate3d(20, 100, 0, -20deg);
} |
Résultat avec le CSS:
http://julien.dollon.net/image.axd?p...umb8_thumb.png
Quelques codes JavaScript ont du être ajoutés pour mettre en forme certaines choses, par exemple pour coller la barre de défilement par rapport a la div <content>:
Avant http://julien.dollon.net/image.axd?p...umb9_thumb.png, après http://julien.dollon.net/image.axd?p...mb10_thumb.png
A ce stade, l’intégralité du code se trouve ici:
http://julien.dollon.net/html5/beta/
Vous pouvez le tester exclusivement avec Mozilla/Chrome/IE11 car mon composant Box n’est supporté que par les derniers navigateurs.
Capter le scroll de la souris et le clavier
J’ai voulu que le scroll de la souris provoque non pas le défilement en vertical de la page, mais en horizontal.
Pour cela il a fallut capter MouseWheel de la souris, trouver le sens, annuler la propagation de l’évènement pour annuler le défilement de la fenêtre et simuler le défilement de manière horizontal sur la div principale.
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| var iScroll = 0;
var lastScroll = 0;
$('#content').bind('mousewheel', function(event, delta) {
$(this).scrollLeft(100 * -delta + (100 * iScroll));
//Si a la fin
if (!($(this).scrollLeft() == lastScroll)) {
iScroll += -delta;
}
lastScroll = $(this).scrollLeft();
event.stopPropagation();
}); |
Il peut être aussi intéressant de capter les évènements du clavier pour reproduire la même action. Forcement comme dab’ chaque navigateurs implémentent les évènements comme ils le veulent…
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| $(document).bind('keydown', function (event) {
event = event || window.event;
var key = event.keyCode ? event.keyCode : event.which ? event.which : event.charCode;
switch (key) {
case 37: // left arrow key
ScrollAnimated(5);
break;
case 39: // right arrow key
ScrollAnimated(-5);
break;
}
event.stopPropagation();
}); |
Proposer un bouton “fullscreen”
Voici un petit script pour mettre le site en fullscreen.
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function InitFullScreenButton() {
$("#pluscentre").click(function () {
//Chrome/Safari
if (document.body.webkitRequestFullScreen != null)
document.body.webkitRequestFullScreen();
//Mozilla
else if (document.body.mozRequestFullScreen != null)
document.body.mozRequestFullScreen();
//Opera
else if (document.body.oRequestFullScreen != null)
document.body.oRequestFullScreen();
//IE11
else if (document.body.msRequestFullScreen != null)
document.body.msRequestFullScreen();
else if (document.body.requestFullscreen != null)
document.body.requestFullscreen();
else
alert("Your bullshit browser doesn't support the fullscreen features");
}); |
Création d’une tuile avec Adobe Edge
Pour la tuile, il existe deux solutions.
Dans un premier temps, je vais utiliser Edge en Preview de Adobe afin de vous montrer les capacités de ce logiciel. Edge est une sorte de Expression Blend version HTML5/JavaScript (jQuery pour être plus précis).
L’avantage de cet outil est qu’il ressemble fortement a Blend. L’inconvénient est que les animations ne sont pas faites pour être éditées par la suite, c’est pourquoi, dans une second temps, je préfère opte pour une tuile faite main.
http://www.blogcdn.com/www.engadget....raph-paper.jpg
Apres avoir lancé Edge, je crée un nouveau projet et je commence par dessiner un petit rectangle représentant ma tuile:
http://julien.dollon.net/image.axd?p...umb1_thumb.png
http://julien.dollon.net/image.axd?p...b4_thumb_1.png
http://julien.dollon.net/image.axd?p...mb15_thumb.png
http://julien.dollon.net/image.axd?p...mb20_thumb.png Je configure mon rectangle (qui est en réalité une div) grâce au panneau de gauche.
Changement de curseur par une main.
Positionnement en haut a gauche.
Mise en place du “Clip” pour que tout ce qui déborde de la div ne s’affiche pas.
Changement de la couleur de fond.
J’insère ensuite un texte et une image avec cette hiérarchie:
http://julien.dollon.net/image.axd?p...mb23_thumb.png http://julien.dollon.net/image.axd?p...mb26_thumb.png
Puis j’anime la div appelée secondRect en enregistrant son positionnement en Y. Pour cela, il faut faire un clique droit sur secondRect->Add Key Frame->Translate(Y).
Puis grâce au storyboard viewer du bas, j’anime le tout.
http://julien.dollon.net/image.axd?p...mb29_thumb.png
Il faut ensuite mettre un peu d’easing animation pour rendre le tout plus naturel.
http://julien.dollon.net/image.axd?p...mb32_thumb.png
Ainsi qu’un trigger pour capter la fin de l’animation et la relancer.
http://julien.dollon.net/image.axd?p...mb35_thumb.png
Création de la tuile “a la main”
Pour cela, j’utilise les animations CSS3, c’est a dire un KeyFrame definissant une translation sur une division.
En HTML, la tuile est représentée comme ceci:
Code:
1 2 3 4
| <div class="box">
<div class="imageXL"><img src="assets/images/windows.png" alt="" /></div>
<div class="nomappXL">Kinect</div>
</div> |
L’objectif étant d’avoir ceci (3 états, la durée de l’animation variant suivant la tuile):
http://julien.dollon.net/image.axd?p...mb12_thumb.pnghttp://julien.dollon.net/image.axd?p...mb16_thumb.pnghttp://julien.dollon.net/image.axd?p...b201_thumb.png
L’animation CSS3 est définie de cette manière:
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @keyframes tuileTranslation {
0%
{
transform: translateY(0em);
}
25%
{
transform: translateY(-3.75em);
}
75%
{
transform: translateY(-7.5em);
}
100%
{
transform: translateY(0em);
}
} |
La div “mère” est clipped avant de ne pas voir l’image déborder lors de la translation:
Code:
1 2 3 4 5 6 7
| .box > .image
{
width:80px;
height:80px;
margin: auto;
clip:rect(15px, auto, auto, auto);
} |
Et le JavaScript s’occupe d’initialiser l’image de la div pour la mettre en “background” (plus facilement manipulable pour moi). Il lance aussi l’animation CSS3 avec un temps “aléatoire”:
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| $(".imageXL").each(function () {
/* animation for tiles with random duration */
var randomnumber = Math.floor(Math.random() * 6) + 1;
$(this).css('animation', 'tuileTranslation ' + (10 + randomnumber).toString() + 's infinite')
.css('-moz-animation', 'tuileTranslation ' + (10 + randomnumber).toString() + 's infinite')
.css('-webkit-animation', 'tuileTranslation ' + (10 + randomnumber).toString() + 's infinite')
.css('-ms-animation', 'tuileTranslation ' + (10 + randomnumber).toString() + 's infinite')
.css('-o-animation', 'tuileTranslation ' + (10 + randomnumber).toString() + 's infinite')
.next(".nomappXL")
.css('animation', 'tuileTranslation ' + (10 + randomnumber).toString() + 's infinite')
.css('-moz-animation', 'tuileTranslation ' + (10 + randomnumber).toString() + 's infinite')
.css('-webkit-animation', 'tuileTranslation ' + (10 + randomnumber).toString() + 's infinite')
.css('-ms-animation', 'tuileTranslation ' + (10 + randomnumber).toString() + 's infinite')
.css('-o-animation', 'tuileTranslation ' + (10 + randomnumber).toString() + 's infinite');
/* initilization of the background of the div depending of the image in it */
$(this).find("img:first").css("visibility", "hidden");
var imageUrl = $(this).find("img:first").attr("src");
$(this).css('background-image', 'url(' + imageUrl + ')');
}); |
Mise en place d’un fond METRO
Le but est d’afficher de manière random, un léger fond en mouvement.
Nous allons utiliser les animations et transformations CSS compatibles avec les derniers browser du marche (sauf IE9 –> voir IE10).
Mise en place du code HTML:
Code:
1 2 3 4 5 6 7 8 9 10
| <body onload="MaMethod();">
<div id="container">
<div id="fond">
<img src="D3B_2156q.jpg" id="img" alt=""/>
</div>
<div id="site">
<!-- Site ici -->
</div>
</div>
</body> |
La div “fond” représente l’image à afficher, le reste du site étant place dans “site”.
Body va appeler une fonction JavaScript permettant d’attribuer au chargement de la page, une classe CSS à “fond” qui déclenchera l’animation.
Code:
1 2 3 4 5 6
| <script>
function MaMethod() {
var div = document.getElementById("fond");
div.setAttribute("class", "anim");
}
</script> |
La partie CSS reste simple:
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| body
{
padding: 0;
margin: 0;
background-color: black;
overflow-y: hidden;
}
#fond > img {
opacity: 0.5;
position: fixed;
}
.anim {
animation: monanim 60s infinite;
}
@keyframes monanim {
0% {
transform: translateY(0em);
transform: translateX(0em);
}
25%{
transform: translate(-15em,-5em);
}
75% {
transform: translate(-15em, -10em);
}
100%
{
transform: translate(0em, 0em);
}
}
#site
{
background-color: transparent;
width: 2000px;
height: 700px;
opacity: 0.5;
} |
Le résultat:
http://julien.dollon.net/html5/fond/index.htm
http://julien.dollon.net/image.axd?p...mb21_thumb.png
Création du service Node.js
Introduction
Il y a dix ans, quand j’ai comence le JavaScript, la première chose qu’on m’a appris: Le JS tourne cote client.
Et bien plus maintenant! Node.js est l’un des nombreux Framework permettant de faire des services avec JavaScript.
Je conseil l’editeur en ligne si vous voulez tester/debuguer du node.js: http://c9.io/
http://tbergeron.com/wp-content/uplo.../02/nodejs.png
Les avantages d’utiliser Node.js sont:
- Si vous avez du code JS a porter cote serveur, c’est très simple
- Si vous êtes un pro du JS/dev web et que vous ne connaissez pas bien un langage d’Homme (http://julien.dollon.net/image.axd?p...n-smile_36.png) comme Java ou C#, node.js vous sauve la vie
- Si vous voulez faire un service léger et rapide, restfull, avec CouchDB ou autre, un truc code a l’arrache from scratch c’est pas mal.
Les désavantages:
- C’est du JavaScript. Autant cote client je comprends l’utilisation d’un langage dynamique est intéressant, mais la cote serveur c’est risque des erreurs dans tous les sens
- L’API est clairement instable, et en plus manque d’optimisation (du style réutilise pas le thread courant pour re-exécuter un service etc…)
- Manque de librairies, comment accéder a mon SQL Server? A mon API S3 ou Azure? etc… Les APIs dispos sont très maigres
Création du service
Le service doit être capable de retourner:
- La dernière vidéo du compte Soumow de youtube
- Les 5 derniers articles de mes blogs perso et pro
- Mes 6 contacts réseaux sociaux
- Mes 5 derniers projets
Le tout en JSON avec un service RESTFULL.
Je pourrais utiliser un routeur de requête type Journey, mais j’ai voulu me débrouiller de A a Z.
Je commence donc par initialiser mon fichier JavaScript:
Code:
1 2 3 4 5 6 7 8 9 10
| var http = require("http");
var url = require("url");
var querystring = require("querystring");
var handle = { };
handle["/"] = rootCall;
handle["/lastarticles"] = lastArticles;
handle["/lastprojects"] = lastProjects;
handle["/lastvideo"] = lastVideo;
handle["/socialnetworks"] = socialnetworks; |
Mon objet handle contient des pointeurs de fonctions vers les méthodes a appeler.
Je démarre ensuite un serveur sur le port 8888 et je route le tout (merci au livre sur Node.js).
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function route(handle, pathname, request, response) {
console.log("About to route a request for " + pathname);
if (typeof handle[pathname] === 'function') {
handle[pathname](request, response);
} else {
console.log("No request handler found for " + pathname);
writeFast(response, "Error, no method found");
}
}
http.createServer(function (request, response) {
//
var pathname = url.parse(request.url).pathname;
console.log("Request for " + pathname + " received.");
route(handle, pathname, request, response);
}).listen(8888); |
Résultat, quand j’appelle l’url /socialnetworks, la fonction socialnetworks est appelée.
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13
| function socialnetworks(request, response) {
response.writeHead(200, { "Content-Type": "text/json" });
var socials = new Array();
socials.push(new DataContractElement("Facebook", "fb.png", "", "https://www.facebook.com/julien.dollon"));
socials.push(new DataContractElement("Twitter", "twitter.png", "", "https://twitter.com/#!/juliendollon"));
socials.push(new DataContractElement("Google+", "google.png", "", "https://plus.google.com/110722800174049431616/posts"));
socials.push(new DataContractElement("Linkedin", "linkedin.png", "", "http://www.linkedin.com/profile/view?id=7903132&trk=tab_pro"));
socials.push(new DataContractElement("Resume", "", "", "http://julien.dollon.net/dollon_en.pdf"));
socials.push(new DataContractElement("MVP Profil", "", "", "http://www.microsoft.com/france/communautes/annuaire/acteurs.aspx?auteur=Dollon"));
response.write(JSON.stringify(socials));
response.end();
} |
De même pour les autres fonctions du service.
Pour le téléchargement des divers flux rss j’ai utilise la librairie Future qui permet de faire des ForeachAsync et des .Then() plutôt pratique.
La méthode permettant de retrouver la dernière video YouTube nécessite de faire une requête GET a l’API Youtube.
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| var options = { host: "gdata.youtube.com"
, path: "/feeds/api/users/" + account + "/uploads?v=2"
};
//make the request server side
http.get(options, function (resp) {
//console.log("hi",response);
response.writeHead(200, { 'Content-Type': 'text/plain' });
var pageData = "";
resp.setEncoding('utf8');
//stream the data into the response
resp.on('data', function (chunk) {
pageData += chunk;
});
//write the data at the end
resp.on('end', function () {
response.write(pageData);
response.end();
});
}); |
Mise en place du service
Node.js est supporte par Azure mais comme ca coute un bras je vais opte pour un déploiement standard sur mon serveur perso avec IIS7.
Ce lien nous explique comment héberger un service node.js dans IIS:
http://www.amazedsaint.com/2011/09/c...ng-app-on.html
En gros il faut:
Voici le résultat:
http://home.dollon.net/node/service/personalservice.js/
Comme en Silverlight et WCF, il faut initialiser le clientaccesspolicy.
Attention aux failles de sécurité avec une “*” en cas d’injection de code XSS.
Code:
1 2 3 4
| response.writeHead(200, {
'Content-Type': 'text/plain',
'Access-Control-Allow-Origin' : '*'
}); |
Typage du JavaScript et mode strict
A ce stade, J’ai commence a utiliser du JS par ci par la mais je n’ai pas fait attention au best practices.
Tout d’abord en terme d’architecture mais j’en parlerai a l’étape suivante.
Mais aussi en terme de typage et propreté du code.
Il existe un mode en JavaScript (ECMAScript 5) qui permet d’être dans un contexte plus stricte que celui de d’habitude. Cet environnement impose certaines règles de développement et désactives des fonctionnalités de JS pouvant être dangereuse.
Pour l’utiliser il faut rajouter:
Code:
<code>"use strict";</code>
Au niveau du scope du code a protéger.
Désormais, si j’utilise une variable non déclarée, j’aurai une erreur dans la console JavaScript:
http://julien.dollon.net/image.axd?p...umb2_thumb.png
Plus d’infos sur le mode strict ici:
http://hacks.mozilla.org/2011/01/ecm...-in-firefox-4/
Une deuxième chose qui existe et qui améliore le code JavaScript est TypedJS.
TypedJS permet d’annoter nos fonctions avec un commentaire précisant le type de retour et des params.
Ce commentaire est lu par des le lancement dans une console JS de “TypedJS.run_tests()”.
A noter que TypedJS supporte Node.js.
Mise en place de l’architecture avec Knockout.js
Depuis le début, je n’utilise aucune architecture. Normal cote front end, puisque que ce soit en MVVM en Silverlight ou JS, tant que le code touche des aspects purement graphiques, je le laisse dans la vue.
Sauf que la, je vais avoir du code pour consommer mon service! Je décide donc de partir sur Knockout.js.
Un seul lien pour l’apprendre: http://learn.knockoutjs.com/
Knockout fournis un framework MVVM avec tout ce qui tourne autours comme le Binding, Observables…
Pour l’installer, tout est explique ici: http://knockoutjs.com/documentation/installation.html
Notre ViewModel a besoin d’exposer:
- Une propriété représentant la vidéo
- Une collection de projets
- Une collection de réseaux sociaux
- Une collection d’articles
- Une propriété IsBusy pour dire que le chargement est en cours
- Une prop pour afficher si il y a une erreur
Tous les liens auront le même model (attention au context “this” qui peut bouger):
Code:
1 2 3 4 5 6 7
| function DataContractElement(title, image, description, link) {
var self = this;
self.title = title;
self.image = image;
self.description = description;
self.link = link;
} |
On crée le ViewModel, ici par exemple avec une propriété Observable qui sera l’url vers la vidéo youtube:
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| "use strict";
function principalViewModel() {
var self = this;
self.isBusy = ko.observable(true);
self.videoUrl = ko.observable("");
self.error = ko.observable("");
self.projects = ko.observableArray([]);
self.sociallinks = ko.observableArray([]);
self.lastarticles = ko.observableArray([]);
$.ajax({
type: "GET",
url: lastvideourl,
success: function (json) {
var mapped = new DataContractElement(
json[0].title, json[0].image, json[0].description, json[0].link);
if (mapped.title == null) {//Bug Firefox
mapped = new DataContractElement(
JSON.parse(json)[0].title, JSON.parse(json)[0].image, JSON.parse(json)[0].description, JSON.parse(json)[0].link);
}
self.videoUrl(mapped.link);
self.isBusy(false);
},
error: function (xhr, textStatus, errorThrown) {
self.error(xhr.responseText);
}
});
} |
On instancie, dans le load de notre page, le ViewModel de cette façon:
Code:
ko.applyBindings(new principalViewModel());
Et cote vue, on réalise un binding sur l’iframe de Youtube:
Code:
<iframe data-bind="attr: {src: videoUrl}"></iframe>
De même pour la liste des articles, sauf que cette fois ci je travaille avec un observableArray et des templates.
Code:
1 2 3
| <ul data-bind="foreach: lastarticles">
<li>Name: <span data-bind="text: title"> </span>
</ul> |
Je termine avec la capture de l’évènement clic et ouverture du lien dans une popup:
Code:
<li data-bind="click: $parent.launchLink">
Téléchargement des images
Dans la version non public du site, j’ai voulu utiliser le web worker (un thread JS grâce a HTML5) pour télécharger les images de fond.
Le problème est que très peu de navigateur le supporte, mais si vous souhaitez voir comment j’ai fais pour downloader une image, c’est par ici:
http://www.html5rocks.com/en/tutoria...lesystem-sync/
Splash screen
Tout d’abord, je souhaite dessiner mon logo.
Pour cela deux options s’offre a moi:
Le canvas, qui va générer un bitmap, c’est a dire quelque chose de “plat” et fixe en 2d tel une image. Par default on dessine en 2D mais WebGL permet de faire de la 3D. On l’utilise avec une balise en HTML5 et on pilote le dessin avec JavaScript.
Les points positifs: performant, possibiliter d’exporter le resultat en image.
De l’autre cote, nous avons un format vectoriel, SVG, base sur du XML.
Les points positifs: j’ai déjà au format Illustrator mon logo, facile a convertir en SVG, facilement animable.
Je decide donc de partir sur SVG. Je rajoute une progressbar (nouveauté HTML5) avec un petit skin CSS3 et un binding en MVVM sur la propriété isBusy:
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <div id="loadingpage" data-bind="visible: isBusy"> <!---->
<svg version="1.1" id="svglogo"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="500px" height="500px" viewBox="-96.46 -235.46 595 842"
xml:space="preserve">
<defs>
</defs>
<polygon fill="#FFFFFF" stroke="#000000" stroke-miterlimit="10" points="115.874,202.54 115.874,65.874 180.54,1.207
180.54,360.54 0.707,180.707 47.707,133.707 "/>
<polygon fill="#FFFFFF" stroke="#000000" stroke-miterlimit="10" points="214.54,360.54 395.207,180.874 214.54,1.207
214.54,91.874 304.874,180.874 214.54,271.207 "/>
</svg>
<div id="loadingElement">
<progress max="100" data-bind="value: pourcentage">
</progress>
<span data-bind="text: pourcentage"></span>
</div>
</div> |
http://julien.dollon.net/image.axd?p...b211_thumb.png
Un hack en CSS?
A ce stade, on a presque fini… Mais on va pas se leurrer, seul peu de navigateurs le supporte.
Le constat est le suivant:
- Aucun pb pour Mozilla
- Aucun pb pour WebKit (Chrome, Safari)
- Opera chie dans la colle
- IE9 a des pbs aussi (mais IE10 fonctionne)
Normalement, je devrais faire plusieurs fichiers CSS et des hack JS pour que tout fonctionne sur tous les navigateurs… mais j’ai pas envi!
Bon je sais bien, on peut pas dire ca entreprise mais comme c’est mon site perso, je m’en fou http://julien.dollon.net/image.axd?p...n-smile_36.png.
Donc direction modernizr, un petit outil JS me permettant de tester les capacités d’un navigateur.
http://www.modernizr.com/
Je configure donc toutes les features dont j’ai besoin:
http://julien.dollon.net/image.axd?p...mb31_thumb.png
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function VerifyBrowserCapacities() {
Modernizr.load();
var listOfNonSupported = [];
for (var propertyName in Modernizr) {
if (!Modernizr[propertyName])
listOfNonSupported.push(propertyName);
}
if (listOfNonSupported.length > 0) {
var root = window.location.toString().toLowerCase().replace("index.html", "");
//alert(root.split('')[root.length - 1]);
if (root.split('')[root.length - 1] != '/')
root += '/';
//alert(listOfNonSupported.toString());
window.location = root + "notsupported.html?error=" + listOfNonSupported.toString();
}
} |
A ce stade, voici le rendu:
http://home.dollon.net/node/website3/
Utilisation de Media en CSS
Les media queries permettant de filtrer le CSS suivant le device.
Je vais l’utiliser pour imposer une résolution minimum.
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @minwidth: 800px;
@minheight: 700px;
@media screen and (max-width: @minwidth) or (max-height: @minheight) {
.popuppage
{
.popuppageabstract;
visibility:visible;
}
}
@media screen and (min-width: @minwidth) and (min-height: @minheight) {
.popuppage
{
.popuppageabstract;
visibility:hidden;
}
} |
http://julien.dollon.net/image.axd?p...1%5D_thumb.png
De même, mes images de fonds ont une taille maximum de 2598x1417 + l’effet de translation. Sur de gros écrans ou sur un iPad, le résultat n’est donc pas terrible. Pour cela je désactive le background pour ces résolutions trop haute.
Une autre problématique, qui ne se résout pas avec le CSS mais que je met ici quand même: le viewport.
Il faut pouvoir empêcher les tablettes de pouvoir zoomer/dezoomer et forcer le scale de base de la page.
Code:
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1, maximum-scale=1, user-scalable=no"/>
une autre chose importante, est d’imposer a l’utilisateur le mode landscape. Mon site rendant clairement mieux tout en longueur.
Pour forcer l’utilisateur, je pourrai utiliser body[orient="landscape"] en CSS mais cette propriété ne marche pas partout.
J’utilise donc un petit hack JavaScript:
Code:
<body onload="OnLoad();" onorientationchange="updateOrientation();">
Version offline et stockage des articles hors ligne
L’objectif de la version offline va être de pouvoir montrer mon beau site quand j’ai pas internet.
Je vais devoir mettre en cache, la page, les scripts, les images mais aussi les données venant du service.
Pour les données j’ai le choix entre les stockes dans un indexeddb ou dans le webstorage. Indexeddb n’est pas encore bien supporte par Safari, j’opte donc pour le second.
Plus d’infos sur ces types de storages ici:
http://stackoverflow.com/questions/6...dexed-database
Pour commencer, je vais mettre en cache tout le site grâce a l’applicationcache feature.
- Mise a jour du web.config pour gérer les fichiers manifest
Code:
1 2 3 4 5 6 7 8 9 10 11 12
| <configuration>
<system.web>
<httpHandlers>
<add verb="GET,HEAD" path="*.appcache" type="System.Web.StaticFileHandler" />
</httpHandlers>
</system.web>
<system.webServer>
<staticContent>
<mimeMap fileExtension=".appcache" mimeType="text/cache-manifest" />
</staticContent>
</system.webServer>
</configuration> |
- Création du fichier manifest qui décrit les éléments accessibles offline
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| CACHE MANIFEST
# version 1
CACHE:
Index.html
notsupported.html
favicon.ico
Font/SEGOEUI.TTF
Assets/less.css
Assets/jquery-1.5.1.min.js
Assets/jquery.mousewheel.min.js
Assets/principalViewModel.js
Assets/wallpaper/0.jpg
Assets/wallpaper/1.jpg
Assets/wallpaper/2.jpg
Assets/images/book.png
Assets/images/df.png
NETWORK:
* |
- Eviter les conflits and client et browser caching en désactivant le caching du browser:
Code:
1 2
| <meta http-equiv='Pragma' content='no-cache'>
<meta http-equiv='Expires' content='-1'> |
Il faut maintenant stocker les données du service dans le storage.
Le webstorage propose deux types de stores: local et Session.
Local storage fonctionne comme un cookie, l’information pouvant être récupérer a tout moment alors que la session a une durée de vie plus limitée.
Pour mon cas, ca sera donc le localstorage (taille entre 2 et 10mo, amplement suffisant).
Désormais, même sans internet, ma page sera accessible.
Simplifier les fichiers JS et CSS
Me voici dans la phase avant déploiement: quelques petites choses sont encore a finaliser:
Ce compilateur de Google se décrit comme un compilateur qui compile du JS en JS, mais un JS plus rapide, plus optimisé.
Pour faire un test:
http://closure-compiler.appspot.com/home
Pour ma part:
http://julien.dollon.net/image.axd?p...umb5_thumb.png
- Nettoyer le HTML (et si vous avez le courage, le faire passer W3C)
- Optimiser les performances
Résultats
Résultat sur PC
http://julien.dollon.net/image.axd?p...umb2_thumb.jpg
WTF WINDOWS ON A MAC!!!! http://julien.dollon.net/image.axd?p...n-smile_36.png
Grace au site http://browserling.com/ je peux émuler le site web sur tous les navigateurs.
Tous les derniers navigateurs de la famille IE, Chrome, Safari et Mozilla sont capables de l’afficher correctement.
Le site final:
home.dollon.net
Résultat sur tablette
Android
http://julien.dollon.net/image.axd?p...4%5D_thumb.jpg
iPad
http://julien.dollon.net/image.axd?p...4%5D_thumb.jpg
Windows 8
http://julien.dollon.net/image.axd?p...umb1_thumb.jpg
Conclusion
HTML5 pour le web est la, s’apprend assez vite et est un plaisir a utiliser.
Cependant, il n’est la que depuis peu, car seul les derniers navigateurs (encore parfois en beta) le supporte.
Internet Explorer est le navigateur un peu en retard par rapport aux autres. Mais le 10 corrige tout. C’est donc pour la sortie de IE10 RTM (fin 2012/début 2013) que je conseillerai le développement HTML5. D’ici la, nous avons le temps de monter en compétence.
HTML5: Oui!
Avant 2013? Non!
Billet original du blog de Julien Dollon