wisspop
wisspop
Modales y dropdowns con animaciones ancladas al origen: el panel no aparece de la nada, nace del elemento que lo abrió y vuelve a él al cerrarse.
Un motor core sin framework (JS + GSAP) con adaptadores finos para Vue 3 y Vanilla JS (utilizable en cualquier framework o web vanilla).
Instalación
pnpm add wisspop gsap
# o con npm / yarn
npm install wisspop gsap
gsap (≥3.12) es dependencia requerida. vue (≥3.4) solo es necesaria si utilizas el wrapper de Vue.
Matriz de imports
| Import | Qué trae | Entorno / Dependencia |
|---|---|---|
wisspop |
createMorph, createFlip, placeBox (core puro) |
gsap |
wisspop/vanilla |
createModal, createFlipModal, helpers dropdown |
gsap |
wisspop/vue |
WissPopMorph, WissPopPill, WissPopFlip |
gsap, vue >= 3.4 |
wisspop/styles.css |
Estructura CSS obligatoria (position, z-index, overflow) | — |
Importación de CSS
import "wisspop/styles.css";
O en tu archivo CSS global:
@import "wisspop/styles.css";
El CSS no es decorativo, provee el posicionamiento position: fixed, gestión de recortes y apilado que el motor requiere. La apariencia (colores, bordes, sombras, tipografía) es 100% controlada por ti mediante tus clases CSS (modalClass, overlayClass).
Los z-index predeterminados pueden ajustarse mediante variables CSS:
:root {
--wisspop-z-overlay: 40;
--wisspop-z-box: 50;
--wisspop-z-flying: 51;
--wisspop-z-close: 101;
}
Tipos de Modales y Cuándo Usar Cada Uno
| Componente | Tipo de Animación | Caso de Uso Principal | Qué Elementos Usa |
|---|---|---|---|
WissPopMorph |
La caja crece físicamente desde el rect del botón hasta su destino (placement="origin" o anclado). |
Dropdowns, menús contextuales, drawers, mini-modales de confirmación o aviso | NO usa texto volador. La caja hace todo el morph físico. |
WissPopPill |
La caja se expande y el texto/icono del botón vuela hacia [data-wisspop-title]. |
Modales de formularios principales, login/auth, calculadoras donde la etiqueta del botón se convierte en el título | Requiere [data-wisspop-title] en el modal y opcionalmente data-wisspop-label en el botón. |
WissPopFlip |
Transición FLIP real: múltiples elementos compartidos con data-flip-id viajan del origen al modal. |
Cards expandibles a detalle, galerías de productos | Requiere atributos data-flip-id="id-unico" coincidentes. |
DropdownPanel |
Despliegue elástico ligero con scaleY 0→1. |
Selects, menús de opciones y filtros contextuales rápidos | Helpers vanilla enterDropdownAnimation / leaveDropdownAnimation. |
Buenas Prácticas y Rendimiento Móvil (60-120 FPS) ⚡📱
- Aplica estilos en
modalClass: Wisspop anima directamente su propio contenedor.wisspop-box. Pasa el fondo, bordes, redondeo y sombra amodalClassen lugar de crear una segunda caja contenedora en el slot. flyingTextClasses exclusivo deWissPopPill: EnWissPopMorph, usa encabezados HTML normales<h3>sindata-wisspop-title.- Define un ancho en el contenido: Para que GSAP calcule la geometría con precisión, dale un ancho explícito o responsivo (ej.
style="width: min(24rem, calc(100vw - 2rem));"). - Colores de fondo armónicos en el botón: WissPop interpola el
backgroundColordel botón hacia el modal al abrir y de vuelta al cerrar. Mantén el botón en tonos armónicos/neutros y coloca los colores llamativos en iconos o badges. - Evita sombras difusas (
box-shadow) en pantalla completa: Clases comoshadow-2xlfuerzan el cálculo de 50px de blur perimetral que no es visible en100vw. Usa sombras responsivas:class="shadow-none sm:shadow-2xl". - Usa
h-fullen lugar demin-h-[100dvh]dentro del slot: WissPop ya calcula y ajusta la altura exacta de la caja; usar100dvhfuerza consultas redundantes al viewport dinámico móvil. Usaclass="h-full sm:h-auto flex flex-col justify-between".
🚀 Arquitectura de Alto Rendimiento (GPU / CPU / VRAM)
WissPop incluye optimizaciones de bajo nivel para garantizar fluidez nativa a 60-120 FPS en dispositivos móviles:
- Aceleración por Capa GPU:
.wisspop-boxopera contransform: translateZ(0)ybackface-visibility: hiddenpara que el navegador cree un contexto de composición independiente. - Contención de Layout (
contain: layout paint): Aísla el reflow durante la animación para que las mutaciones de geometría no invaliden el árbol DOM exterior (fondos, gradientes, textos). - Gestión Dinámica de VRAM (
will-change): La propiedadwill-changese activa exclusivamente durante la interpolación activa y se libera a"auto"al completarse, garantizando cero consumo residual de memoria gráfica. - Suavizado Inteligente de Curvas en Viewport Móvil: Al expandir a pantalla completa (
fullscreenOnMobile), las curvas elásticas con overshoot (back.out) conmutan automáticamente a desaceleraciones suaves (power3.out) para evitar vibraciones del motor de scroll. contentBlur: falsepor Defecto: Elimina filtros de convolución gaussiana en tiempo real durante la expansión de la caja, liberando el fill-rate de la GPU móvil.
Guía de Uso
1. Vue 3 (Wrapper Nativo)
WissPop incluye componentes nativos para Vue 3 (WissPopMorph, WissPopPill, WissPopFlip):
<script setup>
import { ref } from 'vue';
import { WissPopMorph } from 'wisspop/vue';
import 'wisspop/styles.css';
const btn = ref(null);
const abierto = ref(false);
</script>
<template>
<button ref="btn" @click="abierto = true">Abrir</button>
<WissPopMorph
v-model="abierto"
:origin-ref="btn"
placement="bottom"
align="center"
modalClass="panel"
>
<template #default="{ close }">
<div class="panel-body" style="width: min(22rem, calc(100vw - 2rem));">
<p>Contenido del modal</p>
<button @click="close">Cerrar</button>
</div>
</template>
</WissPopMorph>
</template>
2. Vanilla JS / Agnóstico
Puedes usar wisspop/vanilla en cualquier proyecto sin framework (o dentro de Astro, React, Svelte, etc.):
import { createModal } from 'wisspop/vanilla';
import 'wisspop/styles.css';
const modal = createModal({
content: '<div class="p-6" style="width: min(22rem, calc(100vw - 2rem));">Contenido...</div>',
placement: 'bottom',
align: 'center',
modalClass: 'panel',
closeButton: true,
});
document.querySelector('#btn').addEventListener('click', (e) => {
modal.open(e.currentTarget);
});
Texto e Icono Viajero (PillModal)
El segundo argumento de open() es el elemento o texto que viaja hacia data-wisspop-title:
modal.open(boton, "Crear cuenta"); // Solo texto
modal.open(boton, boton.querySelector(".con-icono")); // Texto + icono
modal.open(boton, boton.querySelector("svg")); // Solo icono (modo box)
modal.open(boton, boton.querySelector("img")); // Imagen (modo box)
| Modo | Anima | Aplicación |
|---|---|---|
text |
font-size, line-height, font-weight, color |
Cuando hay texto. Mantiene la nitidez en cada frame. |
box |
width, height |
Sin texto (iconos sueltos o imágenes). |
Métodos de la API
La instancia devuelta por createModal(), createMorph(), createFlipModal() o expuesta en las refs de los wrappers expone los siguientes métodos:
| Método | Firma | Descripción |
|---|---|---|
open |
(originRef, flying?, options?) |
Abre el modal despegando desde el origen (HTMLElement, selector CSS o Rect {top, left, width, height}). |
close |
() |
Cierra el modal retornando a la posición actual del origen. |
changeView |
(mutateFn) |
Reemplaza el contenido con un fade-through sin cerrar el panel. mutateFn se ejecuta síncronamente antes del recálculo. |
resync |
() |
Re-mide la altura del contenido cuando cambia dinámicamente y ajusta la caja. |
destroy |
() |
Limpia los observers y elimina los nodos creados en el DOM. |
Opciones de Configuración
| Opción | Tipo | Por defecto | Descripción |
|---|---|---|---|
placement |
string |
'center' |
top, bottom, left, right, origin, center, drawer-left, drawer-right, drawer-top, drawer-bottom. |
align |
string |
'center' |
Posición sobre el eje cruzado: start/left/top, center, end/right/bottom. |
gap |
number |
8 |
Distancia en píxeles hacia el elemento origen. |
margin |
number |
16 |
Margen mínimo de seguridad a los bordes de la ventana. |
duration |
number |
0.55 |
Duración de la animación de apertura en segundos. |
closeDuration |
number |
0.7 |
Duración de la animación de cierre en segundos. |
ease |
string |
'back.out(1.1)' |
Easing de GSAP para apertura (ej. 'power3.out'). |
closeEase |
string |
'power3.inOut' |
Easing de GSAP para el retorno al origen. |
swipeToClose |
boolean |
false |
Permite descartar el modal arrastrándolo con el mouse o gesto táctil. |
swipeThreshold |
number |
90 |
Píxeles de arrastre para confirmar el descarte. |
closeOnEscape |
boolean |
true |
Cierra el panel al presionar la tecla Escape. |
trapFocus |
boolean |
true |
Mantiene el trampa de foco confinado dentro del panel activo. |
restoreFocus |
boolean |
true |
Devuelve el foco al botón disparador al cerrar. |
lockScroll |
boolean |
true |
Bloquea el scroll del body agregando compensación de scrollbar. |
fullscreenOnMobile |
boolean |
true |
En pantallas menores a 640px los modales grandes ocupan el ancho completo. |
mobileBreakpoint |
number |
640 |
Breakpoint en píxeles para el comportamiento móvil responsive. |
contentBlur |
boolean |
false |
Aplica un filtro suave blur(4px)→0 en el contenido durante la apertura. |
Accesibilidad (A11Y)
WissPop integra accesibilidad nativa lista para usar:
Escape: Presionar la tecla Escape cierra el panel.- Trampa de Foco: El foco del teclado queda atrapado en el modal activo.
- Restauración de Foco: El foco retorna automáticamente al elemento disparador al cerrar.
- Semántica ARIA: Inyección automática de
role="dialog",aria-modal="true", yaria-labelledby. prefers-reduced-motion: Si el usuario activó movimiento reducido en el SO, la librería salta directamente a la geometría final sin animar.
Alcance — Lo que NO hace
- No es un sistema de diseño: No incluye colores, sombras ni tipografía predeterminadas.
- No gestiona notificaciones / toasts globales: wisspop está enfocado en modales y paneles anclados al origen.
- No reemplaza a
<dialog>estático: Si solo requieres una ventana estática sin animación anclada espacialmente,<dialog>nativo es una alternativa válida.
Related Packages
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.2.1 | >=3.4 | MIT | |
| 0.2.0 | >=3.4 | MIT | |
| 0.1.9 | >=3.4 | MIT | |
| 0.1.8 | >=3.4 | MIT | |
| 0.1.7 | >=3.4 | MIT | |
| 0.1.6 | >=3.4 | MIT | |
| 0.1.5 | >=3.4 | MIT | |
| 0.1.4 | >=3.4 | MIT | |
| 0.1.2 | >=3.4 | MIT | |
| 0.1.1 | >=3.4 | MIT | |
| 0.1.0 | >=3.4 | MIT |