The following illustrate how to code a Modal without requiring any libraries or framework:
Prerequisite, create the three files : news.html, contact.html, about.html
and plce them in beside the following files.
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dynamic Content Loading with Modal</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="overlay"></div>
<h1>Custom Modal in vanilla JS</h1>
<nav>
<a href="" class="modal-trigger" data-url="news.html" data-color="#005f73">
News
</a> |
<a href="" class="modal-trigger" data-url="about.html" data-color="#9b2226">
About
</a> |
<a href="" class="modal-trigger" data-url="contact.html" data-color="#ff8800">
Contact
</a>
</nav>
<div style="width: 400px; margin: 0px auto;">
<p style="text-align: left;">
Create the following files in the same directory :
<p>
news.html, about.html, contact.html.</p>
<p style="text-align: left;">Then open this file (index.html) in a web browser</p>
<p style="text-align: left;">
Place stylde.css and app.js in the same directory as well.
</p>
</div>
<div id="modal" class="modal" role="dialog" aria-modal="true">
<button id="modal-close-btn" class="close-btn">×</button>
<div id="modal-content" class="modal-content">
<!-- Content will be loaded here -->
</div>
</div>
<script src="app.js"></script>
</body>
</html>
style.css
/* ==========================================================================
Styles Généraux
========================================================================== */
body {
height: 100%;
font-family: sans-serif;
margin: 0; /* Enlève les marges par défaut du navigateur */
text-align: center;
}
/* ==========================================================================
Overlay (Fond semi-transparent)
========================================================================== */
#overlay {
/* État initial : caché */
opacity: 0;
visibility: hidden;
/* Géométrie et positionnement */
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
z-index: 100;
/* Animation de fondu */
transition: opacity 0.4s ease-in-out, visibility 0.4s ease-in-out;
}
#overlay.is-visible {
/* État final : visible */
opacity: 1;
visibility: visible;
}
/* ==========================================================================
Fenêtre Modale
========================================================================== */
/* --- 1. Styles de base (communs à mobile et desktop) --- */
.modal {
/* NOUVEAU : On utilise la variable CSS. Si elle n'existe pas, la couleur par défaut est 'deeppink'. */
background: var(--modal-background, deeppink);
position: fixed;
z-index: 101; /* Doit être au-dessus de l'overlay */
box-sizing: border-box; /* Simplifie la gestion des dimensions */
/* État initial pour l'animation : complètement transparent et non-interactif */
opacity: 0;
visibility: hidden;
/* NOUVEAU : On anime aussi le changement de 'background' pour une transition de couleur fluide. */
transition: transform 0.4s ease-in-out, opacity 0.4s ease-in-out, background 0.4s ease;
}
/* --- 2. Styles pour l'état visible (communs à mobile et desktop) --- */
.modal.is-visible {
opacity: 1;
visibility: visible;
}
/* --- 3. Styles spécifiques pour DESKTOP (écrans > 768px) --- */
@media screen and (min-width: 769px) {
.modal {
width: 500px;
padding: 25px;
border-radius: 8px;
border: 1px solid black;
box-shadow: 0 5px 20px rgba(0,0,0,0.3);
/* Centrage parfait */
top: 50%;
left: 50%;
/* Hauteur maximale pour gérer le contenu long */
max-height: 85vh;
display: flex;
flex-direction: column;
/* Animation : départ légèrement plus petit et décalé */
transform: translate(-50%, -50%) scale(0.95);
}
.modal.is-visible {
/* Animation : arrivée à la taille et position finales */
transform: translate(-50%, -50%) scale(1);
}
}
/* --- 4. Styles spécifiques pour MOBILE (écrans <= 768px) --- */
@media screen and (max-width: 768px) {
.modal {
/* Géométrie "plein écran" */
width: 100%;
height: 100%;
top: 0;
left: 0;
/* Réinitialisation des styles desktop inutiles sur mobile */
border: none;
border-radius: 0;
box-shadow: none;
padding: 20px;
/* Permet le défilement si le contenu est trop long */
overflow-y: auto;
/* Animation mobile : départ en dehors de l'écran, sur la gauche */
transform: translateX(-100%);
}
.modal.is-visible {
/* Animation mobile : glisse pour occuper l'écran */
transform: translateX(0);
}
}
/* ==========================================================================
Contenu de la Modale (bouton de fermeture, etc.)
========================================================================== */
.close-btn {
position: absolute;
top: 10px;
right: 15px;
font-size: 1.8rem;
font-weight: bold;
color: white;
text-shadow: 0 1px 2px rgba(0,0,0,0.4); /* Améliore la lisibilité */
line-height: 1;
border: none;
background: transparent;
cursor: pointer;
z-index: 10; /* S'assure qu'il est au-dessus du contenu qui défile */
}
.modal-content {
margin-top: 20px;
color: white;
}
/* Règle spécifique au contenu de la modale sur DESKTOP pour le défilement */
@media screen and (min-width: 769px) {
.modal-content {
flex: 1; /* Prend toute la hauteur disponible */
overflow-y: auto; /* Active le défilement si nécessaire */
padding-right: 15px; /* Évite que le texte ne colle à la barre de défilement */
}
}
app.js
/**
* Attend que le contenu de la page (le DOM) soit entièrement chargé avant d'exécuter le script.
* C'est une bonne pratique pour s'assurer que tous les éléments HTML existent.
*/
document.addEventListener('DOMContentLoaded', () => {
// --- 1. Sélection des éléments du DOM ---
// On récupère tous les éléments HTML avec lesquels on va interagir une seule fois.
const modal = document.getElementById('modal');
const overlay = document.getElementById('overlay');
const modalContent = document.getElementById('modal-content');
const closeButton = document.getElementById('modal-close-btn');
const triggerLinks = document.querySelectorAll('.modal-trigger');
// Variable pour mémoriser l'élément qui avait le focus avant l'ouverture de la modale.
// C'est crucial pour l'accessibilité.
let lastActiveElement;
// --- 2. Définition des Fonctions ---
/**
* Ouvre la modale, charge le contenu et applique la couleur de fond.
* @param {HTMLElement} triggerElement - L'élément <a> qui a été cliqué.
*/
const openModal = async (triggerElement) => {
// On récupère l'URL et la couleur depuis les attributs 'data-*' du lien cliqué.
const url = triggerElement.dataset.url;
const color = triggerElement.dataset.color;
if (!url) {
console.error("L'attribut 'data-url' est manquant sur le lien.", triggerElement);
return;
}
lastActiveElement = document.activeElement; // On sauvegarde l'élément actif.
try {
// On charge le contenu depuis l'URL spécifiée.
const response = await fetch(url);
if (!response.ok) throw new Error(`Le réseau n'a pas répondu correctement`);
const content = await response.text();
modalContent.innerHTML = content;
// === LA PARTIE NOUVELLE ET IMPORTANTE ===
// On vérifie si une couleur a été définie dans l'attribut 'data-color'.
if (color) {
// Si oui, on applique cette couleur à une variable CSS (--modal-background) sur la modale.
modal.style.setProperty('--modal-background', color);
} else {
// Sinon, on supprime la variable pour que le CSS utilise sa couleur par défaut.
modal.style.removeProperty('--modal-background');
}
// =======================================
// On affiche la modale et l'overlay.
modal.classList.add('is-visible');
overlay.classList.add('is-visible');
// On déplace le focus sur le bouton de fermeture (accessibilité).
closeButton.focus();
// On ajoute un écouteur pour la touche "Échap".
document.addEventListener('keydown', handleEscKey);
} catch (error) {
modalContent.innerHTML = `<p>Erreur lors du chargement du contenu : ${error.message}</p>`;
console.error('Erreur de fetch :', error);
}
};
/**
* Ferme la modale et l'overlay.
*/
const closeModal = () => {
modal.classList.remove('is-visible');
overlay.classList.remove('is-visible');
modalContent.innerHTML = ''; // On vide le contenu.
// On redonne le focus à l'élément qui a ouvert la modale (accessibilité).
if (lastActiveElement) {
lastActiveElement.focus();
}
// On retire l'écouteur pour la touche "Échap" pour ne pas l'avoir en permanence.
document.removeEventListener('keydown', handleEscKey);
};
/**
* Fonction qui vérifie si la touche pressée est "Échap" et ferme la modale si c'est le cas.
* @param {KeyboardEvent} event - L'événement du clavier.
*/
const handleEscKey = (event) => {
if (event.key === 'Escape') {
closeModal();
}
};
// --- 3. Initialisation des Écouteurs d'Événements ---
// On attache un écouteur de clic à chaque lien qui doit ouvrir la modale.
triggerLinks.forEach(link => {
link.addEventListener('click', (event) => {
event.preventDefault(); // Empêche le lien de naviguer.
openModal(link); // On appelle openModal en lui passant le lien cliqué lui-même.
});
});
// On attache un écouteur de clic au bouton de fermeture.
closeButton.addEventListener('click', closeModal);
});
Top comments (0)