AzDev

Le Browser Object Model (BOM) en JavaScript : Interaction avec l'Environnement de Navigation

Cet article explore le Browser Object Model (BOM) en JavaScript, un ensemble d'interfaces permettant aux développeurs d'interagir avec le navigateur web au-delà du contenu de la page. Nous examinerons les objets principaux du BOM, leurs propriétés et méthodes, ainsi que les cas d'utilisation pratiques pour créer des applications web interactives et réactives.

Publié le
Le Browser Object Model (BOM) en JavaScript : Interaction avec l'Environnement de Navigation

Introduction

Le Browser Object Model (BOM) est une interface JavaScript qui permet aux développeurs d'interagir avec le navigateur web lui-même, plutôt qu'avec le contenu de la page (ce qui est le domaine du DOM). Si le DOM représente la structure du document HTML, le BOM représente le "reste" du navigateur - la fenêtre, l'historique, la localisation, l'écran, les cookies, et bien d'autres fonctionnalités.

Contrairement au DOM, le BOM n'a pas été standardisé pendant longtemps, ce qui a conduit à des implémentations variables entre les navigateurs. Cependant, avec l'évolution des standards web, de nombreuses parties du BOM ont été formalisées dans la spécification HTML5.

Dans cet article, nous explorerons les principales interfaces du BOM, comment les utiliser efficacement, et comment elles peuvent être exploitées pour créer des applications web plus riches et interactives. Nous verrons également comment le BOM s'intègre avec le DOM pour offrir une expérience utilisateur complète et fluide.

1. L'Objet Window : La Porte d'Entrée du BOM

1.1 Vue d'Ensemble de l'Objet Window

L'objet window est l'objet global en JavaScript côté navigateur et représente la fenêtre du navigateur. Il sert de porte d'entrée vers tous les autres objets BOM et DOM.

1// L'objet window est l'objet global
2console.log(window); // Affiche l'objet window complet
3
4// Les variables globales deviennent des propriétés de window
5var globalVar = "Je suis global";
6console.log(window.globalVar); // "Je suis global"
7
8// Les fonctions globales deviennent des méthodes de window
9function globalFunction() {
10  return "Fonction globale";
11}
12console.log(window.globalFunction()); // "Fonction globale"

1.2 Dimensions et Position de la Fenêtre

1// Dimensions intérieures (zone de contenu)
2console.log("Largeur intérieure:", window.innerWidth);
3console.log("Hauteur intérieure:", window.innerHeight);
4
5// Dimensions extérieures (incluant les barres de défilement, etc.)
6console.log("Largeur extérieure:", window.outerWidth);
7console.log("Hauteur extérieure:", window.outerHeight);
8
9// Position de la fenêtre (par rapport à l'écran)
10console.log("Position X:", window.screenX || window.screenLeft);
11console.log("Position Y:", window.screenY || window.screenTop);
12
13// Défilement de la page
14console.log("Défilement horizontal:", window.scrollX || window.pageXOffset);
15console.log("Défilement vertical:", window.scrollY || window.pageYOffset);

1.3 Manipulation de la Fenêtre

1// Ouvrir une nouvelle fenêtre
2const newWindow = window.open("https://example.com", "ExempleWindow", 
3  "width=800,height=600,resizable=yes,scrollbars=yes");
4
5// Fermer la fenêtre
6newWindow.close();
7
8// Redimensionner la fenêtre
9window.resizeTo(1024, 768);
10
11// Déplacer la fenêtre
12window.moveTo(100, 100);
13
14// Faire défiler la page
15window.scrollTo({
16  top: 1000,
17  left: 0,
18  behavior: 'smooth'
19});
20
21// Faire défiler de façon relative
22window.scrollBy({
23  top: 100,
24  left: 0,
25  behavior: 'smooth'
26});

1.4 Temporisation et Intervalles

