DEV Community

ArtyProg
ArtyProg

Posted on

Create a Modal without any framework.

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">&times;</button>
    <div id="modal-content" class="modal-content">
      <!-- Content will be loaded here -->
    </div>
  </div>

  <script src="app.js"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

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 */
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

Top comments (0)