Ad Code

Ticker

6/recent/ticker-posts

Carrusel de deslizamiento de tarjetas: Cómo crear un componente interactivo y atractivo para tu sitio web



En la era digital, captar la atención de los visitantes en segundos es fundamental. El carrusel de deslizamiento de tarjetas se ha convertido en una herramienta esencial en el diseño web moderno, permitiendo exhibir información de manera organizada, atractiva y funcional. Gracias a su versatilidad, puedes mostrar productos, testimonios, artículos destacados o servicios, todo en un espacio compacto que responde a diferentes tamaños de pantalla.

 


Este componente no solo enriquece la estética de tu página, sino que también mejora la experiencia del usuario, aumentando el tiempo de permanencia y facilitando la navegación. Además, aprender a crear un carrusel de tarjetas desde cero te permitirá personalizarlo según tus necesidades y mantener un control total sobre su apariencia y comportamiento.



Estructura HTML básica

 <!--
     * Estructura HTML:
     * Representa la pila de tarjetas. Cada 
es una tarjeta individual. * La variable CSS `--i` se establece inline para cada tarjeta, indicando su posición inicial en la pila. * Esta variable es crucial para las transformaciones 3D y el z-index en CSS. --> <section class="card-stack"> <article class="card a" style="--i: 0"> <span class="icon"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-android" viewBox="0 0 16 16"> <path d="M2.76 3.061a.5.5 0 0 1 .679.2l1.283 2.352A8.9 8.9 0 0 1 8 5a8.9 8.9 0 0 1 3.278.613l1.283-2.352a.5.5 0 1 1 .878.478l-1.252 2.295C14.475 7.266 16 9.477 16 12H0c0-2.523 1.525-4.734 3.813-5.966L2.56 3.74a.5.5 0 0 1 .2-.678ZM5 10a1 1 0 1 0 0-2 1 1 0 0 0 0 2m6 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2" /> </svg> </span> </article> <article class="card b" style="--i: 1"> <span class="icon"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-apple" viewBox="0 0 16 16"> <path d="M11.182.008C11.148-.03 9.923.023 8.857 1.18c-1.066 1.156-.902 2.482-.878 2.516s1.52.087 2.475-1.258.762-2.391.728-2.43m3.314 11.733c-.048-.096-2.325-1.234-2.113-3.422s1.675-2.789 1.698-2.854-.597-.79-1.254-1.157a3.7 3.7 0 0 0-1.563-.434c-.108-.003-.483-.095-1.254.116-.508.139-1.653.589-1.968.607-.316.018-1.256-.522-2.267-.665-.647-.125-1.333.131-1.824.328-.49.196-1.422.754-2.074 2.237-.652 1.482-.311 3.83-.067 4.56s.625 1.924 1.273 2.796c.576.984 1.34 1.667 1.659 1.899s1.219.386 1.843.067c.502-.308 1.408-.485 1.766-.472.357.013 1.061.154 1.782.539.571.197 1.111.115 1.652-.105.541-.221 1.324-1.059 2.238-2.758q.52-1.185.473-1.282" /> <path d="M11.182.008C11.148-.03 9.923.023 8.857 1.18c-1.066 1.156-.902 2.482-.878 2.516s1.52.087 2.475-1.258.762-2.391.728-2.43m3.314 11.733c-.048-.096-2.325-1.234-2.113-3.422s1.675-2.789 1.698-2.854-.597-.79-1.254-1.157a3.7 3.7 0 0 0-1.563-.434c-.108-.003-.483-.095-1.254.116-.508.139-1.653.589-1.968.607-.316.018-1.256-.522-2.267-.665-.647-.125-1.333.131-1.824.328-.49.196-1.422.754-2.074 2.237-.652 1.482-.311 3.83-.067 4.56s.625 1.924 1.273 2.796c.576.984 1.34 1.667 1.659 1.899s1.219.386 1.843.067c.502-.308 1.408-.485 1.766-.472.357.013 1.061.154 1.782.539.571.197 1.111.115 1.652-.105.541-.221 1.324-1.059 2.238-2.758q.52-1.185.473-1.282" /> </svg> </span> </article> <article class="card c" style="--i: 2;"> <span class="icon"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-behance" viewBox="0 0 16 16"> <path d="M4.654 3c.461 0 .887.035 1.278.14.39.07.711.216.996.391s.497.426.641.747c.14.32.216.711.216 1.137 0 .496-.106.922-.356 1.242-.215.32-.566.606-.997.817.606.176 1.067.496 1.348.922s.461.957.461 1.563c0 .496-.105.922-.285 1.278a2.3 2.3 0 0 1-.782.887c-.32.215-.711.39-1.137.496a5.3 5.3 0 0 1-1.278.176L0 12.803V3zm-.285 3.978c.39 0 .71-.105.957-.285.246-.18.355-.497.355-.887 0-.216-.035-.426-.105-.567a1 1 0 0 0-.32-.355 1.8 1.8 0 0 0-.461-.176c-.176-.035-.356-.035-.567-.035H2.17v2.31c0-.005 2.2-.005 2.2-.005zm.105 4.193c.215 0 .426-.035.606-.07.176-.035.356-.106.496-.216s.25-.215.356-.39c.07-.176.14-.391.14-.641 0-.496-.14-.852-.426-1.102-.285-.215-.676-.32-1.137-.32H2.17v2.734h2.305zm6.858-.035q.428.427 1.278.426c.39 0 .746-.106 1.032-.286q.426-.32.53-.64h1.74c-.286.851-.712 1.457-1.278 1.848-.566.355-1.243.566-2.06.566a4.1 4.1 0 0 1-1.527-.285 2.8 2.8 0 0 1-1.137-.782 2.85 2.85 0 0 1-.712-1.172c-.175-.461-.25-.957-.25-1.528 0-.531.07-1.032.25-1.493.18-.46.426-.852.747-1.207.32-.32.711-.606 1.137-.782a4 4 0 0 1 1.493-.285c.606 0 1.137.105 1.598.355.46.25.817.532 1.102.958.285.39.496.851.641 1.348.07.496.105.996.07 1.563h-5.15c0 .58.21 1.11.496 1.396m2.24-3.732c-.25-.25-.642-.391-1.103-.391-.32 0-.566.07-.781.176s-.356.25-.496.39a.96.96 0 0 0-.25.497c-.036.175-.07.32-.07.46h3.196c-.07-.526-.25-.882-.497-1.132zm-3.127-3.728h3.978v.957h-3.978z" /> </svg> </span> </article> <article class="card d" style="--i: 3;"> <span class="icon"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-browser-firefox" viewBox="0 0 16 16"> <path d="M13.384 3.408c.535.276 1.22 1.152 1.556 1.963a8 8 0 0 1 .503 3.897l-.009.077-.026.224A7.758 7.758 0 0 1 .006 8.257v-.04q.025-.545.114-1.082c.01-.074.075-.42.09-.489l.01-.051a6.6 6.6 0 0 1 1.041-2.35q.327-.465.725-.87.35-.358.758-.65a1.5 1.5 0 0 1 .26-.137c-.018.268-.04 1.553.268 1.943h.003a5.7 5.7 0 0 1 1.868-1.443 3.6 3.6 0 0 0 .021 1.896q.105.07.2.152c.107.09.226.207.454.433l.068.066.009.009a2 2 0 0 0 .213.18c.383.287.943.563 1.306.741.201.1.342.168.359.193l.004.008c-.012.193-.695.858-.933.858-2.206 0-2.564 1.335-2.564 1.335.087.997.714 1.839 1.517 2.357a4 4 0 0 0 .439.241q.114.05.228.094c.325.115.665.18 1.01.194 3.043.143 4.155-2.804 3.129-4.745v-.001a3 3 0 0 0-.731-.9 3 3 0 0 0-.571-.37l-.003-.002a2.68 2.68 0 0 1 1.87.454 3.92 3.92 0 0 0-3.396-1.983q-.116.001-.23.01l-.042.003V4.31h-.002a4 4 0 0 0-.8.14 7 7 0 0 0-.333-.314 2 2 0 0 0-.2-.152 4 4 0 0 1-.088-.383 5 5 0 0 1 1.352-.289l.05-.003c.052-.004.125-.01.205-.012C7.996 2.212 8.733.843 10.17.002l-.003.005.003-.001.002-.002h.002l.002-.002h.015a.02.02 0 0 1 .012.007 2.4 2.4 0 0 0 .206.48q.09.153.183.297c.49.774 1.023 1.379 1.543 1.968.771.874 1.512 1.715 2.036 3.02l-.001-.013a8 8 0 0 0-.786-2.353" /> </svg> </span> </article> <article class="card e" style="--i: 4;"> <span class="icon"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-browser-edge" viewBox="0 0 16 16"> <path d="M9.482 9.341c-.069.062-.17.153-.17.309 0 .162.107.325.3.456.877.613 2.521.54 2.592.538h.002c.667 0 1.32-.18 1.894-.519A3.84 3.84 0 0 0 16 6.819c.018-1.316-.44-2.218-.666-2.664l-.04-.08C13.963 1.487 11.106 0 8 0A8 8 0 0 0 .473 5.29C1.488 4.048 3.183 3.262 5 3.262c2.83 0 5.01 1.885 5.01 4.797h-.004v.002c0 .338-.168.832-.487 1.244l.006-.006z" /> <path d="M.01 7.753a8.14 8.14 0 0 0 .753 3.641 8 8 0 0 0 6.495 4.564 5 5 0 0 1-.785-.377h-.01l-.12-.075a5.5 5.5 0 0 1-1.56-1.463A5.543 5.543 0 0 1 6.81 5.8l.01-.004.025-.012c.208-.098.62-.292 1.167-.285q.194.001.384.033a4 4 0 0 0-.993-.698l-.01-.005C6.348 4.282 5.199 4.263 5 4.263c-2.44 0-4.824 1.634-4.99 3.49m10.263 7.912q.133-.04.265-.084-.153.047-.307.086z" /> <path d="M10.228 15.667a5 5 0 0 0 .303-.086l.082-.025a8.02 8.02 0 0 0 4.162-3.3.25.25 0 0 0-.331-.35q-.322.168-.663.294a6.4 6.4 0 0 1-2.243.4c-2.957 0-5.532-2.031-5.532-4.644q.003-.203.046-.399a4.54 4.54 0 0 0-.46 5.898l.003.005c.315.441.707.821 1.158 1.121h.003l.144.09c.877.55 1.721 1.078 3.328.996" /> </svg> </span> </article> <article class="card f" style="--i: 5"> <span class="icon"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-browser-chrome" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M16 8a8 8 0 0 1-7.022 7.94l1.902-7.098a3 3 0 0 0 .05-1.492A3 3 0 0 0 10.237 6h5.511A8 8 0 0 1 16 8M0 8a8 8 0 0 0 7.927 8l1.426-5.321a3 3 0 0 1-.723.255 3 3 0 0 1-1.743-.147 3 3 0 0 1-1.043-.7L.633 4.876A8 8 0 0 0 0 8m5.004-.167L1.108 3.936A8.003 8.003 0 0 1 15.418 5H8.066a3 3 0 0 0-1.252.243 2.99 2.99 0 0 0-1.81 2.59M8 10a2 2 0 1 0 0-4 2 2 0 0 0 0 4" /> </svg> </span> </article> </section>