1// Exécuter une fonction après un délai (une seule fois)
2const timeoutId = setTimeout(() => {
3  console.log("Cette fonction s'exécute après 2 secondes");
4}, 2000);
5
6// Annuler un timeout
7clearTimeout(timeoutId);
8
9// Exécuter une fonction à intervalles réguliers
10const intervalId = setInterval(() => {
11  console.log("Cette fonction s'exécute toutes les secondes");
12}, 1000);
13
14// Arrêter un intervalle
15clearInterval(intervalId);
16
17// Planifier pour le prochain frame d'animation
18requestAnimationFrame(function animate(timestamp) {
19  // Code d'animation
20  requestAnimationFrame(animate);
21});

2. L'Objet Navigator : Informations sur le Navigateur

2.1 Identification du Navigateur

1// Informations sur le navigateur
2console.log("User Agent:", navigator.userAgent);
3console.log("Nom du navigateur:", navigator.appName);
4console.log("Version du navigateur:", navigator.appVersion);
5console.log("Plateforme:", navigator.platform);
6console.log("Langue:", navigator.language);
7
8// Fonction de détection de navigateur (à utiliser avec précaution)
9function detectBrowser() {
10  const userAgent = navigator.userAgent;
11  
12  if (userAgent.indexOf("Firefox") > -1) {
13    return "Firefox";
14  } else if (userAgent.indexOf("Chrome") > -1 && userAgent.indexOf("Edge") === -1) {
15    return "Chrome";
16  } else if (userAgent.indexOf("Safari") > -1 && userAgent.indexOf("Chrome") === -1) {
17    return "Safari";
18  } else if (userAgent.indexOf("Edge") > -1) {
19    return "Edge";
20  } else if (userAgent.indexOf("MSIE") > -1 || userAgent.indexOf("Trident/") > -1) {
21    return "Internet Explorer";
22  } else {
23    return "Unknown";
24  }
25}

2.2 Géolocalisation

1// Vérifier si la géolocalisation est disponible
2if ("geolocation" in navigator) {
3  // Obtenir la position actuelle
4  navigator.geolocation.getCurrentPosition(
5    // Succès
6    position => {
7      const latitude = position.coords.latitude;
8      const longitude = position.coords.longitude;
9      console.log(`Position: ${latitude}, ${longitude}`);
10    },
11    // Erreur
12    error => {
13      console.error("Erreur de géolocalisation:", error.message);
14    },
15    // Options
16    {
17      enableHighAccuracy: true,
18      timeout: 5000,
19      maximumAge: 0
20    }
21  );
22  
23  // Surveiller les changements de position
24  const watchId = navigator.geolocation.watchPosition(
25    position => {
26      console.log("Nouvelle position:", position.coords);
27    }
28  );
29  
30  // Arrêter la surveillance
31  navigator.geolocation.clearWatch(watchId);
32}

2.3 Connectivité Réseau

1// Vérifier l'état de la connexion
2if ("onLine" in navigator) {
3  console.log("État de la connexion:", navigator.onLine ? "En ligne" : "Hors ligne");
4  
5  // Écouter les changements de connectivité
6  window.addEventListener("online", () => {
7    console.log("L'utilisateur est maintenant en ligne");
8  });
9  
10  window.addEventListener("offline", () => {
11    console.log("L'utilisateur est maintenant hors ligne");
12  });
13}

2.4 APIs Modernes du Navigator

1// Vérifier si les notifications sont supportées
2if ("Notification" in window) {
3  Notification.requestPermission().then(permission => {
4    if (permission === "granted") {
5      new Notification("Bonjour!", {
6        body: "Ceci est un exemple de notification",
7        icon: "/path/to/icon.png"
8      });
9    }
10  });
11}
12
13// API de batterie
14if ("getBattery" in navigator) {
15  navigator.getBattery().then(battery => {
16    console.log(`Niveau de batterie: ${battery.level * 100}%`);
17    console.log(`En charge: ${battery.charging ? "Oui" : "Non"}`);
18    
19    battery.addEventListener("levelchange", () => {
20      console.log(`Nouveau niveau: ${battery.level * 100}%`);
21    });
22    
23    battery.addEventListener("chargingchange", () => {
24      console.log(`État de charge changé: ${battery.charging ? "En charge" : "Déconnecté"}`);
25    });
26  });
27}
28
29// API d'enregistrement de l'écran
30if ("mediaDevices" in navigator && "getDisplayMedia" in navigator.mediaDevices) {
31  async function startScreenCapture() {
32    try {
33      const stream = await navigator.mediaDevices.getDisplayMedia({
34        video: true
35      });
36      // Utiliser le flux pour l'enregistrement ou le partage
37    } catch (err) {
38      console.error("Erreur d'accès à l'écran:", err);
39    }
40  }
41}

