Componentes de Modales y Popups
Una colección completa de diálogos modales modernos y componentes popup con animaciones suaves, características de accesibilidad y diseños personalizables
Diseño Responsivo
Sí
Soporte para Modo Oscuro
No
líneas
184
Compatibilidad del Navegador
No
Vista Previa en Vivo
Interactúa con el componente sin salir de la página.
Componentes de Modales y Popups
Una colección completa de diálogos modales modernos y componentes popup con animaciones suaves, soporte de accesibilidad, navegación por teclado y diseños personalizables para mejorar la interacción del usuario.
Características
- Múltiples Tipos de Modal: Modales básicos, diálogos de confirmación, galerías de imágenes y formularios
- Animaciones Suaves: Transiciones basadas en CSS con temporización personalizable
- Soporte de Accesibilidad: Atributos ARIA, navegación por teclado y gestión de foco
- Diseño Responsivo: Se adapta a todos los tamaños de pantalla y orientaciones
- Control de Fondo: Comportamiento de fondo personalizable y clic para cerrar
- Soporte de Apilamiento: Múltiples modales con gestión adecuada de z-index
- Posicionamiento Automático: Posicionamiento inteligente para evitar desbordamiento del viewport
- Diseño Moderno: Estética limpia y minimalista con efectos sutiles
Demo
<div class="modal-demo-container">
<!-- Activadores de Modal -->
<div class="modal-triggers">
<h3>Tipos de Modal</h3>
<div class="trigger-grid">
<button class="trigger-btn" data-modal="basic-modal">
<span class="trigger-icon">📄</span>
Modal Básico
</button>
<button class="trigger-btn" data-modal="confirm-modal">
<span class="trigger-icon">❓</span>
Confirmación
</button>
<button class="trigger-btn" data-modal="form-modal">
<span class="trigger-icon">📝</span>
Modal de Formulario
</button>
<button class="trigger-btn" data-modal="image-modal">
<span class="trigger-icon">🖼️</span>
Galería de Imágenes
</button>
<button class="trigger-btn" data-modal="video-modal">
<span class="trigger-icon">🎥</span>
Modal de Video
</button>
<button class="trigger-btn" data-modal="fullscreen-modal">
<span class="trigger-icon">⛶</span>
Pantalla Completa
</button>
</div>
</div>
<!-- Activadores de Popup -->
<div class="popup-triggers">
<h3>Tipos de Popup</h3>
<div class="trigger-grid">
<button class="trigger-btn" data-popup="tooltip-popup">
<span class="trigger-icon">💬</span>
Tooltip
</button>
<button class="trigger-btn" data-popup="notification-popup">
<span class="trigger-icon">🔔</span>
Notificación
</button>
<button class="trigger-btn" data-popup="dropdown-popup">
<span class="trigger-icon">📋</span>
Desplegable
</button>
<button class="trigger-btn" data-popup="context-popup">
<span class="trigger-icon">⚙️</span>
Menú Contextual
</button>
</div>
</div>
</div>
<!-- Modal Básico -->
<div class="modal-overlay" id="basic-modal">
<div class="modal-container">
<div class="modal-header">
<h2 class="modal-title">Modal Básico</h2>
<button class="modal-close" aria-label="Cerrar modal">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
</svg>
</button>
</div>
<div class="modal-body">
<p>Este es un diálogo modal básico con animaciones suaves y características de accesibilidad. Incluye gestión adecuada de foco y soporte de navegación por teclado.</p>
<p>Puedes personalizar la apariencia, animaciones y comportamiento para que coincidan con tus requisitos de diseño.</p>
</div>
<div class="modal-footer">
<button class="btn btn-secondary modal-cancel">Cancelar</button>
<button class="btn btn-primary">Confirmar</button>
</div>
</div>
</div>
<!-- Modal de Confirmación -->
<div class="modal-overlay" id="confirm-modal">
<div class="modal-container modal-small">
<div class="modal-header">
<div class="modal-icon warning">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/>
</svg>
</div>
<h2 class="modal-title">Confirmar Acción</h2>
<button class="modal-close" aria-label="Cerrar modal">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
</svg>
</button>
</div>
<div class="modal-body">
<p>¿Estás seguro de que quieres eliminar este elemento? Esta acción no se puede deshacer.</p>
</div>
<div class="modal-footer">
<button class="btn btn-secondary modal-cancel">Cancelar</button>
<button class="btn btn-danger">Eliminar</button>
</div>
</div>
</div>
<!-- Modal de Formulario -->
<div class="modal-overlay" id="form-modal">
<div class="modal-container">
<div class="modal-header">
<h2 class="modal-title">Formulario de Contacto</h2>
<button class="modal-close" aria-label="Cerrar modal">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
</svg>
</button>
</div>
<div class="modal-body">
<form class="modal-form">
<div class="form-group">
<label for="modal-name">Nombre</label>
<input type="text" id="modal-name" name="name" required>
</div>
<div class="form-group">
<label for="modal-email">Email</label>
<input type="email" id="modal-email" name="email" required>
</div>
<div class="form-group">
<label for="modal-message">Mensaje</label>
<textarea id="modal-message" name="message" rows="4" required></textarea>
</div>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-secondary modal-cancel">Cancelar</button>
<button class="btn btn-primary" type="submit">Enviar Mensaje</button>
</div>
</div>
</div>
<!-- Modal de Imagen -->
<div class="modal-overlay" id="image-modal">
<div class="modal-container modal-image">
<button class="modal-close" aria-label="Cerrar modal">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
</svg>
</button>
<div class="image-gallery">
<div class="gallery-main">
<img src="https://picsum.photos/800/600?random=1" alt="Imagen de galería" class="gallery-image">
<div class="gallery-controls">
<button class="gallery-btn prev" aria-label="Imagen anterior">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/>
</svg>
</button>
<button class="gallery-btn next" aria-label="Siguiente imagen">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/>
</svg>
</button>
</div>
</div>
<div class="gallery-info">
<h3>Galería de Imágenes</h3>
<p>Imagen 1 de 5</p>
</div>
</div>
</div>
</div>
<!-- Modal de Video -->
<div class="modal-overlay" id="video-modal">
<div class="modal-container modal-video">
<button class="modal-close" aria-label="Cerrar modal">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
</svg>
</button>
<div class="video-container">
<iframe
src="https://www.youtube.com/embed/dQw4w9WgXcQ"
frameborder="0"
allowfullscreen
title="Reproductor de video">
</iframe>
</div>
</div>
</div>
<!-- Modal de Pantalla Completa -->
<div class="modal-overlay" id="fullscreen-modal">
<div class="modal-container modal-fullscreen">
<div class="modal-header">
<h2 class="modal-title">Modal de Pantalla Completa</h2>
<button class="modal-close" aria-label="Cerrar modal">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
</svg>
</button>
</div>
<div class="modal-body">
<div class="fullscreen-content">
<h3>Experiencia de Pantalla Completa</h3>
<p>Este modal ocupa todo el viewport, perfecto para contenido inmersivo, formularios detallados o interfaces complejas.</p>
<div class="content-grid">
<div class="content-card">
<h4>Característica 1</h4>
<p>Descripción detallada de la primera característica con información completa.</p>
</div>
<div class="content-card">
<h4>Característica 2</h4>
<p>Descripción detallada de la segunda característica con información completa.</p>
</div>
<div class="content-card">
<h4>Característica 3</h4>
<p>Descripción detallada de la tercera característica con información completa.</p>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary modal-cancel">Cerrar</button>
<button class="btn btn-primary">Guardar Cambios</button>
</div>
</div>
</div>
<!-- Popup Tooltip -->
<div class="popup-container" id="tooltip-popup">
<div class="popup-content tooltip">
<div class="tooltip-arrow"></div>
<p>Este es un tooltip útil que proporciona información adicional sobre el elemento.</p>
</div>
</div>
<!-- Popup de Notificación -->
<div class="popup-container" id="notification-popup">
<div class="popup-content notification success">
<div class="notification-icon">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
</svg>
</div>
<div class="notification-content">
<h4>¡Éxito!</h4>
<p>Tu acción se ha completado exitosamente.</p>
</div>
<button class="notification-close" aria-label="Cerrar notificación">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
</svg>
</button>
</div>
</div>
<!-- Popup Desplegable -->
<div class="popup-container" id="dropdown-popup">
<div class="popup-content dropdown">
<div class="dropdown-header">
<h4>Opciones</h4>
</div>
<div class="dropdown-list">
<a href="#" class="dropdown-item">
<span class="dropdown-icon">📄</span>
Ver Detalles
</a>
<a href="#" class="dropdown-item">
<span class="dropdown-icon">✏️</span>
Editar Elemento
</a>
<a href="#" class="dropdown-item">
<span class="dropdown-icon">📋</span>
Copiar Enlace
</a>
<div class="dropdown-divider"></div>
<a href="#" class="dropdown-item danger">
<span class="dropdown-icon">🗑️</span>
Eliminar Elemento
</a>
</div>
</div>
</div>
<!-- Popup de Menú Contextual -->
<div class="popup-container" id="context-popup">
<div class="popup-content context-menu">
<div class="context-list">
<button class="context-item">
<span class="context-icon">📋</span>
Copiar
<span class="context-shortcut">Ctrl+C</span>
</button>
<button class="context-item">
<span class="context-icon">📄</span>
Pegar
<span class="context-shortcut">Ctrl+V</span>
</button>
<button class="context-item">
<span class="context-icon">✂️</span>
Cortar
<span class="context-shortcut">Ctrl+X</span>
</button>
<div class="context-divider"></div>
<button class="context-item">
<span class="context-icon">🔄</span>
Actualizar
<span class="context-shortcut">F5</span>
</button>
<button class="context-item">
<span class="context-icon">⚙️</span>
Configuración
</button>
</div>
</div>
</div>.modal-demo-container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.modal-triggers,
.popup-triggers {
margin-bottom: 3rem;
padding: 2rem;
background: white;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
border: 1px solid #e5e7eb;
}
.modal-triggers h3,
.popup-triggers h3 {
margin: 0 0 2rem 0;
font-size: 1.5rem;
font-weight: 700;
color: #1a1a1a;
text-align: center;
}
.trigger-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
.trigger-btn {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
padding: 1.5rem;
border: 2px solid #e5e7eb;
background: white;
border-radius: 12px;
cursor: pointer;
transition: all 0.3s ease;
font-weight: 600;
color: #374151;
}
.trigger-btn:hover {
border-color: #3b82f6;
background: #f8fafc;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15);
}
.trigger-icon {
font-size: 2rem;
margin-bottom: 0.5rem;
}
/* Superposición de Modal */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
padding: 1rem;
}
.modal-overlay.active {
opacity: 1;
visibility: visible;
}
.modal-overlay.active .modal-container {
transform: scale(1) translateY(0);
opacity: 1;
}
/* Contenedor de Modal */
.modal-container {
background: white;
border-radius: 16px;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
max-width: 500px;
width: 100%;
max-height: 90vh;
overflow: hidden;
transform: scale(0.9) translateY(20px);
opacity: 0;
transition: all 0.3s ease;
position: relative;
}
.modal-container.modal-small {
max-width: 400px;
}
.modal-container.modal-large {
max-width: 800px;
}
.modal-container.modal-fullscreen {
max-width: 95vw;
max-height: 95vh;
width: 95vw;
height: 95vh;
}
.modal-container.modal-image {
max-width: 90vw;
max-height: 90vh;
background: transparent;
box-shadow: none;
}
.modal-container.modal-video {
max-width: 80vw;
max-height: 80vh;
background: #000;
border-radius: 8px;
}
/* Encabezado de Modal */
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1.5rem 2rem;
border-bottom: 1px solid #e5e7eb;
background: #f9fafb;
}
.modal-title {
margin: 0;
font-size: 1.25rem;
font-weight: 700;
color: #1a1a1a;
display: flex;
align-items: center;
gap: 0.75rem;
}
.modal-icon {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 1rem;
}
.modal-icon.warning {
background: #fef3c7;
color: #d97706;
}
.modal-icon.success {
background: #d1fae5;
color: #059669;
}
.modal-icon.error {
background: #fee2e2;
color: #dc2626;
}
.modal-icon svg {
width: 20px;
height: 20px;
}
.modal-close {
width: 40px;
height: 40px;
border: none;
background: none;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: #6b7280;
transition: all 0.2s ease;
}
.modal-close:hover {
background: #f3f4f6;
color: #374151;
}
.modal-close svg {
width: 20px;
height: 20px;
}
/* Cuerpo de Modal */
.modal-body {
padding: 2rem;
overflow-y: auto;
max-height: 60vh;
}
.modal-body p {
margin: 0 0 1rem 0;
line-height: 1.6;
color: #374151;
}
.modal-body p:last-child {
margin-bottom: 0;
}
/* Pie de Modal */
.modal-footer {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 1rem;
padding: 1.5rem 2rem;
border-top: 1px solid #e5e7eb;
background: #f9fafb;
}
/* Estilos de Formulario */
.modal-form {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.form-group label {
font-weight: 600;
color: #374151;
font-size: 0.875rem;
}
.form-group input,
.form-group textarea {
padding: 0.75rem;
border: 2px solid #e5e7eb;
border-radius: 8px;
font-size: 1rem;
transition: border-color 0.2s ease;
}
.form-group input:focus,
.form-group textarea:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
/* Estilos de Botón */
.btn {
padding: 0.75rem 1.5rem;
border: none;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
font-size: 0.875rem;
}
.btn-primary {
background: #3b82f6;
color: white;
}
.btn-primary:hover {
background: #2563eb;
transform: translateY(-1px);
}
.btn-secondary {
background: #f3f4f6;
color: #374151;
border: 1px solid #d1d5db;
}
.btn-secondary:hover {
background: #e5e7eb;
}
.btn-danger {
background: #ef4444;
color: white;
}
.btn-danger:hover {
background: #dc2626;
transform: translateY(-1px);
}
/* Galería de Imágenes */
.image-gallery {
position: relative;
background: #000;
border-radius: 8px;
overflow: hidden;
}
.gallery-main {
position: relative;
display: flex;
align-items: center;
justify-content: center;
min-height: 400px;
}
.gallery-image {
max-width: 100%;
max-height: 70vh;
object-fit: contain;
}
.gallery-controls {
position: absolute;
top: 50%;
left: 0;
right: 0;
transform: translateY(-50%);
display: flex;
justify-content: space-between;
padding: 0 1rem;
pointer-events: none;
}
.gallery-btn {
width: 50px;
height: 50px;
border: none;
background: rgba(255, 255, 255, 0.9);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
pointer-events: auto;
}
.gallery-btn:hover {
background: white;
transform: scale(1.1);
}
.gallery-btn svg {
width: 24px;
height: 24px;
color: #374151;
}
.gallery-info {
padding: 1rem;
background: rgba(0, 0, 0, 0.8);
color: white;
text-align: center;
}
.gallery-info h3 {
margin: 0 0 0.5rem 0;
font-size: 1.125rem;
}
.gallery-info p {
margin: 0;
color: #d1d5db;
font-size: 0.875rem;
}
/* Contenedor de Video */
.video-container {
position: relative;
width: 100%;
height: 0;
padding-bottom: 56.25%; /* Relación de aspecto 16:9 */
}
.video-container iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
/* Contenido de Pantalla Completa */
.fullscreen-content {
padding: 2rem 0;
}
.content-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
margin-top: 2rem;
}
.content-card {
padding: 2rem;
background: #f9fafb;
border-radius: 12px;
border: 1px solid #e5e7eb;
}
.content-card h4 {
margin: 0 0 1rem 0;
font-size: 1.125rem;
font-weight: 700;
color: #1a1a1a;
}
.content-card p {
margin: 0;
color: #6b7280;
line-height: 1.6;
}
/* Contenedor de Popup */
.popup-container {
position: fixed;
z-index: 1100;
opacity: 0;
visibility: hidden;
transition: all 0.2s ease;
pointer-events: none;
}
.popup-container.active {
opacity: 1;
visibility: visible;
pointer-events: auto;
}
.popup-content {
background: white;
border-radius: 8px;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
border: 1px solid #e5e7eb;
transform: scale(0.95) translateY(-10px);
transition: all 0.2s ease;
}
.popup-container.active .popup-content {
transform: scale(1) translateY(0);
}
/* Tooltip */
.tooltip {
padding: 0.75rem 1rem;
max-width: 250px;
font-size: 0.875rem;
color: #374151;
line-height: 1.4;
position: relative;
}
.tooltip-arrow {
position: absolute;
bottom: -6px;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 6px solid white;
}
/* Notificación */
.notification {
padding: 1rem 1.5rem;
display: flex;
align-items: center;
gap: 1rem;
min-width: 300px;
max-width: 400px;
position: relative;
}
.notification.success {
background: #f0fdf4;
border-left: 4px solid #22c55e;
}
.notification.warning {
background: #fffbeb;
border-left: 4px solid #f59e0b;
}
.notification.error {
background: #fef2f2;
border-left: 4px solid #ef4444;
}
.notification-icon {
width: 24px;
height: 24px;
flex-shrink: 0;
}
.notification.success .notification-icon {
color: #22c55e;
}
.notification.warning .notification-icon {
color: #f59e0b;
}
.notification.error .notification-icon {
color: #ef4444;
}
.notification-content h4 {
margin: 0 0 0.25rem 0;
font-size: 0.875rem;
font-weight: 600;
color: #1a1a1a;
}
.notification-content p {
margin: 0;
font-size: 0.875rem;
color: #6b7280;
}
.notification-close {
position: absolute;
top: 0.5rem;
right: 0.5rem;
width: 24px;
height: 24px;
border: none;
background: none;
cursor: pointer;
color: #9ca3af;
transition: color 0.2s ease;
}
.notification-close:hover {
color: #6b7280;
}
.notification-close svg {
width: 16px;
height: 16px;
}
/* Desplegable */
.dropdown {
min-width: 200px;
padding: 0.5rem 0;
}
.dropdown-header {
padding: 0.75rem 1rem;
border-bottom: 1px solid #e5e7eb;
margin-bottom: 0.5rem;
}
.dropdown-header h4 {
margin: 0;
font-size: 0.875rem;
font-weight: 600;
color: #374151;
}
.dropdown-list {
display: flex;
flex-direction: column;
}
.dropdown-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
text-decoration: none;
color: #374151;
font-size: 0.875rem;
transition: background-color 0.2s ease;
}
.dropdown-item:hover {
background: #f3f4f6;
}
.dropdown-item.danger {
color: #dc2626;
}
.dropdown-item.danger:hover {
background: #fef2f2;
}
.dropdown-icon {
font-size: 1rem;
}
.dropdown-divider {
height: 1px;
background: #e5e7eb;
margin: 0.5rem 0;
}
/* Menú Contextual */
.context-menu {
min-width: 180px;
padding: 0.5rem 0;
}
.context-list {
display: flex;
flex-direction: column;
}
.context-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.5rem 1rem;
border: none;
background: none;
text-align: left;
cursor: pointer;
font-size: 0.875rem;
color: #374151;
transition: background-color 0.2s ease;
}
.context-item:hover {
background: #f3f4f6;
}
.context-icon {
margin-right: 0.75rem;
font-size: 1rem;
}
.context-shortcut {
font-size: 0.75rem;
color: #9ca3af;
}
.context-divider {
height: 1px;
background: #e5e7eb;
margin: 0.5rem 0;
}
/* Tema Oscuro */
.dark .modal-container {
background: #1f2937;
color: #e5e7eb;
}
.dark .modal-header,
.dark .modal-footer {
background: #111827;
border-color: #374151;
}
.dark .modal-title {
color: #f9fafb;
}
.dark .modal-body p {
color: #d1d5db;
}
.dark .form-group input,
.dark .form-group textarea {
background: #374151;
border-color: #4b5563;
color: #e5e7eb;
}
.dark .form-group input:focus,
.dark .form-group textarea:focus {
border-color: #3b82f6;
}
.dark .content-card {
background: #111827;
border-color: #374151;
}
.dark .popup-content {
background: #1f2937;
border-color: #374151;
color: #e5e7eb;
}
.dark .dropdown-item,
.dark .context-item {
color: #d1d5db;
}
.dark .dropdown-item:hover,
.dark .context-item:hover {
background: #374151;
}
/* Diseño Responsivo */
@media (max-width: 768px) {
.modal-demo-container {
padding: 1rem;
}
.modal-triggers,
.popup-triggers {
padding: 1.5rem;
}
.trigger-grid {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.modal-overlay {
padding: 0.5rem;
}
.modal-container {
max-width: 100%;
margin: 0;
}
.modal-container.modal-fullscreen {
max-width: 100vw;
max-height: 100vh;
width: 100vw;
height: 100vh;
border-radius: 0;
}
.modal-header,
.modal-footer {
padding: 1rem 1.5rem;
}
.modal-body {
padding: 1.5rem;
}
.content-grid {
grid-template-columns: 1fr;
}
.notification {
min-width: 280px;
max-width: 320px;
}
}
@media (max-width: 480px) {
.trigger-grid {
grid-template-columns: 1fr;
}
.trigger-btn {
padding: 1rem;
}
.modal-header,
.modal-footer {
padding: 1rem;
}
.modal-body {
padding: 1rem;
}
.modal-footer {
flex-direction: column;
gap: 0.5rem;
}
.btn {
width: 100%;
}
.gallery-controls {
padding: 0 0.5rem;
}
.gallery-btn {
width: 40px;
height: 40px;
}
}
/* Movimiento Reducido */
@media (prefers-reduced-motion: reduce) {
.modal-overlay,
.modal-container,
.popup-container,
.popup-content {
transition: none;
}
.modal-overlay.active .modal-container {
transform: none;
}
.popup-container.active .popup-content {
transform: none;
}
}
/* Estilos de Foco */
.modal-close:focus,
.btn:focus,
.dropdown-item:focus,
.context-item:focus {
outline: 2px solid #3b82f6;
outline-offset: 2px;
}
/* Clases de Animación */
.fade-in {
animation: fadeIn 0.3s ease;
}
.slide-up {
animation: slideUp 0.3s ease;
}
.scale-in {
animation: scaleIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}class ComponentesModalesPopups {
constructor() {
this.modales = new Map();
this.popups = new Map();
this.modalActivo = null;
this.popupActivo = null;
this.pilaModales = [];
this.elementosEnfocables = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
this.init();
}
init() {
this.configurarEventListeners();
this.inicializarModales();
this.inicializarPopups();
this.configurarNavegacionTeclado();
this.configurarAccesibilidad();
}
configurarEventListeners() {
// Activadores de modal
document.addEventListener('click', (e) => {
const activador = e.target.closest('[data-modal]');
if (activador) {
e.preventDefault();
const modalId = activador.dataset.modal;
this.abrirModal(modalId);
}
// Activadores de popup
const activadorPopup = e.target.closest('[data-popup]');
if (activadorPopup) {
e.preventDefault();
const popupId = activadorPopup.dataset.popup;
this.abrirPopup(popupId, activadorPopup);
}
// Botones de cerrar
if (e.target.closest('.modal-close, .modal-cancel')) {
e.preventDefault();
this.cerrarModalActivo();
}
if (e.target.closest('.notification-close')) {
e.preventDefault();
this.cerrarPopupActivo();
}
// Clic en fondo
if (e.target.classList.contains('modal-overlay')) {
this.cerrarModalActivo();
}
});
// Menú contextual
document.addEventListener('contextmenu', (e) => {
const activadorContexto = e.target.closest('[data-context]');
if (activadorContexto) {
e.preventDefault();
this.abrirMenuContextual(e, 'context-popup');
}
});
// Cerrar popups al hacer clic fuera
document.addEventListener('click', (e) => {
if (this.popupActivo && !e.target.closest('.popup-container')) {
this.cerrarPopupActivo();
}
});
}
inicializarModales() {
const elementosModal = document.querySelectorAll('.modal-overlay');
elementosModal.forEach(modal => {
const modalId = modal.id;
this.modales.set(modalId, {
elemento: modal,
contenedor: modal.querySelector('.modal-container'),
estaAbierto: false,
focoAnterior: null
});
});
}
inicializarPopups() {
const elementosPopup = document.querySelectorAll('.popup-container');
elementosPopup.forEach(popup => {
const popupId = popup.id;
this.popups.set(popupId, {
elemento: popup,
contenido: popup.querySelector('.popup-content'),
estaAbierto: false,
activador: null
});
});
}
configurarNavegacionTeclado() {
document.addEventListener('keydown', (e) => {
// Tecla Escape
if (e.key === 'Escape') {
if (this.popupActivo) {
this.cerrarPopupActivo();
} else if (this.modalActivo) {
this.cerrarModalActivo();
}
}
// Navegación Tab dentro del modal
if (e.key === 'Tab' && this.modalActivo) {
this.manejarNavegacionTab(e);
}
});
}
configurarAccesibilidad() {
// Agregar atributos ARIA
this.modales.forEach((modal, id) => {
modal.elemento.setAttribute('role', 'dialog');
modal.elemento.setAttribute('aria-modal', 'true');
modal.elemento.setAttribute('aria-hidden', 'true');
const titulo = modal.elemento.querySelector('.modal-title');
if (titulo) {
titulo.id = `${id}-title`;
modal.elemento.setAttribute('aria-labelledby', `${id}-title`);
}
});
this.popups.forEach((popup, id) => {
popup.elemento.setAttribute('role', 'tooltip');
popup.elemento.setAttribute('aria-hidden', 'true');
});
}
abrirModal(modalId, opciones = {}) {
const modal = this.modales.get(modalId);
if (!modal || modal.estaAbierto) return;
// Guardar foco anterior
modal.focoAnterior = document.activeElement;
// Cerrar cualquier popup activo
if (this.popupActivo) {
this.cerrarPopupActivo();
}
// Agregar a la pila de modales
this.pilaModales.push(modalId);
this.modalActivo = modalId;
// Mostrar modal
modal.elemento.style.display = 'flex';
modal.elemento.setAttribute('aria-hidden', 'false');
// Activar animación
requestAnimationFrame(() => {
modal.elemento.classList.add('active');
if (opciones.animacion) {
modal.contenedor.classList.add(opciones.animacion);
}
});
// Gestión de foco
setTimeout(() => {
this.enfocarPrimerElemento(modal.elemento);
}, 100);
// Prevenir scroll del body
document.body.style.overflow = 'hidden';
modal.estaAbierto = true;
// Disparar evento
this.dispararEvento('modalAbierto', { modalId, modal: modal.elemento });
}
cerrarModal(modalId) {
const modal = this.modales.get(modalId);
if (!modal || !modal.estaAbierto) return;
// Remover de la pila
const indiceEnPila = this.pilaModales.indexOf(modalId);
if (indiceEnPila > -1) {
this.pilaModales.splice(indiceEnPila, 1);
}
// Actualizar modal activo
this.modalActivo = this.pilaModales.length > 0 ? this.pilaModales[this.pilaModales.length - 1] : null;
// Ocultar modal
modal.elemento.classList.remove('active');
modal.elemento.setAttribute('aria-hidden', 'true');
setTimeout(() => {
modal.elemento.style.display = 'none';
modal.contenedor.className = modal.contenedor.className.replace(/\b(fade-in|slide-up|scale-in)\b/g, '');
}, 300);
// Restaurar foco
if (modal.focoAnterior) {
modal.focoAnterior.focus();
modal.focoAnterior = null;
}
// Restaurar scroll del body si no hay modales abiertos
if (this.pilaModales.length === 0) {
document.body.style.overflow = '';
}
modal.estaAbierto = false;
// Disparar evento
this.dispararEvento('modalCerrado', { modalId, modal: modal.elemento });
}
cerrarModalActivo() {
if (this.modalActivo) {
this.cerrarModal(this.modalActivo);
}
}
abrirPopup(popupId, activador, opciones = {}) {
const popup = this.popups.get(popupId);
if (!popup || popup.estaAbierto) return;
// Cerrar cualquier popup activo
if (this.popupActivo) {
this.cerrarPopupActivo();
}
this.popupActivo = popupId;
popup.activador = activador;
// Posicionar popup
this.posicionarPopup(popup, activador, opciones.posicion);
// Mostrar popup
popup.elemento.style.display = 'block';
popup.elemento.setAttribute('aria-hidden', 'false');
requestAnimationFrame(() => {
popup.elemento.classList.add('active');
});
popup.estaAbierto = true;
// Auto-cerrar para notificaciones
if (popup.elemento.querySelector('.notification') && opciones.autoCerrar !== false) {
setTimeout(() => {
this.cerrarPopup(popupId);
}, opciones.duracion || 5000);
}
// Disparar evento
this.dispararEvento('popupAbierto', { popupId, popup: popup.elemento, activador });
}
cerrarPopup(popupId) {
const popup = this.popups.get(popupId);
if (!popup || !popup.estaAbierto) return;
popup.elemento.classList.remove('active');
popup.elemento.setAttribute('aria-hidden', 'true');
setTimeout(() => {
popup.elemento.style.display = 'none';
}, 200);
if (this.popupActivo === popupId) {
this.popupActivo = null;
}
popup.estaAbierto = false;
popup.activador = null;
// Disparar evento
this.dispararEvento('popupCerrado', { popupId, popup: popup.elemento });
}
cerrarPopupActivo() {
if (this.popupActivo) {
this.cerrarPopup(this.popupActivo);
}
}
abrirMenuContextual(evento, popupId) {
const popup = this.popups.get(popupId);
if (!popup) return;
// Cerrar cualquier popup activo
if (this.popupActivo) {
this.cerrarPopupActivo();
}
this.popupActivo = popupId;
// Posicionar en el cursor
popup.elemento.style.position = 'fixed';
popup.elemento.style.left = `${evento.clientX}px`;
popup.elemento.style.top = `${evento.clientY}px`;
// Ajustar posición si está cerca de los bordes del viewport
const rect = popup.contenido.getBoundingClientRect();
const anchoViewport = window.innerWidth;
const altoViewport = window.innerHeight;
if (evento.clientX + rect.width > anchoViewport) {
popup.elemento.style.left = `${evento.clientX - rect.width}px`;
}
if (evento.clientY + rect.height > altoViewport) {
popup.elemento.style.top = `${evento.clientY - rect.height}px`;
}
// Mostrar popup
popup.elemento.style.display = 'block';
popup.elemento.setAttribute('aria-hidden', 'false');
requestAnimationFrame(() => {
popup.elemento.classList.add('active');
});
popup.estaAbierto = true;
}
posicionarPopup(popup, activador, posicion = 'bottom') {
const rectActivador = activador.getBoundingClientRect();
const rectPopup = popup.contenido.getBoundingClientRect();
const anchoViewport = window.innerWidth;
const altoViewport = window.innerHeight;
const scrollX = window.pageXOffset;
const scrollY = window.pageYOffset;
let izquierda, arriba;
switch (posicion) {
case 'top':
izquierda = rectActivador.left + (rectActivador.width / 2) - (rectPopup.width / 2);
arriba = rectActivador.top - rectPopup.height - 10;
break;
case 'bottom':
izquierda = rectActivador.left + (rectActivador.width / 2) - (rectPopup.width / 2);
arriba = rectActivador.bottom + 10;
break;
case 'left':
izquierda = rectActivador.left - rectPopup.width - 10;
arriba = rectActivador.top + (rectActivador.height / 2) - (rectPopup.height / 2);
break;
case 'right':
izquierda = rectActivador.right + 10;
arriba = rectActivador.top + (rectActivador.height / 2) - (rectPopup.height / 2);
break;
default:
izquierda = rectActivador.left;
arriba = rectActivador.bottom + 10;
}
// Ajustar para límites del viewport
if (izquierda < 0) izquierda = 10;
if (izquierda + rectPopup.width > anchoViewport) izquierda = anchoViewport - rectPopup.width - 10;
if (arriba < 0) arriba = 10;
if (arriba + rectPopup.height > altoViewport) arriba = altoViewport - rectPopup.height - 10;
popup.elemento.style.position = 'fixed';
popup.elemento.style.left = `${izquierda}px`;
popup.elemento.style.top = `${arriba}px`;
}
manejarNavegacionTab(evento) {
const modal = this.modales.get(this.modalActivo);
if (!modal) return;
const elementosEnfocables = modal.elemento.querySelectorAll(this.elementosEnfocables);
const primerElemento = elementosEnfocables[0];
const ultimoElemento = elementosEnfocables[elementosEnfocables.length - 1];
if (evento.shiftKey) {
if (document.activeElement === primerElemento) {
evento.preventDefault();
ultimoElemento.focus();
}
} else {
if (document.activeElement === ultimoElemento) {
evento.preventDefault();
primerElemento.focus();
}
}
}
enfocarPrimerElemento(contenedor) {
const elementosEnfocables = contenedor.querySelectorAll(this.elementosEnfocables);
if (elementosEnfocables.length > 0) {
elementosEnfocables[0].focus();
}
}
dispararEvento(nombreEvento, detalle) {
const evento = new CustomEvent(nombreEvento, {
detail: detalle,
bubbles: true,
cancelable: true
});
document.dispatchEvent(evento);
}
// API Pública
mostrar(id, opciones = {}) {
if (this.modales.has(id)) {
this.abrirModal(id, opciones);
} else if (this.popups.has(id)) {
// Para popups, necesitamos un elemento activador
const activador = opciones.activador || document.body;
this.abrirPopup(id, activador, opciones);
}
}
ocultar(id) {
if (this.modales.has(id)) {
this.cerrarModal(id);
} else if (this.popups.has(id)) {
this.cerrarPopup(id);
}
}
ocultarTodos() {
// Cerrar todos los modales
this.pilaModales.forEach(modalId => {
this.cerrarModal(modalId);
});
// Cerrar popup activo
if (this.popupActivo) {
this.cerrarPopupActivo();
}
}
estaAbierto(id) {
const modal = this.modales.get(id);
const popup = this.popups.get(id);
return (modal && modal.estaAbierto) || (popup && popup.estaAbierto);
}
mostrarNotificacion(mensaje, tipo = 'success', opciones = {}) {
const notificacion = this.crearNotificacion(mensaje, tipo, opciones);
document.body.appendChild(notificacion);
// Posicionar notificación
this.posicionarNotificacion(notificacion, opciones.posicion || 'top-right');
// Mostrar con animación
requestAnimationFrame(() => {
notificacion.classList.add('active');
});
// Auto-cerrar
if (opciones.autoCerrar !== false) {
setTimeout(() => {
this.cerrarNotificacion(notificacion);
}, opciones.duracion || 5000);
}
return notificacion;
}
crearNotificacion(mensaje, tipo, opciones) {
const notificacion = document.createElement('div');
notificacion.className = 'popup-container notification-popup';
notificacion.innerHTML = `
<div class="popup-content notification ${tipo}">
<div class="notification-icon">
${this.obtenerIconoNotificacion(tipo)}
</div>
<div class="notification-content">
<h4>${opciones.titulo || this.obtenerTituloNotificacion(tipo)}</h4>
<p>${mensaje}</p>
</div>
<button class="notification-close" aria-label="Cerrar notificación">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
</svg>
</button>
</div>
`;
// Agregar evento de cerrar
notificacion.querySelector('.notification-close').addEventListener('click', () => {
this.cerrarNotificacion(notificacion);
});
return notificacion;
}
obtenerIconoNotificacion(tipo) {
const iconos = {
success: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>',
warning: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/></svg>',
error: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>',
info: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg>'
};
return iconos[tipo] || iconos.info;
}
obtenerTituloNotificacion(tipo) {
const titulos = {
success: '¡Éxito!',
warning: 'Advertencia',
error: 'Error',
info: 'Información'
};
return titulos[tipo] || 'Notificación';
}
posicionarNotificacion(notificacion, posicion) {
const posiciones = {
'top-right': { top: '20px', right: '20px' },
'top-left': { top: '20px', left: '20px' },
'bottom-right': { bottom: '20px', right: '20px' },
'bottom-left': { bottom: '20px', left: '20px' },
'top-center': { top: '20px', left: '50%', transform: 'translateX(-50%)' },
'bottom-center': { bottom: '20px', left: '50%', transform: 'translateX(-50%)' }
};
const estilos = posiciones[posicion] || posiciones['top-right'];
Object.assign(notificacion.style, {
position: 'fixed',
zIndex: '1200',
...estilos
});
}
cerrarNotificacion(notificacion) {
notificacion.classList.remove('active');
setTimeout(() => {
if (notificacion.parentNode) {
notificacion.parentNode.removeChild(notificacion);
}
}, 300);
}
destruir() {
// Cerrar todos los modales y popups
this.ocultarTodos();
// Limpiar event listeners
document.removeEventListener('click', this.configurarEventListeners);
document.removeEventListener('keydown', this.configurarNavegacionTeclado);
// Restaurar scroll del body
document.body.style.overflow = '';
// Limpiar mapas
this.modales.clear();
this.popups.clear();
this.pilaModales = [];
this.modalActivo = null;
this.popupActivo = null;
}
}
// Inicialización automática
let componentesModalesPopups;
document.addEventListener('DOMContentLoaded', () => {
componentesModalesPopups = new ComponentesModalesPopups();
});
// Exportar para uso como módulo
if (typeof module !== 'undefined' && module.exports) {
module.exports = ComponentesModalesPopups;
}
// API global
window.ComponentesModalesPopups = ComponentesModalesPopups;Ejemplos de Uso
Modal Básico
// Abrir modal programáticamente
componentesModalesPopups.mostrar('basic-modal', {
animacion: 'fade-in'
});
// Cerrar modal
componentesModalesPopups.ocultar('basic-modal');
// Verificar si está abierto
if (componentesModalesPopups.estaAbierto('basic-modal')) {
console.log('El modal está abierto');
}Sistema de Notificaciones
// Mostrar notificación de éxito
componentesModalesPopups.mostrarNotificacion(
'Los datos se han guardado correctamente',
'success',
{
titulo: 'Guardado',
posicion: 'top-right',
duracion: 3000
}
);
// Mostrar notificación de error
componentesModalesPopups.mostrarNotificacion(
'Ha ocurrido un error al procesar la solicitud',
'error',
{
autoCerrar: false // No cerrar automáticamente
}
);Manejo de Eventos
// Escuchar eventos de modal
document.addEventListener('modalAbierto', (e) => {
console.log('Modal abierto:', e.detail.modalId);
});
document.addEventListener('modalCerrado', (e) => {
console.log('Modal cerrado:', e.detail.modalId);
});
// Escuchar eventos de popup
document.addEventListener('popupAbierto', (e) => {
console.log('Popup abierto:', e.detail.popupId);
});Creación Dinámica de Modales
// Crear modal personalizado
function crearModalPersonalizado(titulo, contenido) {
const modalHtml = `
<div class="modal-overlay" id="modal-personalizado">
<div class="modal-container">
<div class="modal-header">
<h2 class="modal-title">${titulo}</h2>
<button class="modal-close" aria-label="Cerrar modal">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
</svg>
</button>
</div>
<div class="modal-body">
${contenido}
</div>
<div class="modal-footer">
<button class="btn btn-secondary modal-cancel">Cancelar</button>
<button class="btn btn-primary">Aceptar</button>
</div>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeend', modalHtml);
componentesModalesPopups.inicializarModales();
componentesModalesPopups.mostrar('modal-personalizado');
}Referencia de API
Métodos
| Método | Descripción | Parámetros |
|---|---|---|
mostrar(id, opciones) | Abre un modal o popup | id: ID del elemento, opciones: objeto de configuración |
ocultar(id) | Cierra un modal o popup específico | id: ID del elemento |
ocultarTodos() | Cierra todos los modales y popups abiertos | - |
estaAbierto(id) | Verifica si un modal/popup está abierto | id: ID del elemento |
mostrarNotificacion(mensaje, tipo, opciones) | Muestra una notificación | mensaje: texto, tipo: success/warning/error/info, opciones: configuración |
destruir() | Limpia todos los event listeners y elementos | - |
Eventos
| Evento | Descripción | Detalle |
|---|---|---|
modalAbierto | Se dispara cuando se abre un modal | { modalId, modal } |
modalCerrado | Se dispara cuando se cierra un modal | { modalId, modal } |
popupAbierto | Se dispara cuando se abre un popup | { popupId, popup, activador } |
popupCerrado | Se dispara cuando se cierra un popup | { popupId, popup } |
Opciones
Opciones de Modal
{
animacion: 'fade-in' | 'slide-up' | 'scale-in', // Tipo de animación
cerrarAlHacerClicFuera: true, // Cerrar al hacer clic en el fondo
cerrarConEscape: true // Cerrar con la tecla Escape
}Opciones de Popup
{
posicion: 'top' | 'bottom' | 'left' | 'right', // Posición relativa al activador
activador: Element, // Elemento que activa el popup
autoCerrar: true, // Cerrar automáticamente
duracion: 5000 // Duración antes del auto-cierre (ms)
}Opciones de Notificación
{
titulo: 'Título personalizado', // Título de la notificación
posicion: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'top-center' | 'bottom-center',
autoCerrar: true, // Cerrar automáticamente
duracion: 5000 // Duración antes del auto-cierre (ms)
}Personalización
Variables CSS
:root {
--modal-overlay-bg: rgba(0, 0, 0, 0.5);
--modal-bg: white;
--modal-border-radius: 16px;
--modal-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
--modal-header-bg: #f9fafb;
--modal-footer-bg: #f9fafb;
--popup-bg: white;
--popup-border-radius: 8px;
--popup-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
--notification-success-bg: #f0fdf4;
--notification-warning-bg: #fffbeb;
--notification-error-bg: #fef2f2;
--animation-duration: 0.3s;
--animation-timing: ease;
}Animaciones Personalizadas
/* Animación personalizada */
@keyframes slideInFromRight {
from {
opacity: 0;
transform: translateX(100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.modal-container.slide-right {
animation: slideInFromRight 0.3s ease;
}Accesibilidad
- Navegación por Teclado: Soporte completo para Tab, Shift+Tab y Escape
- Gestión de Foco: El foco se mueve automáticamente al modal y se restaura al cerrarlo
- Atributos ARIA: Roles y propiedades ARIA apropiados para lectores de pantalla
- Contraste de Color: Cumple con las pautas WCAG 2.1 AA
- Texto Alternativo: Todos los iconos incluyen etiquetas aria-label
Soporte de Navegadores
- Chrome 60+
- Firefox 55+
- Safari 12+
- Edge 79+
- iOS Safari 12+
- Android Chrome 60+
Consideraciones de Rendimiento
- Lazy Loading: Los modales se inicializan solo cuando se necesitan
- Event Delegation: Uso eficiente de event listeners
- CSS Transforms: Animaciones optimizadas con GPU
- Memory Management: Limpieza automática de elementos dinámicos
- Debouncing: Prevención de múltiples activaciones rápidas
Integración
React
import { useEffect, useRef } from 'react';
function ModalComponent({ isOpen, onClose, children }) {
const modalRef = useRef();
useEffect(() => {
if (isOpen) {
componentesModalesPopups.mostrar('react-modal');
} else {
componentesModalesPopups.ocultar('react-modal');
}
}, [isOpen]);
return (
<div className="modal-overlay" id="react-modal" ref={modalRef}>
<div className="modal-container">
{children}
</div>
</div>
);
}Vue
<template>
<div class="modal-overlay" :id="modalId" v-show="isOpen">
<div class="modal-container">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
props: ['isOpen', 'modalId'],
watch: {
isOpen(newVal) {
if (newVal) {
this.$nextTick(() => {
componentesModalesPopups.mostrar(this.modalId);
});
} else {
componentesModalesPopups.ocultar(this.modalId);
}
}
}
};
</script>HTML
24
líneas
CSS
160
líneas
<div class="modal-demo">
<button class="open-modal-btn" onclick="openModal('modal1')">Abrir Modal</button>
<div id="modal1" class="modal-overlay">
<div class="modal-content">
<div class="modal-header">
<h3>Título del Modal</h3>
<button class="close-btn" onclick="closeModal('modal1')">×</button>
</div>
<div class="modal-body">
<p>Este es el contenido del modal. Puedes agregar cualquier contenido aquí.</p>
<form class="modal-form">
<input type="text" placeholder="Nombre" required>
<input type="email" placeholder="Email" required>
<textarea placeholder="Mensaje"></textarea>
</form>
</div>
<div class="modal-footer">
<button class="btn-secondary" onclick="closeModal('modal1')">Cancelar</button>
<button class="btn-primary">Guardar</button>
</div>
</div>
</div>
</div>