API HTML 5 et CSS 3
Samuel BOUCHET
Freelance - JavaScript developer and architect
Le plan
- le web version html 5
- structure des pages html 5
- webforms2
- multimedia et graphisme
- communications
- webworkers
- fichiers et ressources locales
- device api
- css3
- responsive design
Note:
- Beaucoup de choses
- Objectif: bien tout comprendre mais pas forcément mémoriser. Ce support permettra de retrouver les infos.
LE WEB VERSION HTML 5
Définition et limites de HTML 5
- Successeur de HTML 4
- Spécification d'un ensemble de fonctionnalité et de comportements attendus
- Chaque fonctionnalité => support navigateur différent
- Concerne surtout l'API JavaScript
- Concerne aussi l'accessibilité et l'interactivité
Support des navigateurs
Compatibilité : Modernizr
Détecte le support d'une fonctionnalité par le navigateur
Note:
- Modernizer
- Au moment d'afficher la page chez l'utilisateur, répond à la question : Le navigateur supporte t'il la fonctionnalité X ?
- Pivot de l'amélioration progressive (progressive enhancement)
- Plus fin que la détection de user-agent navigateur
Compatibilité : Polyfills
Ajoute en JavaScript une fonctionnalité native qu'un navigateur n'implémente pas
🔎 <nom_fonctionalité> polyfill <navigateur_cible>
Compatibilité : BabelJS
Permet de faire fonctionner une version récente de javascript (ES6) sur un navigateur qui ne le supporte.
Impact sur les architectures Web
- + de fonctionnalités
- Donc des applications web + riches
- Donc des architectures + complèxes
Quelques mots clés :
- Bibliothèque (Library) : A utiliser en l'état
- Framework : Impose une manière de faire
- Boilerplate : Squelette d'application pré construit
- Generators : initialise une appli sur mesure
Note: Architecture logiciel n'est pas l'objet de la formation, on va rester sur les fonctionnalités individuelles et garder l'architecture la plus simple possible.
HTML 5 pour les mobiles
Les smartphones se sont développés au moment de HTML 5 : Très bon support globalement
Popularité des applications webview (ex: cordova)
Popularité des Progressive Web Applications (PWA)
Note:
- le principe d'une application webview est d'encapsuler une page web dans une application mobile.
- PWA uniquement sous Android pour le moment (bientôt Microsoft) et qui permet d'installer une application web offline.
STRUCTURE DES PAGES HTML 5
clic droit -> afficher la source
- Doctype
<!doctype html>
- Balises sémantiques
<section></section>
- Microformats
<li itemtype="http://microformats.org/profile/hcard" itemscope>
<div itemprop="fn">Tony Stark</div>
</li>
note:
- pas de comparatif avec avant car peu d'intérêt de voir ce qu'on ne pouvait pas faire.
- Doctype pour indiquer au navigateur qu'on est en html5
- Balises sémantiques et microformats pour permettre une meilleur accessibilités (outils de retranscription vocaux ou brail, robots, référencement)
WEBFORMS2
Nouveaux champs de saisie
<input name="myinput" type="choisir_ci-dessous" />
- Hidden state (type=hidden)
- Text (type=text) state and Search state (type=search)
- Telephone state (type=tel)
- URL state (type=url)
- E-mail state (type=email)
- Password state (type=password)
- Date state (type=date)
- Month state (type=month)
- Week state (type=week)
- Time state (type=time)
- Local Date and Time state (type=datetime-local)
- Number state (type=number)
- Range state (type=range)
- Color state (type=color)
- Checkbox state (type=checkbox)
- Radio Button state (type=radio)
- File Upload state (type=file)
- Submit Button state (type=submit)
- Image Button state (type=image)
- Reset Button state (type=reset)
- Button state (type=button)
Note:
- intéressant de choisir précisément celui au plus près de la donnée pour faciliter l'utilisation: accessibilité, facilité.
- Référence : https://www.w3.org/TR/html5/sec-forms.html#sec-states-of-the-type-attribute
Sliders
https://caniuse.com/#search=sliders (94.5%)
<input type="range" min="0" max="100" step="10" name="range" />
datalisthttps://caniuse.com/#search=datalist (72.29%)
- Liste de suggestions pour assister la saisie
- Autocompletion sans JavaScript
- Inidicateur visuels
<input type="range" list="rangelist" min="0" max="100" name="range" />
<datalist id="rangelist">
<option value="20" label="low">
<option value="50">
<option value="80" label="high">
</datalist>
Note: Polyfill à tester ! Fonctionnalité encore mal couverte : mettre en place le cas d'utilisation et tester sur toutes les cibles. Alternativement choisir une solution en JavaScript qui fournira un service équivalent.
placeholder
https://caniuse.com/#search=placeholder (97.7%)
<input type="text" list="valuelist" name="val" placeholder="Saisir une valeur" />
<datalist id="valuelist">
<option value="abc" label="low">
<option value="bcd">
<option value="cde" label="high">
</datalist>
Expressions régulières
Exemple 1:
const dateRegexp = /[0-9]{2}-[0-9]{2}-[0-9]{4}/gi
document.getElementById('regexpDate').innerHTML = 'test de dates:' + '<br/>'
document.getElementById('regexpDate').innerHTML += '20/10/1987'.match(dateRegexp) + '<br/>'
document.getElementById('regexpDate').innerHTML += '20-10-1987'.match(dateRegexp) + '<br/>'
</div>
Expressions régulières
Exemple 2:
const bulletLine = /\* (.*)/g
const text =
`* une ligne
* une autre
* ça va changer !`;
document.getElementById('regexpText').innerHTML = text.replace(bulletLine, '- $1<br/>')
Expressions régulières
Impossible de penser à tous les cas du premier coup :
regexp date_fr
Validation automatique
Avec un pattern
<form action="#" name="validate1" onsubmit="alert(document.validate1.val.value);return false;">
<input type="text" list="valuelist2" name="val" placeholder="Saisir une valeur"
pattern="[A-Za-z]{3}" title="3 lettres minuscule ou majuscules"/>
<datalist id="valuelist2">
<option value="dfg" label="low">
<option value="fgh">
<option value="ghj" label="high">
</datalist>
<input type="submit" />
</form>
Note: Pas de visuel pour indiquer le champ en erreur sur tous les navigateur. Utiliser les sélecteurs de style :valid
et :invalid
Validation automatique
Avec un required
<form action="#" name="validate2" onsubmit="alert(document.validate2.val.value);return false;">
<input type="text" list="valuelist3" name="val" placeholder="Saisir une valeur"
required pattern="[A-Za-z]{3}" title="3 lettres minuscule ou majuscules" />
<datalist id="valuelist3">
<option value="jkl" label="low">
<option value="klm">
<option value="mpo" label="high">
</datalist>
<input type="submit" />
</form>
Note: Pas de changement visuel a priori, indication visuelle "required" (souvent asterisque + message) à faire en html.
Validation dans le code
Par exemple en forçant une valeur de la datalist
<form … onsubmit="return formIsValid();">
function formIsValid() {
const value = document.validate2.val.value
const list = document.getElementById('valuelist3').options
if (value === 'secret') return true
for(var i = 0; i < list.length; i++) {
if (list.item(i).value === value) return true
}
return false;
}
Note: Chercher dans la doc de l'api pour connaitre la structure des données, ici : https://www.w3schools.com/jsref/coll_datalist_options.asp
MULTIMEDIA ET GRAPHISME
Vidéo
https://developer.mozilla.org/fr/docs/Web/HTML/Element/Video
<video src="https://zippy.gfycat.com/WavyMeanDesertpupfish.webm"
height="150" loop controls
poster="https://cdn.pixabay.com/photo/2017/11/11/23/31/curtain-2940999__340.png">
Votre navigateur ne permet pas de lire les vidéos.
Mais vous pouvez toujours <a href="https://zippy.gfycat.com/WavyMeanDesertpupfish.webm">la télécharger</a> !
</video>
Audio
https://developer.mozilla.org/fr/docs/Web/HTML/Element/audio
<audio src="http://developer.mozilla.org/@api/deki/files/2926/=AudioTest_(1).ogg"
autoplay>
Votre navigateur ne supporte pas l'élément <code>audio</code>.
</audio>
Canvas
<canvas id='canvas1' width='480' height='320'> Canvas not supported</canvas>
const c = document.getElementById('canvas1');
const ctx = c.getContext('2d');
let x = 100, y = 160, i = 0;
ctx.fillStyle = 'lightgrey'; ctx.fillRect(0, 0, 480, 320);
// axis
ctx.beginPath();ctx.strokeStyle='red';
ctx.moveTo(0,y);ctx.lineTo(480,y);ctx.stroke();
ctx.moveTo(x,0);ctx.lineTo(x,320);ctx.stroke();
// draw function
ctx.beginPath();ctx.moveTo(x,y + Math.sin(0) * 100);
setInterval(function() {
i++;
ctx.lineTo(x + i, y + Math.sin(i/50) * 100);
ctx.strokeStyle='black';ctx.stroke();
}, 1000/60)
Dessiner !
SVG
<svg width="480" height="320">
<rect width="480" height="320" style="fill:lightgrey;" />
<rect x="100" y="0" width="1" height="320" style="fill:red;" />
<rect x="0" y="160" width="480" height="1" style="fill:red;" />
<path id="svgpath" d="M 100 160 L 101 162 L 102 164 L 103 166 L 104 168 L 105 170 L 106 172 L 107 174 L 108 176 L 109 178 L 110 180 L 111 182 L 112 184 L 113 186 L 114 188 L 115 190 L 116 191 L 117 193 L 118 195 L 119 197 L 120 199 L 121 201 L 122 203 L 123 204 L 124 206 L 125 208 L 126 210 L 127 211 L 128 213 L 129 215 L 130 216 L 131 218 L 132 220 L 133 221 L 134 223 L 135 224 L 136 226 L 137 227 L 138 229 L 139 230 L 140 232 L 141 233 L 142 234 L 143 236 L 144 237 L 145 238 L 146 240 L 147 241 L 148 242 L 149 243 L 150 244 L 151 245 L 152 246 L 153 247 L 154 248 L 155 249 L 156 250 L 157 251 L 158 252 L 159 252 L 160 253 L 161 254 L 162 255 L 163 255 L 164 256 L 165 256 L 166 257 L 167 257 L 168 258 L 169 258 L 170 259 L 171 259 L 172 259 L 173 259 L 174 260 L 175 260 L 176 260 L 177 260 L 178 260 L 179 260 L 180 260 L 181 260 L 182 260 L 183 260 L 184 259 L 185 259 L 186 259 L 187 259 L 188 258 L 189 258 L 190 257 L 191 257 L 192 256 L 193 256 L 194 255 L 195 255 L 196 254 L 197 253 L 198 253 L 199 252 L 200 251 L 201 250 L 202 249 L 203 248 L 204 247 L 205 246 L 206 245 L 207 244 L 208 243 L 209 242 L 210 241 L 211 240 L 212 238 L 213 237 L 214 236 L 215 235 L 216 233 L 217 232 L 218 230 L 219 229 L 220 228 L 221 226 L 222 225 L 223 223 L 224 221 L 225 220 L 226 218 L 227 217 L 228 215 L 229 213 L 230 212 L 231 210 L 232 208 L 233 206 L 234 205 L 235 203 L 236 201 L 237 199 L 238 197 L 239 195 L 240 193 L 241 192 L 242 190 L 243 188 L 244 186 L 245 184 L 246 182 L 247 180 L 248 178 L 249 176 L 250 174 L 251 172 L 252 170 L 253 168 L 254 166 L 255 164 L 256 162 L 257 160 L 258 158 L 259 156 L 260 154 L 261 152 L 262 150 L 263 148 L 264 146 L 265 144 L 266 142 L 267 140 L 268 138 L 269 136 L 270 134 L 271 133 L 272 131 L 273 129 L 274 127 L 275 125 L 276 123 L 277 121 L 278 119 L 279 118 L 280 116 L 281 114 L 282 112 L 283 110 L 284 109 L 285 107 L 286 105 L 287 104 L 288 102 L 289 100 L 290 99 L 291 97 L 292 96 L 293 94 L 294 93 L 295 91 L 296 90 L 297 88 L 298 87 L 299 86 L 300 84 L 301 83 L 302 82 L 303 81 L 304 79 L 305 78 L 306 77 L 307 76 L 308 75 L 309 74 L 310 73 L 311 72 L 312 71 L 313 70 L 314 69 L 315 68 L 316 68 L 317 67 L 318 66 L 319 65 L 320 65 L 321 64 L 322 64 L 323 63 L 324 63 L 325 62 L 326 62 L 327 61 L 328 61 L 329 61 L 330 61 L 331 60 L 332 60 L 333 60 L 334 60 L 335 60 L 336 60 L 337 60 L 338 60 L 339 60 L 340 60 L 341 61 L 342 61 L 343 61 L 344 61 L 345 62 L 346 62 L 347 63 L 348 63 L 349 64 L 350 64 L 351 65 L 352 65 L 353 66 L 354 67 L 355 67 L 356 68 L 357 69 L 358 70 L 359 71 L 360 72 L 361 73 L 362 74 L 363 75 L 364 76 L 365 77 L 366 78 L 367 79 L 368 80 L 369 81 L 370 83 L 371 84 " stroke="black" stroke-width="3" fill="none" />
</svg>
const path = document.getElementById('svgpath');
let x = 100, y = 160, i = 0;
path.setAttribute('d', /* … */);
setInterval(function() {
i++;
path.setAttribute('d', /* … */);
}, 1000/60)
Dessiner !
WebGL
<canvas id='canvas' width='480' height='320'> Canvas not supported</canvas>
const c = document.getElementById('canvas2');
const gl = c.getContext('webgl');
// instructions opengl a partir d'ici
// c'est trop long pour une slide
Dessiner !
Note: Canvas avancé pour utiliser la puissance de la carte graphique.
COMMUNICATIONS
Réseau
Architecture client / serveur
HTTP = protocole de transfert
format du message HTTP = headers + payload
headers = request [ + response ] + métadonnées
JSON
compatible avec l'objet JavaScript
Autres formats similaires : xml, yaml, formats binaires
{
"menu": "Fichier",
"commandes": [
{
"titre": "Nouveau",
"action":"CreateDoc"
},
{
"titre": "Ouvrir",
"action": "OpenDoc"
}
]
}
Note:
XHR2
Ne pas utiliser XHR2 !
https://developer.mozilla.org/fr/docs/Web/API/Fetch_API
+ polyfills si besoin (https://github.com/github/fetch)
fetch('/users.json')
.then(function(response) {
return response.json()
}).then(function(json) {
console.log('parsed json', json)
}).catch(function(ex) {
console.log('parsing failed', ex)
})
Note: Les Polyfills s'appuiront sur xhr2 de façon transparent pour vous.
fetch typical use
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response
} else {
var error = new Error(response.statusText)
error.response = response
throw error
}
}
function parseJSON(response) {
return response.json()
}
fetch('/users')
.then(checkStatus)
.then(parseJSON)
.then(function(data) {
console.log('request succeeded with JSON response', data)
}).catch(function(error) {
console.log('request failed', error)
})
CORS (Cross-Origin Resource Sharing )
Système de sécurité implémenté par les navigateurs
Empêche par défaut les requêtes cross-sites
(En anglais cross-domain)
Se configure au niveau du serveur web, les informations sont transmises à travers les headers http
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER
Access-Control-Max-Age: 1728000
https://developer.mozilla.org/fr/docs/HTTP/Access_control_CORS
Note: Laissez les développeurs expérimentés vous expliquer.
Messaging
https://html.spec.whatwg.org/multipage/web-messaging.html#crossDocumentMessages
Note: Je n'en ai jamais eu l'usage, permet de transmettre des messages entre plus pages de domaines différents. Plus souvent fetch + politique CORS sont utilisés pour communiquer.
WebSocket
Ouverture d'un socket réseau direct entre un serveur et un client. socket = adresse IP + port
Nécessite un serveur websocket dédié, application différente du serveur http
Utiliser des bibliothèques plutôt que l'API native, https://socket.io/
WebRTC est une autre API qui met l'accent sur les performances du streaming, plus complexe
Note: la plupart du temps le même serveur fait à la fois HTTP et Websocket, parfois même au sein du même programme serveur, mais il s'agit bien de 2 mécaniques complètement différentes pour communiquer entre client et serveur.
WEBWORKERS
Modèle mono-thread
- JavaScript est mono-threadé
- L'asynchrone est simplement décalé dans la file d'exécution
- Le système d'événements permet à l'environnement (navigateur, système d'exploitation) de notifier d'une changement
- Système d'événement est la force des performances de JavaScript
Workers
Agents qui exécutent du code Asynchrone
Shared
- type spécifique de worker qui peut être accédé à partir de plusieurs contextes de navigation, tels que plusieurs fenêtres, iframes ou même workers
- Utile pour synchroniser des données par exemple
- https://developer.mozilla.org/fr/docs/Web/API/SharedWorker
FICHIERS ET RESSOURCES LOCALES
LocalStorage et SessionStorage
- https://caniuse.com/#search=web%20storage (95.31%)
- Stocker des données dans le navigateur
- localStorage persiste, peut être vidé manuellement
- sessionStorage vidé à la fin de la session
localStorage.setItem("username", "John");
alert( "username = " + localStorage.getItem("username"));
~ 10Mo de capacité
ApplicationCache Service Workers
https://caniuse.com/#search=Service%20Workers (73.67%) https://developer.mozilla.org/fr/docs/Web/API/Service_Worker_API/Using_Service_Workers
- Stocke des fichiers en cache
- Permet de rendre l'entièreté de votre site disponible une fois déconnecté du réseau
Note: Les Services Workers ne sont pas trivial à utiliser mais pas non plus excessivement complèxes. Avancer étape par étape.
Capacité dépends des navigateur et du disque dur de l'utilisateur, en théorie beaucoup (> 100 Mo partagés)
IndexedDB
https://caniuse.com/#search=indexedDB (93.98%)
Stocker des données dans une base de données objets sur le navigateur du client
https://developer.mozilla.org/fr/docs/Web/API/API_IndexedDB
Capacité dépends des navigateur et du disque dur de l'utilisateur, en théorie beaucoup (> 100 Mo partagés)
File API
https://caniuse.com/#search=file%20API (94.62%)
Manipuler des fichiers sélectionnés par l'utilisateur côté client
https://developer.mozilla.org/fr/docs/Web/API/File/Using_files_from_web_applications
Plusieurs cas d'utilisation (téléchargement, affichage des images, modification avec envoi)
DEVICE API
Géolocalisation
https://caniuse.com/#search=Geolocation (95.01%)
Connaitre la position de l'utilisateur
if ("geolocation" in navigator) {
/* géolocalisation possible */
navigator.geolocation.getCurrentPosition(function(position) {
do_something(position.coords.latitude, position.coords.longitude);
})
} else {
alert("Le service de géolocalisation n'est pas disponible sur votre ordinateur.");
}
Orientation
https://caniuse.com/#search=Orientation (91.8%)
Connaitre l'orientation du téléphone
window.addEventListener('deviceorientation', handleOrientation); function handleOrientation(event) { // true pour un référentiel terrestre // false pour un référentiel arbitraire const absolute = event.absolute; // inclinaison autour de l"axe Z const alpha = event.alpha; // inclinaison autour de l"axe X const beta = event.beta; // inclinaison autour de l"axe Y const gamma = event.gamma; }
Batterie
https://caniuse.com/#search=battery (69.15%)
Connaitre l'état de charge du périphérique. Support en baisse pour confidentialité.
const battery = navigator.battery || navigator.mozBattery || navigator.webkitBattery;
function updateBatteryStatus() {
console.log("Batterie chargée à : " + battery.level * 100 + " %");
if (battery.charging) { console.log("Chargement de la batterie"); }
}
battery.addEventListener("chargingchange", updateBatteryStatus);
battery.addEventListener("levelchange", updateBatteryStatus);
updateBatteryStatus();
Caméra et micro
https://caniuse.com/#search=getUserMedia (84.56%)
Obtenir un flux de données du micro ou de la webcam.
Nombreux changement d'API, utilisation d'un Shim ou d'un polyfill recommandé.
🔎 Shim getUserMedia
CSS3
Fonts
Problématique très complèxe
Sans webfont
- Dépendant du système => http://fontfamily.io/
- Utiliser une font stack
🔎 css font stack
- Peu de choix, pas de fantaisie
Fonts
Problématique très complèxe
avec webfont
- Problèmatique de performance
- FOUT (Flash Of Unstyled Text)
- FOIT (Flash Of Invisible Text)
- FOFT (Flash Of Faux Text)
- https://www.zachleat.com/web/comprehensive-webfonts
- https://www.zachleat.com/web/recipes/
- Problèmatiques de fidélité du rendu
Note: Si possible confier le sujet à un web developer expérimenté. Sinon suivre une méthode éprouvée.
Fonts
Approche naïve
@font-face {
font-family: 'League Gothic';
src: url('league-gothic.eot');
src: url('league-gothic.eot?#iefix') format('embedded-opentype'),
url('league-gothic.woff') format('woff'),
url('league-gothic.ttf') format('truetype');
}
Je suis en League Gothic !
<span style="font-family: 'League Gothic'">Je suis en League Gothic !</span>
Sélecteurs CSS 3
https://caniuse.com/#search=CSS3%20selectors (98.16%)
sélecteurs CSS utilisés également dans les biblitohèque de requêtage du DOM
🔎 css selecteur <motif>
https://developer.mozilla.org/fr/docs/Web/CSS/S%C3%A9lecteurs_CSS
Sélecteurs CSS 3
- [foo^="bar"] (attribute start with)
- [foo$="bar"] (attribute end with)
- [foo*="bar"] (attribute contains)
- :root (racine du document)
- :nth-child()
- :nth-last-child()
- :nth-of-type()
- :nth-last-of-type()
- :last-child
- :first-of-type
- :last-of-type
- :only-child
- :only-of-type
- :empty
- :target
- :enabled
- :disabled
- :checked
- :not()
- ~
Sélecteurs CSS 3
Bordures
.shadowBox {
width: 200px;
color: #444444;
background-color: #ddd; /* Fallback for older browsers */
background-image: linear-gradient(#E5E5E5, #CFCFCF);
box-shadow: -1px 2px 5px 1px rgba(0, 0, 0, 0.7) /* inset */;
margin: 2rem;
padding: 1rem;
}
Couleurs et opacité
espace de couleur sRGB sur le web (sRGB color space)
.shadowBoxLeft {
background-color: rgba(255, 0, 0, 0.5);
}
.shadowBoxRight {
background-image: linear-gradient(#0000FF, rgba(0, 0, 255, 0));
/* #0000FFFF = rgba(0, 0, 255, 1)
= rgb(0, 0, 255)
= hsla(240, 100%, 50%, 1)
= hsl(240, 100%, 50%) */
}
Transitions et transformations
.spinButton {
transition: transform 0.5s ease-in;
}
.spinButton:active {
transform: rotate(270deg);
}
.spinButton.easeout {
transition: transform 0.5s ease-out;
}
Click me ! Ease-in Click me ! Ease-out
<button class="spinButton">Click me ! Ease-in</button>
<button class="spinButton easeout">Click me ! Ease-out</button>
Note:
mots clés: css transition et css transform;
performance de transform
(scale, opacitity, rotate
) meilleures que de modifier width et height en JavaScript.
Animations
@keyframes alwaysSpinButtonAnimation {
from {transform: rotate(0deg);}
50% {transform: rotate(90deg);}
to {transform: rotate(360deg);}
}
.alwaysSpinButton {
animation-name: alwaysSpinButtonAnimation;
animation-duration: 1s;
animation-iteration-count: 10;
animation-timing-function: linear;
}
Click me !
<button class="alwaysSpinButton">Click me !</button>
Note: mots clés: css animation keyframes;
RESPONSIVE DESIGN
Responsive Web Design (RWD)
Contenu qui s'adapte à l'espace d'affichage disponible
- Grille fluide (float, display: inline-block, flexbox, CSS grid)
- Media queries (
@media screen and (min-width: 1024px) {
)- Avec breakpoints
- Sur mesure
- Element queries (nécessite JavaScript)
- images et video adaptives
- SVG et vectoriel
https://developer.mozilla.org/fr/Apps/app_layout/responsive_design_building_blocks
Progressive Enhancement
- Utiliser les fonctionnalités les répandues en premier
- Utiliser modernizr pour ajouter des fonctionnalités si possible
- Avoir un fallback CSS pour les propriétés mal supportées
Retrouver les slides sur https://github.com/Lythom/formation-api-html5-css3
Formation aux API HTML5 et CSS3 de Samuel Bouchet est mis à disposition selon les termes de la licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International.