3. L'Objet Location : Navigation et URL

3.1 Propriétés de l'URL Courante

1// URL complète
2console.log("URL complète:", location.href); // https://example.com:8080/path/page.html?query=value#section
3
4// Parties composantes
5console.log("Protocole:", location.protocol); // https:
6console.log("Nom d'hôte:", location.hostname); // example.com
7console.log("Port:", location.port); // 8080
8console.log("Chemin:", location.pathname); // /path/page.html
9console.log("Requête:", location.search); // ?query=value
10console.log("Ancre:", location.hash); // #section
11console.log("Origine:", location.origin); // https://example.com:8080

3.2 Navigation Programmée

1// Rediriger vers une nouvelle URL (ajouté à l'historique)
2location.assign("https://example.com/nouvelle-page");
3
4// Remplacer l'URL actuelle (sans ajouter à l'historique)
5location.replace("https://example.com/autre-page");
6
7// Recharger la page
8location.reload();
9
10// Recharger la page depuis le serveur (sans cache)
11location.reload(true);
12
13// Modification directe de propriétés (provoque une navigation)
14location.href = "https://example.com/page-directe";
15location.pathname = "/nouveau-chemin";
16location.search = "?nouvelle=requete";
17location.hash = "#nouvelle-section";

3.3 Parsing et Manipulation des Paramètres d'URL

1// Création d'un objet URLSearchParams
2const urlParams = new URLSearchParams(location.search);
3
4// Récupération de valeurs
5const value = urlParams.get("param");
6const allValues = urlParams.getAll("param"); // Pour les paramètres multiples
7
8// Vérification de l'existence
9const hasParam = urlParams.has("param");
10
11// Itération sur tous les paramètres
12for (const [key, value] of urlParams.entries()) {
13  console.log(`${key}: ${value}`);
14}
15
16// Modification
17urlParams.set("param", "nouvelle-valeur");
18urlParams.append("nouveau", "valeur");
19urlParams.delete("inutile");
20
21// Conversion en chaîne pour utilisation
22const newSearchString = urlParams.toString();
23location.search = newSearchString;
24
25// Utilisation de URL API
26const url = new URL(location.href);
27console.log(url.searchParams.get("param"));

4. L'Objet History : Gestion de l'Historique du Navigateur

4.1 Navigation dans l'Historique

1// Nombre d'entrées dans l'historique
2console.log("Longueur de l'historique:", history.length);
3
4// Navigation arrière (équivalent au bouton Retour)
5history.back();
6
7// Navigation avant (équivalent au bouton Suivant)
8history.forward();
9
10// Navigation relative
11history.go(-2); // Reculer de 2 pages
12history.go(1);  // Avancer d'une page
13history.go(0);  // Rafraîchir la page courante

4.2 History API Moderne

1// Ajouter une entrée dans l'historique sans changer de page
2const stateObj = { page: "nouvelle-vue" };
3history.pushState(stateObj, "Titre de page", "/nouvelle-route");
4
5// Remplacer l'entrée courante dans l'historique
6history.replaceState(stateObj, "Titre modifié", "/route-modifiee");
7
8// Écouter les changements d'état (navigation avec les boutons du navigateur)
9window.addEventListener("popstate", event => {
10  if (event.state) {
11    console.log("Retour à l'état:", event.state);
12    // Restaurer l'état de l'application
13  }
14});

4.3 Implémentation d'une SPA (Single Page Application)

