wisspop

Modales y dropdowns con animaciones ancladas al origen: el panel nace del elemento que lo abrió
1,970
Install
npm install wisspop
Latest Version:0.2.1
Vue:3
License:MIT
Last Updated:Sep 26, 2026
Links: GitHub  ·  npm
Maintainer: ejcp

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) ⚡📱

  1. Aplica estilos en modalClass: Wisspop anima directamente su propio contenedor .wisspop-box. Pasa el fondo, bordes, redondeo y sombra a modalClass en lugar de crear una segunda caja contenedora en el slot.
  2. flyingTextClass es exclusivo de WissPopPill: En WissPopMorph, usa encabezados HTML normales <h3> sin data-wisspop-title.
  3. 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));").
  4. Colores de fondo armónicos en el botón: WissPop interpola el backgroundColor del 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.
  5. Evita sombras difusas (box-shadow) en pantalla completa: Clases como shadow-2xl fuerzan el cálculo de 50px de blur perimetral que no es visible en 100vw. Usa sombras responsivas: class="shadow-none sm:shadow-2xl".
  6. Usa h-full en lugar de min-h-[100dvh] dentro del slot: WissPop ya calcula y ajusta la altura exacta de la caja; usar 100dvh fuerza consultas redundantes al viewport dinámico móvil. Usa class="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-box opera con transform: translateZ(0) y backface-visibility: hidden para 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 propiedad will-change se 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: false por 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", y aria-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

@morev/vue-transitions

Shareable Vue transitions library

412,603 222
vue-starport

Shared Vue component across routes with animations

57,522 1,949
@vue-gsap-flip/vue-router

Vue Router integration for vue-gsap-flip

2,652 3
vodal

A vue modal with animations.

20,491 376

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