Esc
 Naviguer  Ouvrir Esc Fermer
Aller au contenu

6.3 — APIs du navigateur

Débutant 30 min prérequis : axes 6.1 et 6.2 lus

🎯 Objectif : connaître ce que le navigateur offre nativement avant de tirer un framework. Beaucoup de besoins se résolvent en 5 lignes de vanilla JS.

À l'issue de cet axe, tu sauras :

  • Manipuler le DOM (sélection, création, événements)
  • Faire des requêtes avec fetch et FormData
  • Choisir entre localStorage, sessionStorage, IndexedDB
  • Utiliser IntersectionObserver, MutationObserver, ResizeObserver
  • Décharger un calcul lourd vers un Web Worker

Le DOM est l’arbre d’objets qui représente la page. JavaScript peut le lire et le modifier.

// Un élément
document.querySelector('#header')
document.querySelector('.btn-primary')
document.querySelector('input[type="email"]')
document.getElementById('header') // plus rapide mais moins flexible
// Plusieurs (NodeList)
document.querySelectorAll('.card')
// Itérer
document.querySelectorAll('.card').forEach(card => card.classList.add('visible'));
const el = document.querySelector('#title');
// Texte
el.textContent = 'Nouveau titre'; // brut, sécurisé
el.innerHTML = '<strong>HTML</strong>'; // ⚠ XSS si la valeur vient de l'utilisateur
// Attributs
el.setAttribute('data-state', 'open');
el.dataset.state = 'open'; // raccourci pour data-*
el.classList.add('active');
el.classList.toggle('active');
el.classList.contains('active');
// Style
el.style.backgroundColor = 'blue'; // évite, préfère ajouter une classe
el.style.setProperty('--color', '#f00'); // pour les variables CSS
// Création
const div = document.createElement('div');
div.textContent = 'Nouveau';
document.body.append(div); // append : à la fin (1+ enfants ou strings)
parent.prepend(child); // au début
parent.insertBefore(newNode, refNode); // avant un autre
// Suppression
el.remove();
button.addEventListener('click', (event) => {
console.log(event.target);
event.preventDefault(); // empêche le comportement par défaut
});
// Une fois seulement
button.addEventListener('click', handler, { once: true });
// Délégation — un seul listener pour de nombreux enfants
document.querySelector('.list').addEventListener('click', (e) => {
if (e.target.matches('.item')) {
console.log('Item cliqué :', e.target.textContent);
}
});
// GET simple
const response = await fetch('/api/users');
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const users = await response.json();
// POST JSON
const r = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Alice' }),
});
// FormData (upload, formulaires)
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('name', 'Alice');
await fetch('/api/upload', {
method: 'POST',
body: formData, // pas besoin de Content-Type, le navigateur le met
});
// Avec credentials (cookies)
fetch('/api/me', { credentials: 'include' });
try {
const r = await fetch('/api');
if (!r.ok) {
// 4xx/5xx — la requête est partie, mais le serveur a refusé
throw new Error(`HTTP ${r.status}`);
}
const data = await r.json();
} catch (err) {
if (err instanceof TypeError) {
// erreur réseau (DNS, offline, CORS bloqué…)
}
}

Important : fetch ne rejette pas sur 404 ou 500. Il faut tester r.ok. Il rejette uniquement sur erreurs réseau ou CORS.

const url = new URL('https://example.com/search?q=cat&page=2');
url.protocol; // "https:"
url.hostname; // "example.com"
url.pathname; // "/search"
url.searchParams.get('q'); // "cat"
url.searchParams.set('q', 'dog');
url.searchParams.append('tag', 'pet');
url.toString(); // "https://example.com/search?q=dog&page=2&tag=pet"
// Construire à partir de paramètres
const params = new URLSearchParams({ q: 'cat', page: 2 });
fetch(`/search?${params}`);

Bien plus propre que la concat de strings.

APICapacitéPersistanceAPI
localStorage~5 MoPersistanteclé/valeur (string)
sessionStorage~5 MoOnglet uniquementidem
IndexedDB50 % du disquePersistantebase structurée async
Cookies~4 KoConfigurableenvoyé au serveur
localStorage.setItem('user', JSON.stringify({ id: 1, name: 'Alice' }));
const user = JSON.parse(localStorage.getItem('user'));
localStorage.removeItem('user');
localStorage.clear();
// Quota dépassé
try {
localStorage.setItem('big', hugeData);
} catch (err) {
if (err.name === 'QuotaExceededError') {
// 5 Mo dépassés
}
}