1// Gestionnaire de routage simple pour SPA
2function setupRouter() {
3  // Intercepter les clics sur les liens
4  document.addEventListener("click", e => {
5    if (e.target.tagName === "A" && e.target.getAttribute("router-link") === "true") {
6      e.preventDefault();
7      const href = e.target.getAttribute("href");
8      navigateTo(href);
9    }
10  });
11  
12  // Gérer les changements dans l'historique
13  window.addEventListener("popstate", handleRouteChange);
14  
15  // Navigation programmée
16  function navigateTo(path) {
17    history.pushState({ path }, "", path);
18    handleRouteChange();
19  }
20  
21  // Mettre à jour le contenu
22  function handleRouteChange() {
23    const path = location.pathname;
24    
25    // Supprimer la classe 'active' de tous les liens
26    document.querySelectorAll("[router-link]").forEach(link => {
27      link.classList.remove("active");
28    });
29    
30    // Ajouter la classe 'active' au lien actif
31    document.querySelector(`[href="${path}"]`)?.classList.add("active");
32    
33    // Mettre à jour le contenu en fonction de la route
34    let content;
35    switch (path) {
36      case "/":
37        content = "<h1>Accueil</h1><p>Bienvenue sur notre site!</p>";
38        break;
39      case "/about":
40        content = "<h1>À propos</h1><p>Informations sur notre entreprise.</p>";
41        break;
42      case "/contact":
43        content = "<h1>Contact</h1><p>Contactez-nous via ce formulaire.</p>";
44        break;
45      default:
46        content = "<h1>404</h1><p>Page non trouvée.</p>";
47    }
48    
49    document.getElementById("app").innerHTML = content;
50  }
51  
52  // Initialiser avec la route courante
53  handleRouteChange();
54}

5. L'Objet Screen : Information sur l'Écran

5.1 Propriétés d'écran

1// Dimensions totales de l'écran
2console.log("Largeur totale:", screen.width);
3console.log("Hauteur totale:", screen.height);
4
5// Dimensions disponibles (excluant les barres de tâches, etc.)
6console.log("Largeur disponible:", screen.availWidth);
7console.log("Hauteur disponible:", screen.availHeight);
8
9// Profondeur de couleur
10console.log("Profondeur de couleur:", screen.colorDepth);
11console.log("Profondeur de pixel:", screen.pixelDepth);
12
13// Orientation (sur les appareils mobiles)
14if ("orientation" in screen) {
15  console.log("Type d'orientation:", screen.orientation.type);
16  console.log("Angle d'orientation:", screen.orientation.angle);
17  
18  // Écouter les changements d'orientation
19  screen.orientation.addEventListener("change", () => {
20    console.log("Nouvelle orientation:", screen.orientation.type);
21  });
22}

5.2 Utilisation des Media Queries pour l'Adaptation

1// Vérifier si un media query correspond
2const isMobile = window.matchMedia("(max-width: 768px)").matches;
3console.log("Est sur mobile:", isMobile);
4
5// Écouter les changements de media query
6const mediaQueryList = window.matchMedia("(prefers-color-scheme: dark)");
7function handleThemeChange(e) {
8  if (e.matches) {
9    document.body.classList.add("dark-theme");
10  } else {
11    document.body.classList.remove("dark-theme");
12  }
13}
14
15mediaQueryList.addEventListener("change", handleThemeChange);
16handleThemeChange(mediaQueryList);

6. Stockage de Données Côté Client

6.1 Cookies

1// Créer un cookie
2document.cookie = "username=John; expires=Fri, 31 Dec 2023 23:59:59 UTC; path=/";
3
4// Lire les cookies
5console.log("Tous les cookies:", document.cookie);
6
7// Fonctions utilitaires pour les cookies
8function setCookie(name, value, days) {
9  let expires = "";
10  if (days) {
11    const date = new Date();
12    date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
13    expires = "; expires=" + date.toUTCString();
14  }
15  document.cookie = name + "=" + encodeURIComponent(value) + expires + "; path=/";
16}
17
18function getCookie(name) {
19  const nameEQ = name + "=";
20  const ca = document.cookie.split(';');
21  for (let i = 0; i < ca.length; i++) {
22    let c = ca[i];
23    while (c.charAt(0) === ' ') c = c.substring(1, c.length);
24    if (c.indexOf(nameEQ) === 0) return decodeURIComponent(c.substring(nameEQ.length, c.length));
25  }
26  return null;
27}
28
29function deleteCookie(name) {
30  document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
31}

