Maîtrisez HTML5 moderne ! Web Components, APIs avancées, PWA, et sémantique de pointe. Créez des applications web natives dignes d'une interface S.H.I.E.L.D.
Découvrez la différence entre HTML basique et HTML moderne de niveau professionnel. Nous allons créer une interface S.H.I.E.L.D. avec les techniques les plus avancées.
<div class="card">
<div class="header">
<h2>Titre</h2>
</div>
<div class="content">
<p>Contenu simple</p>
<div onclick="alert('Click')">
Bouton
</div>
</div>
</div>
⚡ Fonctionnel mais non sémantique
<article itemscope itemtype="https://schema.org/NewsArticle">
<header role="banner">
<h1 itemprop="headline">Titre Sémantique</h1>
<time datetime="2025-01-15" itemprop="datePublished">
15 Janvier 2025
</time>
</header>
<section itemprop="articleBody">
<p>Contenu riche et accessible</p>
<smart-button type="primary"
aria-label="Action principale">
Action Intelligente
</smart-button>
</section>
</article>
🤯 Sémantique, accessible et intelligent !
customElements.define()
Custom Elements
this.attachShadow()
Shadow DOM
<template>
Templates HTML
itemscope itemtype
Schema.org
role="application"
ARIA avancé
<main> <aside>
HTML5 structural
navigator.serviceWorker
Service Workers
window.localStorage
Storage API
canvas.getContext('2d')
Canvas 2D/WebGL
manifest.json
Web App Manifest
sw.register()
Installation PWA
cache.addAll()
Cache Strategy
loading="lazy"
Lazy Loading
rel="preload"
Resource Hints
intersection-observer
APIs Performance
aria-live="polite"
Live Regions
aria-describedby
Relations ARIA
tabindex="-1"
Focus Management
Mettez en pratique les techniques HTML5 modernes avec ces 3 exercices progressifs. Créez des composants réutilisables et des interfaces intelligentes.
Custom Elements + Shadow DOM + Template + Slot
Tony Stark - Genius Inventor
Créez un Web Component réutilisable pour afficher des cartes d'Avengers avec Shadow DOM pour l'encapsulation et des slots pour la flexibilité.
HTML Template :
<template id="avenger-card-template">
<style>
/* TODO: Styles pour Shadow DOM */
:host {
/* TODO: Styles du composant hôte */
}
.card {
/* TODO: Style de la carte */
}
</style>
<div class="card">
<!-- TODO: Structure avec slots -->
<slot name="avatar"></slot>
<slot name="content"></slot>
</div>
</template>
JavaScript (à compléter) :
class AvengerCard extends HTMLElement {
constructor() {
super();
// TODO: Attacher Shadow DOM
// TODO: Cloner template
}
connectedCallback() {
// TODO: Logique de connexion
}
static get observedAttributes() {
// TODO: Attributs observés
return ['name', 'power'];
}
attributeChangedCallback(name, oldValue, newValue) {
// TODO: Réagir aux changements d'attributs
}
}
// TODO: Définir le custom element
<template id="avenger-card-template">
<style>
:host {
display: block;
margin: 1rem 0;
}
.card {
display: flex;
align-items: center;
padding: 1rem;
border: 2px solid #E34F26;
border-radius: 8px;
background: rgba(227, 79, 38, 0.1);
gap: 1rem;
transition: transform 0.3s ease;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 20px rgba(227, 79, 38, 0.3);
}
::slotted([slot="avatar"]) {
width: 64px;
height: 64px;
border-radius: 50%;
flex-shrink: 0;
}
</style>
<div class="card">
<slot name="avatar">
<div class="default-avatar">🦸</div>
</slot>
<div class="content">
<slot name="content">
<h3>Avenger</h3>
<p>Description par défaut</p>
</slot>
</div>
</div>
</template>
<script>
class AvengerCard extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({mode: 'open'});
const template = document.getElementById('avenger-card-template');
shadow.appendChild(template.content.cloneNode(true));
}
connectedCallback() {
console.log('Avenger card connectée au DOM');
}
static get observedAttributes() {
return ['name', 'power', 'description'];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'name') {
this.updateContent();
}
}
updateContent() {
// Mise à jour du contenu basée sur les attributs
const name = this.getAttribute('name') || 'Avenger';
const power = this.getAttribute('power') || 'Unknown';
this.shadowRoot.querySelector('slot[name="content"] h3').textContent = name;
this.shadowRoot.querySelector('slot[name="content"] p').textContent = power;
}
}
customElements.define('avenger-card', AvengerCard);
</script>
Service Worker + Web App Manifest + Cache Strategy
Application installable hors-ligne
Créez une Progressive Web App complète avec installation, fonctionnement hors-ligne et synchronisation en arrière-plan pour les opérations S.H.I.E.L.D.
manifest.json (à compléter) :
{
"name": "S.H.I.E.L.D. Command Center",
"short_name": "SHIELD",
// TODO: Ajouter icônes, couleurs
// TODO: Configuration display et orientation
// TODO: URLs de démarrage
"start_url": "/",
"display": "standalone",
"background_color": "#1e3c72",
"theme_color": "#E34F26"
}
Service Worker (à compléter) :
const CACHE_NAME = 'shield-v1';
const STATIC_ASSETS = [
'/',
'/index.html',
// TODO: Ajouter assets à cacher
];
self.addEventListener('install', event => {
// TODO: Mettre en cache les assets
});
self.addEventListener('fetch', event => {
// TODO: Stratégie Cache First/Network First
});
// sw.js - Service Worker S.H.I.E.L.D. v2.0
const CACHE_VERSION = 'v2.1.0';
const CACHE_NAMES = {
static: `shield-static-${CACHE_VERSION}`,
dynamic: `shield-dynamic-${CACHE_VERSION}`,
images: `shield-images-${CACHE_VERSION}`,
api: `shield-api-${CACHE_VERSION}`
};
const STATIC_ASSETS = [
'/',
'/index.html',
'/manifest.json',
'/styles/main.css',
'/styles/components.css',
'/scripts/app.js',
'/scripts/storage-manager.js',
'/icons/icon-192.png',
'/icons/icon-512.png',
'/fonts/shield-ui.woff2',
'/offline.html' // Page hors-ligne de fallback
];
const API_ENDPOINTS = [
'/api/agents',
'/api/missions',
'/api/threats',
'/api/status'
];
// 🚀 Installation - Phase de mise en cache initiale
self.addEventListener('install', event => {
console.log('[SW] Installation démarrée');
event.waitUntil(
Promise.all([
// Cache des assets statiques
caches.open(CACHE_NAMES.static)
.then(cache => {
console.log('[SW] Mise en cache des assets statiques');
return cache.addAll(STATIC_ASSETS);
}),
// Pré-cache des API critiques
caches.open(CACHE_NAMES.api)
.then(cache => {
console.log('[SW] Pré-cache des APIs critiques');
return Promise.allSettled(
API_ENDPOINTS.map(url =>
fetch(url)
.then(response => cache.put(url, response))
.catch(err => console.log(`[SW] Erreur pré-cache ${url}:`, err))
)
);
})
])
);
// Force l'activation immédiate
self.skipWaiting();
});
// ⚡ Activation - Nettoyage des anciens caches
self.addEventListener('activate', event => {
console.log('[SW] Activation démarrée');
event.waitUntil(
Promise.all([
// Nettoyage des anciens caches
caches.keys().then(cacheNames => {
const validCacheNames = Object.values(CACHE_NAMES);
return Promise.all(
cacheNames.map(cacheName => {
if (!validCacheNames.includes(cacheName)) {
console.log('[SW] Suppression ancien cache:', cacheName);
return caches.delete(cacheName);
}
})
);
}),
// Prendre le contrôle immédiatement
self.clients.claim()
])
);
});
// 🔄 Stratégies de Cache Intelligentes
self.addEventListener('fetch', event => {
const request = event.request;
const url = new URL(request.url);
// Ignorer les requêtes non-HTTP
if (!request.url.startsWith('http')) return;
// Stratégie par type de ressource
if (request.destination === 'image') {
event.respondWith(handleImageRequest(request));
} else if (url.pathname.startsWith('/api/')) {
event.respondWith(handleApiRequest(request));
} else if (request.destination === 'document') {
event.respondWith(handleDocumentRequest(request));
} else if (STATIC_ASSETS.includes(url.pathname)) {
event.respondWith(handleStaticRequest(request));
} else {
event.respondWith(handleDynamicRequest(request));
}
});
// 🖼️ Cache First pour les images
async function handleImageRequest(request) {
try {
const cachedResponse = await caches.match(request);
if (cachedResponse) {
return cachedResponse;
}
const networkResponse = await fetch(request);
const cache = await caches.open(CACHE_NAMES.images);
cache.put(request, networkResponse.clone());
return networkResponse;
} catch (error) {
console.log('[SW] Erreur image:', error);
// Retourner une image placeholder par défaut
return caches.match('/icons/placeholder.png');
}
}
// 🔄 Stale While Revalidate pour les APIs
async function handleApiRequest(request) {
const cache = await caches.open(CACHE_NAMES.api);
const cachedResponse = await cache.match(request);
// Requête réseau en parallèle
const networkPromise = fetch(request)
.then(response => {
if (response.ok) {
cache.put(request, response.clone());
}
return response;
})
.catch(error => {
console.log('[SW] API hors-ligne:', request.url);
return null;
});
// Retourner le cache immédiatement si disponible
return cachedResponse || networkPromise ||
new Response(JSON.stringify({
error: 'Service indisponible',
offline: true,
timestamp: Date.now()
}), {
headers: { 'Content-Type': 'application/json' }
});
}
// 📄 Network First pour les documents
async function handleDocumentRequest(request) {
try {
const networkResponse = await fetch(request);
return networkResponse;
} catch (error) {
console.log('[SW] Document hors-ligne, fallback vers cache');
const cachedResponse = await caches.match(request);
return cachedResponse || caches.match('/offline.html');
}
}
// ⚡ Cache First pour les assets statiques
async function handleStaticRequest(request) {
const cachedResponse = await caches.match(request);
if (cachedResponse) {
return cachedResponse;
}
try {
const networkResponse = await fetch(request);
const cache = await caches.open(CACHE_NAMES.static);
cache.put(request, networkResponse.clone());
return networkResponse;
} catch (error) {
console.log('[SW] Asset statique indisponible:', request.url);
throw error;
}
}
// 🌐 Network First pour le contenu dynamique
async function handleDynamicRequest(request) {
try {
const networkResponse = await fetch(request);
const cache = await caches.open(CACHE_NAMES.dynamic);
cache.put(request, networkResponse.clone());
return networkResponse;
} catch (error) {
const cachedResponse = await caches.match(request);
return cachedResponse || caches.match('/offline.html');
}
}
// 📡 Background Sync pour les données critiques
self.addEventListener('sync', event => {
console.log('[SW] Background Sync:', event.tag);
if (event.tag === 'sync-mission-data') {
event.waitUntil(syncMissionData());
} else if (event.tag === 'sync-agent-status') {
event.waitUntil(syncAgentStatus());
}
});
async function syncMissionData() {
try {
// Récupérer les données en attente depuis IndexedDB
const pendingData = await getStoredData('pending-missions');
for (const mission of pendingData) {
const response = await fetch('/api/missions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(mission)
});
if (response.ok) {
await removeStoredData('pending-missions', mission.id);
console.log('[SW] Mission synchronisée:', mission.id);
}
}
} catch (error) {
console.log('[SW] Erreur sync missions:', error);
}
}
// 🔔 Push Notifications pour les alertes
self.addEventListener('push', event => {
console.log('[SW] Push reçu:', event);
const options = {
body: 'Nouvelle alerte de sécurité',
icon: '/icons/icon-192.png',
badge: '/icons/badge.png',
vibrate: [200, 100, 200],
data: {
url: '/alerts',
timestamp: Date.now()
},
actions: [
{
action: 'view',
title: 'Voir l\'alerte',
icon: '/icons/view.png'
},
{
action: 'dismiss',
title: 'Ignorer',
icon: '/icons/dismiss.png'
}
]
};
if (event.data) {
const payload = event.data.json();
options.body = payload.message;
options.data.url = payload.url;
}
event.waitUntil(
self.registration.showNotification('S.H.I.E.L.D. Alert', options)
);
});
// Gestion des clics sur notifications
self.addEventListener('notificationclick', event => {
event.notification.close();
const action = event.action;
const url = event.notification.data.url;
if (action === 'view' || !action) {
event.waitUntil(
clients.openWindow(url)
);
}
// L'action 'dismiss' ferme juste la notification
});
// 🔧 Fonctions utilitaires pour IndexedDB
async function getStoredData(storeName) {
// Implémentation simplifiée - voir section localStorage avancée
return [];
}
async function removeStoredData(storeName, id) {
// Implémentation simplifiée - voir section localStorage avancée
return true;
}
// app.js - Gestion Service Worker côté client
class ServiceWorkerManager {
constructor() {
this.registration = null;
this.isOnline = navigator.onLine;
this.initServiceWorker();
this.setupEventListeners();
}
async initServiceWorker() {
if ('serviceWorker' in navigator) {
try {
this.registration = await navigator.serviceWorker.register('/sw.js', {
scope: '/'
});
console.log('Service Worker enregistré:', this.registration.scope);
// Écouter les mises à jour
this.registration.addEventListener('updatefound', () => {
const newWorker = this.registration.installing;
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
this.showUpdateAvailable();
}
});
});
} catch (error) {
console.error('Erreur Service Worker:', error);
}
}
}
setupEventListeners() {
// Détecter les changements de connexion
window.addEventListener('online', () => {
this.isOnline = true;
this.showConnectionStatus('En ligne');
this.syncPendingData();
});
window.addEventListener('offline', () => {
this.isOnline = false;
this.showConnectionStatus('Mode hors-ligne');
});
// Écouter les messages du Service Worker
navigator.serviceWorker.addEventListener('message', event => {
const { type, data } = event.data;
switch (type) {
case 'CACHE_UPDATED':
this.showCacheUpdateNotification(data);
break;
case 'SYNC_COMPLETED':
this.showSyncCompletedNotification();
break;
}
});
}
showUpdateAvailable() {
const notification = document.createElement('div');
notification.className = 'update-notification';
notification.innerHTML = `
Nouvelle version disponible!
`;
document.body.appendChild(notification);
}
async updateServiceWorker() {
if (this.registration && this.registration.waiting) {
this.registration.waiting.postMessage({ type: 'SKIP_WAITING' });
window.location.reload();
}
}
async syncPendingData() {
if ('serviceWorker' in navigator && 'sync' in window.ServiceWorkerRegistration.prototype) {
try {
await this.registration.sync.register('sync-mission-data');
await this.registration.sync.register('sync-agent-status');
console.log('Background Sync programmé');
} catch (error) {
console.log('Background Sync non supporté, sync immédiat');
this.performImmediateSync();
}
}
}
showConnectionStatus(status) {
const statusEl = document.getElementById('connection-status');
if (statusEl) {
statusEl.textContent = status;
statusEl.className = this.isOnline ? 'text-green-500' : 'text-orange-500';
}
}
}
// Initialiser le gestionnaire
const swManager = new ServiceWorkerManager();
// storage-manager.js - Gestionnaire de stockage S.H.I.E.L.D.
class StorageManager {
constructor() {
this.storageQuota = 0;
this.storageUsage = 0;
this.compressionEnabled = true;
this.encryptionKey = this.generateEncryptionKey();
this.initStorage();
this.monitorQuota();
}
async initStorage() {
// Vérifier les capacités de stockage
if ('storage' in navigator && 'estimate' in navigator.storage) {
const estimate = await navigator.storage.estimate();
this.storageQuota = estimate.quota;
this.storageUsage = estimate.usage;
console.log(`[Storage] Quota: ${this.formatBytes(this.storageQuota)}`);
console.log(`[Storage] Utilisé: ${this.formatBytes(this.storageUsage)}`);
}
// Initialiser IndexedDB pour les données complexes
this.initIndexedDB();
// Migration depuis localStorage si nécessaire
this.migrateFromLocalStorage();
}
// 🗄️ localStorage Optimisé pour les données simples
setItem(key, value, options = {}) {
try {
const data = {
value: value,
timestamp: Date.now(),
expiry: options.expiry || null,
compressed: false,
encrypted: options.encrypt || false
};
// Compression pour les grandes données
if (JSON.stringify(data).length > 1024 && this.compressionEnabled) {
data.value = this.compress(JSON.stringify(data.value));
data.compressed = true;
}
// Chiffrement pour les données sensibles
if (options.encrypt) {
data.value = this.encrypt(JSON.stringify(data.value));
}
localStorage.setItem(key, JSON.stringify(data));
// Mettre à jour les métriques
this.updateStorageMetrics();
return true;
} catch (error) {
console.error('[Storage] Erreur setItem:', error);
// Gestion du quota dépassé
if (error.name === 'QuotaExceededError') {
this.handleQuotaExceeded(key, value);
}
return false;
}
}
getItem(key) {
try {
const item = localStorage.getItem(key);
if (!item) return null;
const data = JSON.parse(item);
// Vérifier l'expiration
if (data.expiry && Date.now() > data.expiry) {
this.removeItem(key);
return null;
}
let value = data.value;
// Décompression si nécessaire
if (data.compressed) {
value = JSON.parse(this.decompress(value));
}
// Déchiffrement si nécessaire
if (data.encrypted) {
value = JSON.parse(this.decrypt(value));
}
return value;
} catch (error) {
console.error('[Storage] Erreur getItem:', error);
return null;
}
}
removeItem(key) {
localStorage.removeItem(key);
this.updateStorageMetrics();
}
// 📊 Gestion intelligente du cache
handleQuotaExceeded(key, value) {
console.log('[Storage] Quota dépassé, nettoyage automatique');
// Stratégie LRU (Least Recently Used)
const items = [];
for (let i = 0; i < localStorage.length; i++) {
const itemKey = localStorage.key(i);
const item = localStorage.getItem(itemKey);
try {
const data = JSON.parse(item);
items.push({
key: itemKey,
timestamp: data.timestamp,
size: item.length
});
} catch (e) {
// Élément corrompu, le supprimer
localStorage.removeItem(itemKey);
}
}
// Trier par timestamp (plus ancien en premier)
items.sort((a, b) => a.timestamp - b.timestamp);
// Supprimer les éléments les plus anciens jusqu'à libérer 25% de l'espace
let freedSpace = 0;
const targetSpace = this.getLocalStorageSize() * 0.25;
for (const item of items) {
if (freedSpace >= targetSpace) break;
localStorage.removeItem(item.key);
freedSpace += item.size;
console.log(`[Storage] Supprimé: ${item.key} (${this.formatBytes(item.size)})`);
}
// Réessayer l'opération initiale
this.setItem(key, value);
}
// 💾 IndexedDB pour les données complexes
async initIndexedDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open('ShieldDB', 2);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.db = request.result;
resolve(this.db);
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
// Store pour les missions
if (!db.objectStoreNames.contains('missions')) {
const missionStore = db.createObjectStore('missions', { keyPath: 'id' });
missionStore.createIndex('status', 'status', { unique: false });
missionStore.createIndex('priority', 'priority', { unique: false });
missionStore.createIndex('timestamp', 'timestamp', { unique: false });
}
// Store pour les agents
if (!db.objectStoreNames.contains('agents')) {
const agentStore = db.createObjectStore('agents', { keyPath: 'id' });
agentStore.createIndex('status', 'status', { unique: false });
agentStore.createIndex('clearanceLevel', 'clearanceLevel', { unique: false });
}
// Store pour les données en attente de sync
if (!db.objectStoreNames.contains('pending-sync')) {
const syncStore = db.createObjectStore('pending-sync', {
keyPath: 'id',
autoIncrement: true
});
syncStore.createIndex('type', 'type', { unique: false });
syncStore.createIndex('timestamp', 'timestamp', { unique: false });
}
// Store pour le cache des images
if (!db.objectStoreNames.contains('image-cache')) {
const imageStore = db.createObjectStore('image-cache', { keyPath: 'url' });
imageStore.createIndex('timestamp', 'timestamp', { unique: false });
}
};
});
}
// 📝 Opérations IndexedDB avancées
async saveMission(mission) {
const transaction = this.db.transaction(['missions'], 'readwrite');
const store = transaction.objectStore('missions');
// Ajouter timestamp et métadonnées
mission.timestamp = Date.now();
mission.lastModified = Date.now();
mission.syncStatus = navigator.onLine ? 'synced' : 'pending';
return new Promise((resolve, reject) => {
const request = store.put(mission);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async getMissions(filters = {}) {
const transaction = this.db.transaction(['missions'], 'readonly');
const store = transaction.objectStore('missions');
return new Promise((resolve, reject) => {
const request = store.getAll();
request.onsuccess = () => {
let missions = request.result;
// Appliquer les filtres
if (filters.status) {
missions = missions.filter(m => m.status === filters.status);
}
if (filters.priority) {
missions = missions.filter(m => m.priority >= filters.priority);
}
// Trier par timestamp décroissant
missions.sort((a, b) => b.timestamp - a.timestamp);
resolve(missions);
};
request.onerror = () => reject(request.error);
});
}
// 🔐 Chiffrement simple des données sensibles
encrypt(data) {
// Implémentation simple XOR pour la démonstration
// En production, utiliser Web Crypto API
let encrypted = '';
for (let i = 0; i < data.length; i++) {
encrypted += String.fromCharCode(
data.charCodeAt(i) ^ this.encryptionKey.charCodeAt(i % this.encryptionKey.length)
);
}
return btoa(encrypted);
}
decrypt(encryptedData) {
const encrypted = atob(encryptedData);
let decrypted = '';
for (let i = 0; i < encrypted.length; i++) {
decrypted += String.fromCharCode(
encrypted.charCodeAt(i) ^ this.encryptionKey.charCodeAt(i % this.encryptionKey.length)
);
}
return decrypted;
}
generateEncryptionKey() {
// Générer une clé basée sur des caractéristiques du navigateur
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.textBaseline = 'top';
ctx.font = '14px Arial';
ctx.fillText('S.H.I.E.L.D. Security', 2, 2);
return canvas.toDataURL().slice(-16);
}
// 📊 Compression simple
compress(str) {
// Compression RLE simplifiée pour la démonstration
return str.replace(/(.)\1+/g, (match, char) => char + match.length);
}
decompress(str) {
// Décompression RLE
return str.replace(/(.)\d+/g, (match, char) => {
const count = parseInt(match.slice(1));
return char.repeat(count);
});
}
// 📈 Métriques et monitoring
updateStorageMetrics() {
const usage = this.getLocalStorageSize();
const percentage = (usage / (5 * 1024 * 1024)) * 100; // 5MB typique
// Émettre un événement pour l'UI
window.dispatchEvent(new CustomEvent('storage-update', {
detail: {
usage: usage,
percentage: percentage,
formatted: this.formatBytes(usage)
}
}));
// Alerter si l'usage dépasse 80%
if (percentage > 80) {
console.warn('[Storage] Utilisation élevée:', percentage.toFixed(1) + '%');
}
}
getLocalStorageSize() {
let total = 0;
for (let key in localStorage) {
if (localStorage.hasOwnProperty(key)) {
total += localStorage[key].length;
}
}
return total;
}
formatBytes(bytes) {
const units = ['B', 'KB', 'MB', 'GB'];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(1)} ${units[unitIndex]}`;
}
async monitorQuota() {
if ('storage' in navigator && 'estimate' in navigator.storage) {
const estimate = await navigator.storage.estimate();
const usagePercentage = (estimate.usage / estimate.quota) * 100;
console.log(`[Storage] Utilisation totale: ${usagePercentage.toFixed(1)}%`);
// Demander le stockage persistant si utilisation élevée
if (usagePercentage > 70 && 'persist' in navigator.storage) {
const persistent = await navigator.storage.persist();
console.log('[Storage] Persistance:', persistent ? 'accordée' : 'refusée');
}
}
}
}
// 🎯 Initialisation et utilisation
const storageManager = new StorageManager();
// Exemples d'utilisation
const shieldAPI = {
// Sauvegarder une mission avec chiffrement
async saveMission(mission) {
mission.classified = true;
await storageManager.saveMission(mission);
// Sauvegarder aussi en localStorage pour accès rapide
storageManager.setItem(`mission_${mission.id}`, {
id: mission.id,
title: mission.title,
status: mission.status
}, { encrypt: mission.classified });
},
// Récupérer les données hors-ligne
async getOfflineData() {
return {
missions: await storageManager.getMissions(),
agents: storageManager.getItem('agents') || [],
lastSync: storageManager.getItem('last-sync')
};
},
// Nettoyer les données expirées
cleanupExpiredData() {
const keys = Object.keys(localStorage);
keys.forEach(key => {
const item = storageManager.getItem(key);
if (item === null) {
console.log(`[Cleanup] Supprimé: ${key}`);
}
});
}
};
// Event listeners pour l'UI
window.addEventListener('storage-update', (event) => {
const { usage, percentage, formatted } = event.detail;
const statusEl = document.getElementById('storage-status');
if (statusEl) {
statusEl.innerHTML = `
Stockage: ${formatted}
`;
}
});
// Stratégies de stockage par type de données
const StorageStrategies = {
// 🎯 Configuration utilisateur - localStorage avec backup
userPreferences: {
save: (preferences) => {
storageManager.setItem('user-preferences', preferences);
// Backup en IndexedDB
storageManager.db.transaction(['user-data'], 'readwrite')
.objectStore('user-data')
.put({ id: 'preferences', data: preferences });
},
load: async () => {
// Essayer localStorage d'abord (plus rapide)
let prefs = storageManager.getItem('user-preferences');
if (!prefs) {
// Fallback vers IndexedDB
const transaction = storageManager.db.transaction(['user-data'], 'readonly');
const result = await transaction.objectStore('user-data').get('preferences');
prefs = result?.data;
}
return prefs || getDefaultPreferences();
}
},
// 📱 État de l'application - sessionStorage + localStorage
appState: {
save: (state) => {
// Session courante
sessionStorage.setItem('app-state', JSON.stringify(state));
// Persistance entre sessions (état critique seulement)
const persistentState = {
currentMission: state.currentMission,
userLocation: state.userLocation,
securityLevel: state.securityLevel
};
storageManager.setItem('persistent-state', persistentState);
},
restore: () => {
// État de session d'abord
const sessionState = JSON.parse(sessionStorage.getItem('app-state') || '{}');
// Compléter avec l'état persistant
const persistentState = storageManager.getItem('persistent-state') || {};
return { ...persistentState, ...sessionState };
}
},
// 💾 Cache intelligent avec TTL
smartCache: {
set: (key, data, ttl = 3600000) => { // TTL par défaut: 1 heure
storageManager.setItem(`cache_${key}`, data, {
expiry: Date.now() + ttl
});
},
get: (key) => {
return storageManager.getItem(`cache_${key}`);
},
invalidate: (pattern) => {
const keys = Object.keys(localStorage);
keys.forEach(key => {
if (key.startsWith('cache_') && key.includes(pattern)) {
storageManager.removeItem(key);
}
});
}
},
// 🔄 Queue de synchronisation
syncQueue: {
add: async (operation) => {
const transaction = storageManager.db.transaction(['pending-sync'], 'readwrite');
const store = transaction.objectStore('pending-sync');
await new Promise((resolve, reject) => {
const request = store.add({
type: operation.type,
data: operation.data,
timestamp: Date.now(),
retries: 0,
maxRetries: 3
});
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
},
process: async () => {
const transaction = storageManager.db.transaction(['pending-sync'], 'readwrite');
const store = transaction.objectStore('pending-sync');
const operations = await new Promise((resolve, reject) => {
const request = store.getAll();
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
for (const op of operations) {
try {
await this.executeOperation(op);
// Supprimer après succès
await store.delete(op.id);
} catch (error) {
op.retries++;
if (op.retries >= op.maxRetries) {
console.error('[Sync] Échec définitif:', op, error);
await store.delete(op.id);
} else {
await store.put(op);
}
}
}
},
executeOperation: async (operation) => {
switch (operation.type) {
case 'mission-update':
await fetch('/api/missions/' + operation.data.id, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(operation.data)
});
break;
case 'agent-status':
await fetch('/api/agents/' + operation.data.id + '/status', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status: operation.data.status })
});
break;
}
}
}
};
// Utilisation des stratégies
class ShieldDataManager {
constructor() {
this.strategies = StorageStrategies;
this.initializeDataLayer();
}
async initializeDataLayer() {
// Restaurer l'état de l'application
const appState = this.strategies.appState.restore();
window.appState = appState;
// Charger les préférences utilisateur
const preferences = await this.strategies.userPreferences.load();
this.applyUserPreferences(preferences);
// Traiter la queue de synchronisation
if (navigator.onLine) {
await this.strategies.syncQueue.process();
}
}
// API unifié pour les différents types de données
async saveData(type, key, data, options = {}) {
switch (type) {
case 'cache':
this.strategies.smartCache.set(key, data, options.ttl);
break;
case 'user-pref':
this.strategies.userPreferences.save(data);
break;
case 'app-state':
this.strategies.appState.save(data);
break;
case 'mission':
await storageManager.saveMission(data);
if (!navigator.onLine) {
await this.strategies.syncQueue.add({
type: 'mission-update',
data: data
});
}
break;
}
}
async loadData(type, key, defaultValue = null) {
switch (type) {
case 'cache':
return this.strategies.smartCache.get(key) || defaultValue;
case 'user-pref':
return await this.strategies.userPreferences.load();
case 'app-state':
return this.strategies.appState.restore();
case 'missions':
return await storageManager.getMissions();
default:
return storageManager.getItem(key) || defaultValue;
}
}
}
const dataManager = new ShieldDataManager();
manifest.json :
{
"name": "S.H.I.E.L.D. Command Center",
"short_name": "SHIELD",
"description": "Interface de commandement S.H.I.E.L.D. avec capacités hors-ligne avancées",
"start_url": "/",
"display": "standalone",
"background_color": "#1e3c72",
"theme_color": "#E34F26",
"orientation": "portrait",
"scope": "/",
"lang": "fr",
"dir": "ltr",
"categories": ["security", "productivity", "utilities"],
"icons": [
{
"src": "icons/icon-72.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src": "icons/icon-96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "icons/icon-128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "icons/icon-144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "icons/icon-152.png",
"sizes": "152x152",
"type": "image/png"
},
{
"src": "icons/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icons/icon-384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "icons/icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"shortcuts": [
{
"name": "Missions actives",
"url": "/missions?status=active",
"description": "Voir les missions en cours"
},
{
"name": "Alertes urgentes",
"url": "/alerts?priority=high",
"description": "Alertes de sécurité prioritaires"
},
{
"name": "Statut agents",
"url": "/agents",
"description": "État des agents sur le terrain"
}
],
"screenshots": [
{
"src": "screenshots/desktop-1280x720.png",
"sizes": "1280x720",
"type": "image/png",
"form_factor": "wide",
"label": "Interface principale desktop"
},
{
"src": "screenshots/mobile-540x720.png",
"sizes": "540x720",
"type": "image/png",
"form_factor": "narrow",
"label": "Interface mobile responsive"
}
],
"related_applications": [
{
"platform": "play",
"url": "https://play.google.com/store/apps/details?id=com.shield.command",
"id": "com.shield.command"
}
]
}
Canvas 2D + WebGL + Interaction + Animation
Créez une interface holographique interactive utilisant Canvas 2D et WebGL. L'utilisateur peut interagir avec des éléments 3D et voir des données en temps réel.
HTML Canvas :
<canvas id="hologram"
width="800"
height="600"
aria-label="Interface holographique interactive">
Interface holographique non supportée
</canvas>
JavaScript (à compléter) :
class HologramInterface {
constructor(canvasId) {
this.canvas = document.getElementById(canvasId);
this.ctx = this.canvas.getContext('2d');
// TODO: Initialiser WebGL si disponible
// TODO: Configurer les événements de souris
}
render() {
// TODO: Nettoyer le canvas
// TODO: Dessiner les éléments holographiques
// TODO: Animer les particules
requestAnimationFrame(() => this.render());
}
handleMouseMove(event) {
// TODO: Interaction avec la souris
}
}
// TODO: Initialiser l'interface
class HologramInterface {
constructor(canvasId) {
this.canvas = document.getElementById(canvasId);
this.ctx = this.canvas.getContext('2d');
this.mouseX = 0;
this.mouseY = 0;
this.particles = [];
this.time = 0;
this.initParticles();
this.bindEvents();
this.render();
}
initParticles() {
for (let i = 0; i < 50; i++) {
this.particles.push({
x: Math.random() * this.canvas.width,
y: Math.random() * this.canvas.height,
vx: (Math.random() - 0.5) * 2,
vy: (Math.random() - 0.5) * 2,
size: Math.random() * 3 + 1,
color: `hsl(${120 + Math.random() * 60}, 70%, 50%)`
});
}
}
bindEvents() {
this.canvas.addEventListener('mousemove', (e) => {
const rect = this.canvas.getBoundingClientRect();
this.mouseX = e.clientX - rect.left;
this.mouseY = e.clientY - rect.top;
});
this.canvas.addEventListener('click', (e) => {
this.createRipple(this.mouseX, this.mouseY);
});
}
createRipple(x, y) {
// Effet de ripple au clic
for (let i = 0; i < 20; i++) {
const angle = (i / 20) * Math.PI * 2;
this.particles.push({
x: x,
y: y,
vx: Math.cos(angle) * 5,
vy: Math.sin(angle) * 5,
size: 2,
color: '#00FF94',
life: 60
});
}
}
drawGrid() {
this.ctx.strokeStyle = 'rgba(0, 217, 255, 0.2)';
this.ctx.lineWidth = 1;
const gridSize = 30;
for (let x = 0; x < this.canvas.width; x += gridSize) {
this.ctx.beginPath();
this.ctx.moveTo(x, 0);
this.ctx.lineTo(x, this.canvas.height);
this.ctx.stroke();
}
for (let y = 0; y < this.canvas.height; y += gridSize) {
this.ctx.beginPath();
this.ctx.moveTo(0, y);
this.ctx.lineTo(this.canvas.width, y);
this.ctx.stroke();
}
}
drawCursor() {
const radius = 20;
const pulseRadius = radius + Math.sin(this.time * 0.1) * 5;
this.ctx.strokeStyle = '#00FF94';
this.ctx.lineWidth = 2;
this.ctx.beginPath();
this.ctx.arc(this.mouseX, this.mouseY, pulseRadius, 0, Math.PI * 2);
this.ctx.stroke();
// Croix au centre
this.ctx.beginPath();
this.ctx.moveTo(this.mouseX - 10, this.mouseY);
this.ctx.lineTo(this.mouseX + 10, this.mouseY);
this.ctx.moveTo(this.mouseX, this.mouseY - 10);
this.ctx.lineTo(this.mouseX, this.mouseY + 10);
this.ctx.stroke();
}
updateParticles() {
for (let i = this.particles.length - 1; i >= 0; i--) {
const p = this.particles[i];
p.x += p.vx;
p.y += p.vy;
// Rebond sur les bords
if (p.x < 0 || p.x > this.canvas.width) p.vx *= -1;
if (p.y < 0 || p.y > this.canvas.height) p.vy *= -1;
// Attraction vers la souris
const dx = this.mouseX - p.x;
const dy = this.mouseY - p.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 100) {
p.vx += dx * 0.0001;
p.vy += dy * 0.0001;
}
// Vie limitée pour les ripples
if (p.life !== undefined) {
p.life--;
if (p.life <= 0) {
this.particles.splice(i, 1);
continue;
}
}
// Dessiner la particule
this.ctx.fillStyle = p.color;
this.ctx.beginPath();
this.ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
this.ctx.fill();
}
}
drawHUD() {
// Interface HUD style S.H.I.E.L.D.
this.ctx.strokeStyle = '#00D9FF';
this.ctx.fillStyle = '#00D9FF';
this.ctx.font = '14px monospace';
this.ctx.lineWidth = 2;
// Coin supérieur gauche
this.ctx.strokeRect(10, 10, 200, 80);
this.ctx.fillText('S.H.I.E.L.D. SYSTEM', 20, 30);
this.ctx.fillText(`PARTICLES: ${this.particles.length}`, 20, 50);
this.ctx.fillText(`CURSOR: ${this.mouseX}, ${this.mouseY}`, 20, 70);
// Coin supérieur droit
const rightX = this.canvas.width - 150;
this.ctx.strokeRect(rightX, 10, 140, 60);
this.ctx.fillText('STATUS: ONLINE', rightX + 10, 30);
this.ctx.fillText(`TIME: ${this.time}`, rightX + 10, 50);
}
render() {
this.time++;
// Nettoyer le canvas avec un effet de trail
this.ctx.fillStyle = 'rgba(15, 20, 25, 0.1)';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
this.drawGrid();
this.updateParticles();
this.drawCursor();
this.drawHUD();
requestAnimationFrame(() => this.render());
}
}
// Initialiser l'interface quand le DOM est prêt
document.addEventListener('DOMContentLoaded', () => {
new HologramInterface('hologram');
});
Voici le résultat final : une Progressive Web App complète utilisant toutes les techniques HTML5 avancées. Interface de commandement S.H.I.E.L.D. installable avec fonctionnement hors-ligne.
Field Operations
Global Security
Response Team
Worldwide Operations
Helicarrier surveillance protocol
Enhanced individual recovery
Infrastructure
App native avec icône sur l'écran d'accueil
Fonctionne sans connexion internet
Alertes mission en temps réel
Chargement instantané avec le cache
Vous venez de découvrir les techniques HTML5 les plus avancées ! Web Components, PWA, APIs natives - tout pour créer des applications web modernes.