Limites :

  • Synchrone (bloque le thread).
  • Stockage de strings uniquement → JSON.stringify.
  • Accessible en JS → jamais de tokens d’auth dedans.
// API native verbeuse — utilise plutôt une lib comme "idb"
import { openDB } from 'idb';
const db = await openDB('myApp', 1, {
upgrade(db) {
db.createObjectStore('todos', { keyPath: 'id', autoIncrement: true });
},
});
await db.add('todos', { title: 'Apprendre IndexedDB' });
const todos = await db.getAll('todos');

Pour des gros volumes ou des données structurées (cache offline d’une PWA, par exemple).

3 observers utiles pour réagir à des changements sans polling.

Cas typiques : lazy-load d’images, scroll spy, animation à l’apparition.

const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
observer.unobserve(entry.target); // arrêter d'observer
}
});
}, {
threshold: 0.1, // 10 % visible
rootMargin: '50px', // commence 50px avant
});
document.querySelectorAll('.fade-in').forEach(el => observer.observe(el));

MutationObserver — détecter les changements DOM

Section intitulée « MutationObserver — détecter les changements DOM »
const observer = new MutationObserver((mutations) => {
for (const m of mutations) {
if (m.type === 'childList') {
console.log('Enfants ajoutés/retirés');
}
}
});
observer.observe(document.querySelector('#content'), {
childList: true,
subtree: true,
attributes: true,
});

Utile pour les libs tierces qui injectent du DOM (chats, widgets, etc.).

ResizeObserver — détecter les changements de taille

Section intitulée « ResizeObserver — détecter les changements de taille »
const observer = new ResizeObserver((entries) => {
for (const e of entries) {
console.log(`Largeur : ${e.contentRect.width}`);
}
});
observer.observe(document.querySelector('.responsive-element'));

Bien plus performant que window.addEventListener('resize') couplé à des calculs : ne se déclenche que pour l’élément observé, et seulement quand sa taille change.

Un Web Worker tourne dans un vrai thread parallèle. Pas d’accès au DOM, mais utile pour les calculs CPU-intensifs.

main.js
const worker = new Worker(new URL('./compute.js', import.meta.url), { type: 'module' });
worker.postMessage({ data: hugeArray });
worker.onmessage = (e) => {
console.log('Résultat :', e.data);
};
compute.js
self.onmessage = (e) => {
const result = doHeavyCalculation(e.data.data);
self.postMessage(result);
};

Cas d’usage : compression, encodage, parsing de gros JSON, machine learning côté client (tensorflow.js).

Service Workers — un proxy entre la page et le réseau

Section intitulée « Service Workers — un proxy entre la page et le réseau »

Voir axe 2.4. Permet :

  • Mode hors-ligne (cache des ressources).
  • Notifications push.
  • Background sync.

C’est la base d’une PWA.

// Ajouter à l'historique
history.pushState({ id: 42 }, '', '/products/42');
// Remplacer l'URL actuelle
history.replaceState(null, '', '/login');
// Réagir au bouton retour
window.addEventListener('popstate', (e) => {
console.log('État précédent :', e.state);
router.navigate(location.pathname);
});

C’est la base d’un router SPA : on change l’URL sans recharger, on rend une nouvelle vue.

Clipboard, geolocation, file system — APIs récentes

Section intitulée « Clipboard, geolocation, file system — APIs récentes »
// Clipboard
await navigator.clipboard.writeText('hello');
const text = await navigator.clipboard.readText();
// Geolocation (demande permission)
navigator.geolocation.getCurrentPosition(
(pos) => console.log(pos.coords.latitude, pos.coords.longitude),
(err) => console.error(err),
);
// File System Access API (Chrome surtout)
const [handle] = await window.showOpenFilePicker();
const file = await handle.getFile();
const text = await file.text();
Tu fetches /api/foo, le serveur renvoie 500. Que vaut response.ok ?
Tu veux animer l'apparition d'éléments au scroll. Outil natif idéal ?
Tu veux stocker un JWT côté navigateur. Choix sécurisé ?

Suite : 6.4 — TypeScript — typer ton JS pour éliminer une catégorie entière de bugs.