Estilos CSS para el diseño y responsividad

<style>
        /*
         * Estilos CSS:
         * Esta sección define la apariencia y el comportamiento visual de la pila de tarjetas.
         * Se utilizan variables CSS para facilitar la personalización y la gestión de animaciones.
         */

        /* Reseteo básico de CSS para asegurar consistencia entre navegadores */
        *,
        *::after,
        *::before {
            margin: 0;
            padding: 0;
            box-sizing: border-box; /* Incluye padding y borde en el ancho/alto total del elemento */
        }

        /* Estilos del cuerpo de la página */
        body {
            /* Utiliza flexbox para centrar el contenido vertical y horizontalmente */
            display: grid;
            place-content: center; /* Atajo para place-items y justify-content */
            min-block-size: 100vh; /* Asegura que el cuerpo ocupe al menos el 100% de la altura del viewport */
            overflow-x: clip; /* Evita el desplazamiento horizontal no deseado */
            /* Fondo degradado para un aspecto moderno y atractivo */
            background: linear-gradient(45deg, hsl(203 7% 89%), hsl(198 13% 71%));
            font-family: 'Inter', sans-serif; /* Aplica la fuente Inter a todo el cuerpo */
            color: #333; /* Color de texto por defecto */
        }

        /* Contenedor principal de la pila de tarjetas */
        .card-stack {
            width: 16rem; /* Ancho fijo para el contenedor */
            height: 22rem; /* Alto fijo para el contenedor */
            position: relative; /* Necesario para posicionar las tarjetas absolutamente dentro */
            display: grid;
            grid-auto-flow: column; /* Organiza los elementos en una columna (aunque solo hay uno visible a la vez) */
            place-content: center; /* Centra el contenido dentro del grid */
            user-select: none; /* Evita que el usuario seleccione texto */
            touch-action: none; /* Deshabilita acciones táctiles predeterminadas del navegador (ej. scroll) */
            transform-style: preserve-3d; /* Permite que los elementos hijos se posicionen en 3D */
            /* Añade un poco de perspectiva para un efecto más inmersivo */
            perspective: var(--card-perspective, 700px);
        }

        /* Variables CSS globales para la configuración de las tarjetas */
        :root {
            --card-perspective: 700px; /* Profundidad de la perspectiva para el efecto 3D */
            --card-z-offset: 12px; /* Desplazamiento en el eje Z para cada tarjeta en la pila */
            --card-y-offset: 7px; /* Desplazamiento en el eje Y para cada tarjeta en la pila */
            --card-max-z-index: 100; /* Z-index máximo para la tarjeta superior */
            --card-swap-duration: 0.3s; /* Duración de la animación de deslizamiento y reordenamiento */
            --swipe-x: 0px; /* Variable para el desplazamiento horizontal durante el swipe */
            --swipe-rotate: 0deg; /* Variable para la rotación durante el swipe */
        }

        /* Estilos para cada tarjeta individual */
        .card {
            cursor: grab; /* Cambia el cursor a "grab" para indicar que es arrastrable */
            background-color: #eee; /* Color de fondo por defecto (será sobrescrito por degradados específicos) */
            display: grid;
            place-content: center; /* Centra el contenido (el icono) dentro de la tarjeta */
            place-self: center; /* Centra la tarjeta dentro de su contenedor grid */
            position: absolute; /* Permite superponer las tarjetas */
            width: calc(100% - 2rem); /* Ancho de la tarjeta, un poco más pequeño que el contenedor */
            height: calc(100% - 2rem); /* Alto de la tarjeta, un poco más pequeño que el contenedor */
            border: 1px solid #99a; /* Borde sutil */
            border-radius: 0.75rem; /* Esquinas redondeadas */
            /*
             * Z-index calculado:
             * La tarjeta superior tendrá el z-index más alto (100), y las tarjetas inferiores
             * tendrán un z-index decreciente, creando el efecto de pila.
             * `--i` es una variable CSS establecida en el HTML (o por JavaScript) que representa el índice de la tarjeta.
             */
            z-index: calc(var(--card-max-z-index) - var(--i));
            /*
             * Transformaciones 3D para el efecto de pila y deslizamiento:
             * perspective(): Define la profundidad del espacio 3D.
             * translateZ(): Desplaza la tarjeta en el eje Z (hacia atrás en la pila).
             * translateY(): Desplaza la tarjeta en el eje Y (hacia abajo en la pila).
             * translateX(): Desplaza la tarjeta horizontalmente durante el swipe (controlado por --swipe-x).
             * rotateY(): Rota la tarjeta durante el swipe (controlado por --swipe-rotate).
             */
            transform: perspective(var(--card-perspective))
                translateZ(calc(-1 * var(--card-z-offset) * var(--i)))
                translateY(calc(var(--card-y-offset) * var(--i)))
                translateX(var(--swipe-x, 0px)) rotateY(var(--swipe-rotate, 0deg));
            /*
             * Transición para animaciones suaves:
             * Aplica una transición a la propiedad 'transform' y 'opacity' para suavizar los cambios.
             * La duración se toma de la variable CSS --card-swap-duration.
             */
            transition: transform var(--card-swap-duration) ease, opacity var(--card-swap-duration) ease;
            will-change: transform, opacity; /* Sugiere al navegador que estas propiedades cambiarán para optimización */
            box-shadow: 0 2px 2px rgba(0, 0, 0, 0.18); /* Sombra sutil para dar profundidad */
        }

        /* Estilos para el contenedor del icono SVG */
        .icon {
            aspect-ratio: 1; /* Mantiene el icono cuadrado */
            block-size: 6em; /* Tamaño del icono */
            place-self: center; /* Centra el icono dentro de su contenedor */
        }

        /* Estilos para los iconos SVG dentro del contenedor .icon */
        .icon svg {
            display: block; /* Elimina espacios extra que puedan aparecer con SVGs inline */
            width: 100%; /* El SVG ocupa todo el ancho de su contenedor */
            height: 100%; /* El SVG ocupa todo el alto de su contenedor */
            fill: #fff; /* Color de relleno del SVG (blanco) */
            filter: drop-shadow(0px 2px 3px rgba(0, 0, 0, 0.47)); /* Sombra para el icono */
        }

        /* Estilo cuando una tarjeta está siendo arrastrada (activa) */
        .card:active {
            cursor: grabbing; /* Cambia el cursor a "grabbing" */
        }

        /* Estilos de fondo para cada tarjeta individual (degradados únicos) */
        .a { background: linear-gradient(45deg, #32de84, #deb); } /* Verde a beige */
        .b { background: linear-gradient(45deg, #cf8bf3, #fdb99b); } /* Morado a naranja */
        .c { background: linear-gradient(45deg, #ea52ca, #8ed5f0); } /* Rosa a azul claro */
        .d { background: linear-gradient(45deg, #967edf, #89ffe3); } /* Morado a verde azulado */
        .e { background: linear-gradient(45deg, #4ecde2, #faffd2); } /* Azul claro a amarillo pálido */
        .f { background: linear-gradient(45deg, #a4ffbd, #ffd89b); } /* Verde menta a naranja pálido */

        /* Media queries para asegurar la responsividad en pantallas más pequeñas */
        @media (max-width: 640px) {
            .card-stack {
                width: 14rem; /* Reduce el ancho del contenedor en móviles */
                height: 20rem; /* Reduce el alto del contenedor en móviles */
            }

            .icon {
                block-size: 5em; /* Reduce el tamaño del icono en móviles */
            }
        }
    </style>
    
    

Funcionalidad JavaScript para el deslizamiento

<script>
        /*
         * Lógica JavaScript:
         * Este script maneja la interactividad de la pila de tarjetas,
         * permitiendo al usuario deslizar (swipe) la tarjeta superior
         * para revelar la siguiente.
         */

        // Asegura que el script se ejecute una vez que el DOM esté completamente cargado.
        document.addEventListener("DOMContentLoaded", () => {
            // Referencia al contenedor de la pila de tarjetas
            const cardStack = document.querySelector(".card-stack");
            // Colección de todas las tarjetas, convertida a un array para facilitar la manipulación
            let cards = [...document.querySelectorAll(".card")];

            // Variables de estado para el manejo del swipe
            let isSwiping = false; // Indica si se está realizando un swipe
            let startX = 0; // Posición X inicial del puntero al inicio del swipe
            let currentX = 0; // Posición X actual del puntero durante el swipe
            let animationFrameId = null; // ID para requestAnimationFrame, para optimizar las animaciones

            /**
             * @function getDurationFromCSS
             * @description Obtiene el valor de una variable CSS que representa una duración (ej. "0.3s" o "300ms").
             * @param {string} variableName - El nombre de la variable CSS (ej. "--card-swap-duration").
             * @param {HTMLElement} [element=document.documentElement] - El elemento del cual obtener la variable.
             * @returns {number} La duración en milisegundos.
             */
            const getDurationFromCSS = (
                variableName,
                element = document.documentElement
            ) => {
                const value = getComputedStyle(element)
                    ?.getPropertyValue(variableName)
                    ?.trim();
                if (!value) return 0; // Retorna 0 si la variable no existe o está vacía
                if (value.endsWith("ms")) return parseFloat(value); // Si termina en 'ms', parsea directamente
                if (value.endsWith("s")) return parseFloat(value) * 1000; // Si termina en 's', convierte a ms
                return parseFloat(value) || 0; // Intenta parsear como número, por defecto 0
            };

            /**
             * @function getActiveCard
             * @description Retorna la tarjeta actualmente en la parte superior de la pila.
             * @returns {HTMLElement|null} La tarjeta activa o null si no hay tarjetas.
             */
            const getActiveCard = () => cards[0];

            /**
             * @function updatePositions
             * @description Actualiza las variables CSS `--i` de todas las tarjetas
             * y reinicia las transformaciones de swipe. Esto es crucial para
             * reordenar visualmente las tarjetas después de un swipe.
             */
            const updatePositions = () => {
                cards.forEach((card, i) => {
                    // Establece la variable --i para cada tarjeta.
                    // Esto afecta su z-index, translateZ y translateY en CSS.
                    card.style.setProperty("--i", i); // Usamos 'i' directamente ya que el CSS espera 0, 1, 2...
                    // Reinicia las transformaciones de swipe para la tarjeta
                    card.style.setProperty("--swipe-x", "0px");
                    card.style.setProperty("--swipe-rotate", "0deg");
                    card.style.opacity = "1"; // Asegura que la tarjeta sea completamente visible
                    // Vuelve a habilitar las transiciones después de un posible deshabilitado durante el swipe
                    card.style.transition = `transform var(--card-swap-duration) ease, opacity var(--card-swap-duration) ease`;
                });
            };

            /**
             * @function applySwipeStyles
             * @description Aplica las transformaciones visuales (traslación y rotación)
             * a la tarjeta activa durante el arrastre del usuario.
             * @param {number} deltaX - El desplazamiento horizontal del puntero desde el inicio del swipe.
             */
            const applySwipeStyles = (deltaX) => {
                const card = getActiveCard();
                if (!card) return; // No hacer nada si no hay tarjeta activa

                // Actualiza las variables CSS para el desplazamiento y la rotación
                card.style.setProperty("--swipe-x", `${deltaX}px`);
                // La rotación es proporcional al desplazamiento, creando un efecto de "inclinación"
                card.style.setProperty("--swipe-rotate", `${deltaX * 0.2}deg`);
                // La opacidad disminuye a medida que la tarjeta se desliza más lejos
                card.style.opacity = 1 - Math.min(Math.abs(deltaX) / 100, 1) * 0.75;
            };

            /**
             * @function handleStart
             * @description Maneja el evento de inicio del puntero (pointerdown).
             * Inicializa el estado de swipe y desactiva las transiciones de la tarjeta.
             * @param {number} clientX - La coordenada X del puntero al inicio.
             */
            const handleStart = (clientX) => {
                if (isSwiping) return; // Evita iniciar un nuevo swipe si ya hay uno en curso
                isSwiping = true;
                startX = currentX = clientX; // Guarda la posición inicial del puntero
                const card = getActiveCard();
                // Desactiva temporalmente las transiciones para un arrastre más responsivo
                card && (card.style.transition = "none");
            };

            /**
             * @function handleMove
             * @description Maneja el evento de movimiento del puntero (pointermove).
             * Actualiza las transformaciones de la tarjeta en tiempo real usando requestAnimationFrame.
             * @param {number} clientX - La coordenada X actual del puntero.
             */
            const handleMove = (clientX) => {
                if (!isSwiping) return; // Solo procesa el movimiento si se está swiping
                cancelAnimationFrame(animationFrameId); // Cancela cualquier frame de animación anterior
                // Solicita un nuevo frame de animación para actualizar la UI
                animationFrameId = requestAnimationFrame(() => {
                    currentX = clientX; // Actualiza la posición X actual
                    const deltaX = currentX - startX; // Calcula el desplazamiento desde el inicio
                    applySwipeStyles(deltaX); // Aplica los estilos de swipe

                    // Si el desplazamiento excede un umbral, se considera un swipe completado
                    // y se llama a handleEnd para finalizar la acción.
                    // Esto permite un "swipe rápido" donde no es necesario soltar el botón/dedo.
                    if (Math.abs(deltaX) > 50) {
                        handleEnd();
                    }
                });
            };

            /**
             * @function handleEnd
             * @description Maneja el evento de finalización del puntero (pointerup).
             * Determina si la tarjeta debe ser deslizada o si debe regresar a su posición original.
             */
            const handleEnd = () => {
                if (!isSwiping) return; // Solo procesa si se estaba swiping
                cancelAnimationFrame(animationFrameId); // Cancela cualquier frame de animación pendiente

                const deltaX = currentX - startX; // Desplazamiento total del swipe
                const threshold = 50; // Umbral en píxeles para considerar un swipe válido
                const duration = getDurationFromCSS("--card-swap-duration"); // Duración de la animación

                const card = getActiveCard();
                if (card) {
                    // Vuelve a habilitar las transiciones para la animación de retorno o de salida
                    card.style.transition = `transform ${duration}ms ease, opacity ${duration}ms ease`;

                    if (Math.abs(deltaX) > threshold) {
                        // Si el swipe excede el umbral, la tarjeta se desliza fuera
                        const direction = Math.sign(deltaX); // 1 para derecha, -1 para izquierda

                        // Mueve la tarjeta fuera de la vista y la rota ligeramente
                        card.style.setProperty("--swipe-x", `${direction * 300}px`);
                        card.style.setProperty("--swipe-rotate", `${direction * 20}deg`);
                        card.style.opacity = "0"; // Hace que la tarjeta desaparezca

                        // Pequeño retraso para un efecto visual de "rebote"
                        setTimeout(() => {
                            card.style.setProperty("--swipe-rotate", `${-direction * 20}deg`);
                        }, duration * 0.5);

                        // Después de la duración de la animación, reordena las tarjetas
                        setTimeout(() => {
                            // Mueve la tarjeta deslizada al final del array
                            cards = [...cards.slice(1), card];
                            updatePositions(); // Actualiza las posiciones de todas las tarjetas
                        }, duration);
                    } else {
                        // Si el swipe no excede el umbral, la tarjeta regresa a su posición original
                        applySwipeStyles(0); // Reinicia los estilos de swipe (vuelve a 0)
                    }
                }

                // Reinicia el estado de swipe
                isSwiping = false;
                startX = currentX = 0;
            };

            /**
             * @function addEventListeners
             * @description Adjunta los listeners de eventos de puntero al contenedor de la pila de tarjetas.
             * Los eventos de puntero son preferibles a mouse/touch individuales para compatibilidad.
             */
            const addEventListeners = () => {
                cardStack?.addEventListener("pointerdown", ({ clientX }) =>
                    handleStart(clientX)
                );
                cardStack?.addEventListener("pointermove", ({ clientX }) =>
                    handleMove(clientX)
                );
                // pointerup y pointercancel son importantes para manejar el final del arrastre
                // incluso si el puntero sale del elemento.
                cardStack?.addEventListener("pointerup", handleEnd);
                cardStack?.addEventListener("pointercancel", handleEnd);
            };

            // Inicializa las posiciones de las tarjetas al cargar la página
            updatePositions();
            // Adjunta los listeners de eventos para la interactividad
            addEventListeners();
        });
    </script>


Ahora que conoces el paso a paso para crear un carrusel de deslizamiento de tarjetas, te invito a poner en práctica estos conocimientos y personalizar tu componente según las necesidades de tu proyecto.

¡No dudes en dejar tus comentarios abajo con tus dudas, sugerencias o ejemplos de cómo implementaste esta funcionalidad! ¿Quieres seguir aprendiendo sobre diseño web y desarrollo frontend? Explora más recetas y tutoriales en nuestro blog y convierte tu sitio en una experiencia visual única y efectiva.

Publicar un comentario

0 Comentarios