6.2 Local Storage et Session Storage

1// Local Storage (persistant)
2localStorage.setItem("theme", "dark");
3const theme = localStorage.getItem("theme");
4console.log("Thème stocké:", theme);
5localStorage.removeItem("theme");
6localStorage.clear(); // Effacer tout
7
8// Session Storage (temporaire, jusqu'à la fermeture de l'onglet)
9sessionStorage.setItem("sessionId", "abc123");
10const sessionId = sessionStorage.getItem("sessionId");
11console.log("ID de session:", sessionId);
12sessionStorage.removeItem("sessionId");
13sessionStorage.clear();
14
15// Écouter les changements de stockage (entre onglets)
16window.addEventListener("storage", event => {
17  console.log(`Clé modifiée: ${event.key}`);
18  console.log(`Ancienne valeur: ${event.oldValue}`);
19  console.log(`Nouvelle valeur: ${event.newValue}`);
20  console.log(`URL de l'onglet qui a effectué le changement: ${event.url}`);
21});

6.3 IndexedDB

1// Ouvrir une base de données
2const request = indexedDB.open("MyDatabase", 1);
3
4// Gestionnaire de création/mise à niveau
5request.onupgradeneeded = event => {
6  const db = event.target.result;
7  
8  // Créer un object store (table)
9  const store = db.createObjectStore("customers", { keyPath: "id", autoIncrement: true });
10  
11  // Créer des index (pour les recherches)
12  store.createIndex("name", "name", { unique: false });
13  store.createIndex("email", "email", { unique: true });
14};
15
16// Succès de l'ouverture
17request.onsuccess = event => {
18  const db = event.target.result;
19  
20  // Ajouter des données
21  function addCustomer(customer) {
22    const transaction = db.transaction(["customers"], "readwrite");
23    const store = transaction.objectStore("customers");
24    const request = store.add(customer);
25    
26    request.onsuccess = () => {
27      console.log("Client ajouté avec ID:", request.result);
28    };
29    
30    transaction.oncomplete = () => {
31      console.log("Transaction terminée avec succès");
32    };
33    
34    transaction.onerror = event => {
35      console.error("Erreur de transaction:", event.target.error);
36    };
37  }
38  
39  // Lire des données
40  function getCustomerByEmail(email) {
41    const transaction = db.transaction(["customers"], "readonly");
42    const store = transaction.objectStore("customers");
43    const index = store.index("email");
44    const request = index.get(email);
45    
46    request.onsuccess = () => {
47      if (request.result) {
48        console.log("Client trouvé:", request.result);
49      } else {
50        console.log("Client non trouvé");
51      }
52    };
53  }
54};
55
56// Erreur
57request.onerror = event => {
58  console.error("Erreur d'ouverture de base de données:", event.target.error);
59};

7. API de Communication

7.1 XMLHttpRequest

1// Créer une requête
2const xhr = new XMLHttpRequest();
3
4// Configurer la requête
5xhr.open("GET", "https://api.example.com/data", true);
6
7// Définir les gestionnaires d'événements
8xhr.onload = function() {
9  if (xhr.status >= 200 && xhr.status < 300) {
10    // Succès
11    const data = JSON.parse(xhr.responseText);
12    console.log("Données reçues:", data);
13  } else {
14    // Erreur
15    console.error("Erreur de requête:", xhr.statusText);
16  }
17};
18
19xhr.onerror = function() {
20  console.error("Erreur réseau");
21};
22
23xhr.onprogress = function(event) {
24  if (event.lengthComputable) {
25    const progress = (event.loaded / event.total) * 100;
26    console.log(`Progression: ${progress.toFixed(2)}%`);
27  }
28};
29
30// Ajouter des en-têtes
31xhr.setRequestHeader("Content-Type", "application/json");
32
33// Envoyer la requête
34xhr.send();
35
36// Avec des données (pour POST, PUT, etc.)
37const data = { name: "John", age: 30 };
38xhr.send(JSON.stringify(data));

7.2 Fetch API

1// Requête GET simple
2fetch("https://api.example.com/data")
3  .then(response => {
4    if (!response.ok) {
5      throw new Error(`Erreur HTTP: ${response.status}`);
6    }
7    return response.json();
8  })
9  .then(data => {
10    console.log("Données reçues:", data);
11  })
12  .catch(error => {
13    console.error("Erreur de fetch:", error);
14  });
15
16// Requête POST avec options
17fetch("https://api.example.com/users", {
18  method: "POST",
19  headers: {
20    "Content-Type": "application/json",
21    "Authorization": "Bearer token123"
22  },
23  body: JSON.stringify({
24    name: "Marie",
25    email: "marie@example.com"
26  })
27})
28  .then(response => response.json())
29  .then(data => console.log("Utilisateur créé:", data))
30  .catch(error => console.error("Erreur:", error));
31
32// Avec async/await
33async function fetchData() {
34  try {
35    const response = await fetch("https://api.example.com/data");
36    if (!response.ok) {
37      throw new Error(`Erreur HTTP: ${response.status}`);
38    }
39    const data = await response.json();
40    return data;
41  } catch (error) {
42    console.error("Erreur:", error);
43    return null;
44  }
45}

7.3 WebSockets

1// Créer une connexion WebSocket
2const socket = new WebSocket("wss://echo.websocket.org");
3
4// Événement de connexion
5socket.onopen = function(event) {
6  console.log("Connexion WebSocket établie");
7  
8  // Envoyer un message
9  socket.send("Hello, WebSocket!");
10};
11
12// Réception de message
13socket.onmessage = function(event) {
14  console.log("Message reçu:", event.data);
15};
16
17// Gestion des erreurs
18socket.onerror = function(error) {
19  console.error("Erreur WebSocket:", error);
20};
21
22// Fermeture de connexion
23socket.onclose = function(event) {
24  console.log("Connexion fermée:", event.code, event.reason);
25};
26
27// Fermer manuellement
28function closeConnection() {
29  socket.close(1000, "Fermeture normale");
30}

Conclusion

Le Browser Object Model (BOM) est un ensemble d'interfaces puissantes qui permettent aux développeurs JavaScript d'interagir avec le navigateur web. Contrairement au DOM qui se concentre sur le contenu de la page, le BOM offre un accès aux fonctionnalités du navigateur lui-même : fenêtre, navigation, historique, localisation, stockage, réseau, et bien plus.

La maîtrise du BOM est essentielle pour créer des applications web modernes et réactives. Les objets comme window, navigator, location, history et screen offrent des capacités qui vont bien au-delà de la simple manipulation du contenu HTML. Ils permettent de gérer l'état de l'application, de réagir aux changements de connectivité, d'offrir des expériences utilisateur adaptatives, et de créer des applications web qui rivalisent avec les applications natives.

Avec l'évolution constante des standards web, le BOM continue de s'enrichir de nouvelles fonctionnalités qui élargissent les possibilités des applications web. Les développeurs qui maîtrisent ces interfaces peuvent créer des expériences plus riches, plus fiables et plus performantes pour leurs utilisateurs.

Bien que certaines parties du BOM aient été standardisées relativement récemment, les différences entre navigateurs tendent à s'estomper, et des mécanismes de détection de fonctionnalités permettent de gérer efficacement la compatibilité. En adoptant les bonnes pratiques concernant la sécurité, la performance et l'expérience utilisateur, les développeurs peuvent tirer pleinement parti de la puissance du BOM pour créer des applications web de qualité professionnelle.