Tutorial de Alpine.js (Con ejemplos prácticos)

Alpine.js es un framework de JavaScript muy ligero y con una sintaxis muy similar a Vue.js, es perfecto para proyectos pequeños en donde no uses mucho código de JavaScript.

Voy a mostrarte como usar Alpine.js creando un juego de memoria. Si quieres puedes probar el juego hecho con Alpine.js antes.

Instalar Alpine.js y Tailwind CSS

Tailwind CSS es un framework para trabajar con hojas de estilo, vamos a usarlo muy poco, ya que no es mi intensión hacer un juego con una gran estética, solo quiero mostrarte como usar Alpine.js. También vamos a utilizar la librería de iconos Font Awesome para las imágenes de las cartas.

Primero crea un archivo HTML y agrega este código:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <script defer src="https://unpkg.com/[email protected]/dist/cdn.min.js"></script>
    <script src="https://cdn.tailwindcss.com/"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"/>

    <title>Juego de memoria</title>
</head>
<body >
 
</body>
</html>

Como puedes notar no es necesario instalar NPM para usar Alpine.js, solo necesitas cargar el script.

Ahora vamos a agregar el código HTML para mostrar las cartas, solo debes agregar esto entre las etiquetas body:

<div class="grid grid-cols-3 w-44">                   
    <div class="bg-amber-500 w-10 p-2 m-2" >                        
        <button><i class="fa-solid fa-baseball-bat-ball"></i></button>                        
    </div>  
    <div class="bg-amber-500 w-10 p-2 m-2" >                        
        <button><i class="fa-solid fa-baseball-bat-ball"></i></button>                        
    </div>  
    <div class="bg-amber-500 w-10 p-2 m-2" >                        
        <button><i class="fa-solid fa-baseball-bat-ball"></i></button>                        
    </div>
    <div class="bg-amber-500 w-10 p-2 m-2" >                        
        <button><i class="fa-solid fa-baseball-bat-ball"></i></button>                        
    </div>  
    <div class="bg-amber-500 w-10 p-2 m-2" >                        
        <button><i class="fa-solid fa-baseball-bat-ball"></i></button>                        
    </div>  
    <div class="bg-amber-500 w-10 p-2 m-2" >                        
        <button><i class="fa-solid fa-baseball-bat-ball"></i></button>                        
    </div>      
</div>

Si consultas la página web, verás algo como esto

Ejemplo del juego que construiremos con Alpine.js

Ya mostramos las cartas, pero hay dos problemas (1) todas las cartas son iguales y (2) todas están reveladas y necesitamos que estén voleadas. Vamos a corregir esto usado Alpine.js.

Trabajando con datos y plantillas

Alpine.js reconoce variables declaradas como parte de tu código HTML, agregando atributos en las etiquetas HTML. Estas variables pueden ser textos, números o incluso objetos y hasta objetos devueltos por medio de una función.

Borremos el código que estaba entre las etiquetas body y lo sustituimos por este:

<div x-data="juego()">
        
</div>

<script>
    function juego() {
        return {
            cartas: [
                { icon: 'fa-solid fa-baseball-bat-ball', volteada: false, encontrada: false },
                { icon: 'fa-solid fa-baseball-bat-ball', volteada: false, encontrada: false },
                { icon: 'fa-solid fa-medal', volteada: false, encontrada: false },
                { icon: 'fa-solid fa-medal', volteada: false, encontrada: false },                     
                { icon: 'fa-solid fa-bicycle', volteada: false, encontrada: false },
                { icon: 'fa-solid fa-bicycle', volteada: false, encontrada: false },                      
            ]                 
        }
    };
</script>

Como puedes ver hay una etiqueta div con un atributo llamado x-data, esa es la sintaxis para declarar nuestras variables. El valor de este atributo es una función que regresa un objeto json que contiene una lista de cartas con su respectivo icono y estados para saber si la carta está voleada o si ya ha sido encontrada por el jugador.

Ahora que ya tenemos los datos, vamos a generar la plantilla, para eso agregas este código dentro de la etiqueta div que ya tienes

<div class="grid grid-cols-3 w-44">
    <template x-for="(carta, index) in cartas" :key="index">                
            <div class="bg-amber-500 w-10 p-2 m-2" >                        
                <button>
                    <i x-bind:class="carta.icon"></i>
                </button>                        
            </div>                
    </template>
</div> 

Usamos la etiqueta template para definir la plantilla HTML de una carta, y usamos el atributo x-for de alpine.js para que se comporte como un ciclo for y genere una carta para cada elemento de nuestro objeto json que tiene un array de cartas llamado cartas.

También puedes notar que usamos el atributo x-bind, este se usa para definir dinámicamente un atributo para etiquetas HTML, en este caso estamos definiendo el atributo class para mostrar un icono en cada carta de acuerdo al objeto json que tenemos.

Puedes probar el código hasta ahora y verás que se generan las cartas según nuestro objeto json, también puedes intentar agregar más cartas en el objeto json y verás que se muestran en la página web.

Trabajando con eventos

Ahora vamos a agregar un evento clic para que podamos voltear cartas. Pero antes debemos hacer que las cartas este todas ocultando su icono.

Como ya tenemos una propiedad que nos dice, si la carta está volteada, vamos a usarla para mostrar su icono u ocultarlo. ¿Recuerdas el atributo x-bind?, bueno, ahora vamos a cambiar esta línea de código y la dejaremos de esta forma:

<i x-bind:class="(carta.volteada ? carta.icon : '')"></i>

A eso se le conoce como operador ternario, es una forma de if abreviado. Ahora necesitamos una función que nos permita voltear las cartas, para que alpine.js la reconozca sin problemas; la debemos agregar al objeto json de esta forma:

function juego() {
        return {
            cartas: [
                { icon: 'fa-solid fa-baseball-bat-ball', volteada: false, encontrada: false },
                { icon: 'fa-solid fa-baseball-bat-ball', volteada: false, encontrada: false },
                { icon: 'fa-solid fa-medal', volteada: false, encontrada: false },
                { icon: 'fa-solid fa-medal', volteada: false, encontrada: false },                     
                { icon: 'fa-solid fa-bicycle', volteada: false, encontrada: false },
                { icon: 'fa-solid fa-bicycle', volteada: false, encontrada: false },                      
            ],
            voltear(carta) {
                    carta.volteada = true ;                     
                }                 
        }
    };

La función que agregamos se llama voltear y recibe como parámetro un objeto. Lo único que hace es cambiar la propiedad volteada a cierto. Para ingresar código en el evento clic usamos el atributo @click, esto lo haremos en la plantilla que creamos y quedará de esta forma:

<template x-for="(carta, index) in cartas" :key="index">                
        <div class="bg-amber-500 w-10 p-2 m-2" @click="voltear(carta)">                        
            <button>
                <i x-bind:class="(carta.volteada ? carta.icon : '')"></i>
            </button>                        
        </div>                
</template>

Ya puedes hacer clic sobre las cartas y estas se voltearán revelando su icono.

Como definir propiedades para un objeto en Alpine.js

Vamos a avanzar más con nuestro juego, pero primero debemos ser capaces de consultar algunas cosas como: la lista de cartas, las cartas que se encuentran volteadas (para determinar si son pareja o no) y las cartas que aún siguen en juego.

La mejor formar de hacerlo es agregando algunas propiedades que se definen con la palabra get y básicamente son funciones que regresan un valor. Vamos a agregar 3 propiedades, de forma que nuestro script quede de esta forma:

<script>
    function juego() {
        return {
            cartas: [
                { icon: 'fa-solid fa-baseball-bat-ball', volteada: false, encontrada: false },
                { icon: 'fa-solid fa-baseball-bat-ball', volteada: false, encontrada: false },
                { icon: 'fa-solid fa-medal', volteada: false, encontrada: false },
                { icon: 'fa-solid fa-medal', volteada: false, encontrada: false },                     
                { icon: 'fa-solid fa-bicycle', volteada: false, encontrada: false },
                { icon: 'fa-solid fa-bicycle', volteada: false, encontrada: false },                      
            ],
            voltear(carta) {
                    carta.volteada = true ;                     
                },
            get cartasVolteadas() {
                return this.cartas.filter(carta => (carta.volteada && !carta.encontrada));
            },    

            get cartasEnJuego(){
                return this.cartas.filter(carta => (!carta.encontrada));
            }   ,

            get baraja(){
                return this.cartas;
            }   ,                     
        }
    };
</script>

get cartasVolteadas() hace eso de los filtros de arreglos de javascript regresando cada elemento en donde la propiedad volteada este como true y que su propiedad encontrada sea falso, es decir todas la cartas que esten volteadas y no pertenezcan a las cartas que ya fueron encontradas. Esto nos servirá para saber que cartas debemos verificar si son parejas o no.

get cartasEnJuego() regresa todas las cartas que aun no han sido encontradas junto a sus parejas, esto nos servira para saber si el juego ya termino.

get baraja() regresa todas las cartas sin ningun filtro, esto nos permitira recorrer todas las cartas.

Ahora vamos a comenzar a hacer uso de estas propiedades en la función voltear(carta), vamos su código para que qude de esta forma:

voltear(carta) {
        carta.volteada = true ;  
        if( this.cartasVolteadas.length == 2 )
            {
            if (this.cartasVolteadas[0].icon ==  this.cartasVolteadas[1].icon )
            {
                this.cartasVolteadas.forEach(carta => carta.encontrada = true);
                if (this.cartasEnJuego.length == 0) {
                    alert("Ganaste!!!");
                    // Resetear el juego
                    this.baraja.forEach(carta => {carta.volteada = false; carta.encontrada = false});
                }                                       
            }
            else{

                setTimeout(() => {
                    this.cartasVolteadas.forEach(carta => carta.volteada = false);                                
                }, 500);
            }
        }                   
    },

Voy a hablar un poco del codigo anterior, primero obtenemos las cartas volteadas y vemos si su longitud es igual a 2, es decir, ya elegimos dos cartas. Comparamos si tiene el mismo icono (son pareja), si no lo son entonces esperamos 500 milisengudos y volvemos a voltear la carta.

Como Alpine.js es un framework reactivo al cambiar la el objeto carta.volteada = false, la carta se actualiza automaticamente.

En caso de que sí sean parejas entonces cambiamos su el objeto carta.encontrada a verdadero para sacar esas cartas del juego.

Después verificamos si hay más cartas en juego (this.cartasEnJuego.length == 0), si no hay más cartas, entonces el juego termino y recorremos toda la baraja con la propiedad this.baraja que creamos, para resetear los valores de volteada y encontrada.

Algunas mejoras al juego

Para hacer un poco mejor el juego voy a agregar un sistema de puntos, vamos a revolver las cartas para no saber en donde están y un mensaje que te diga cuando hayas encontrado una pareja.

Comencemos por lo más sencillo, para revolver o desordenar las cartas, solo agregamos esta línea al final de la lista de cartas: .sort(() => Math.random() - .5),

El código quedará de esta forma:

cartas: [
    { icon: 'fa-solid fa-baseball-bat-ball', volteada: false, encontrada: false },
    { icon: 'fa-solid fa-baseball-bat-ball', volteada: false, encontrada: false },
    { icon: 'fa-solid fa-medal', volteada: false, encontrada: false },
    { icon: 'fa-solid fa-medal', volteada: false, encontrada: false },                     
    { icon: 'fa-solid fa-bicycle', volteada: false, encontrada: false },
    { icon: 'fa-solid fa-bicycle', volteada: false, encontrada: false },                      
].sort(() => Math.random() - .5),

Ahora veamos el sistema de puntos, primero debemos agregar una variable más al objeto json (intentos:0) y vamos a incrementarlo cada vez que volteemos dos cartas. También tenemos que agregar un poco de código HTML para mostrar los puntos y también enlazarlos a la variable intentos. El código HTML, será algo como esto:

<h1 >
  <span x-text="intentos"></span>
  Intentos
</h1>

Como puedes ver, hay una etiqueta span con un atributo llamado x-text con el valor “intentos”, x-text es la forma de enlazar el contenido de una etiqueta con el valor de una variable o función en alpine.js.

De esta forma el texto que verás será 0 Intentos, 1 Intento y así sucesivamente mientras el valor de la variable se incremente.

Esta es una imagen de los cambios en el código que debes aplicar para llevar la puntuación:

Mostrar puntaje en nuestro juego de cartas de Alpine.js

Veamos ahora como mostrar un mensaje cuando encuentres una pareja. En esta ocasión voy a introducir varias funcionalidades de alpine.js

Primero agreguemos este código HTML en las etiquetas BODY

<div id="dialogo" x-data="{show:true, message:''}" x-show="show" 
            x-transition:enter="transition ease-out duration-300"
            x-transition:enter-start="opacity-0 scale-90"
            x-transition:enter-end="opacity-100 scale-100"
            x-transition:leave="transition ease-in duration-300"
            x-transition:leave-start="opacity-100 scale-100"
            x-transition:leave-end="opacity-0 scale-90"
        >
            <h1 x-text="message"></h1>
</div> 

Por medio de x-data definimos dos variables, show nos permitirá definir si queremos mostrar u ocultar el mensaje, y message nos servirá para definir el mensaje que deseamos mostrar.

Puedes observar que hay un atributo nuevo x-show, este se usa para mostrar u ocultar elementos de la página web, como está enlazado a la variable show, cuando esta sea true, se mostrará y cuando su valor sea false, se ocultará.

También hay una serie de propiedades x-transition esas se usan para generar animaciones de entrada y salida (nuestros mensajes tendrán una animación).

Ahora necesitamos una función para hacer la llamada y mostrar los mensajes en cuanto los necesitemos. Este es el código de la función:

mensaje(msg)
    {
        console.log(  msg );
        document.getElementById('dialogo')._x_dataStack[0].show = 1;
        document.getElementById('dialogo')._x_dataStack[0].message = msg;
        setInterval(() => {
            document.getElementById('dialogo')._x_dataStack[0].show = 0;
        }, 2500);
    }  

Como pueden ver, usando JavaScript puro, podemos acceder a las variables de alpine.js usando el objeto _x_dataStack[0] y el nombre de las variables.

Lo que hace esta función es mostrar el cuadro del mensaje, definir el mensaje a mostrar, luego espera 2500 milisegundos y oculta el mensaje.

Ahora solo hace falta mostrar el mensaje, para ello usamos este código:

this.mensaje("Encontraste una pareja!!");     

En caso de que te perdieras en alguna parte, aquí está el código completo:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <script defer src="https://unpkg.com/[email protected]/dist/cdn.min.js"></script>
    <script src="https://cdn.tailwindcss.com/"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"/>

    <title>Juego de memoria</title>
</head>
<body >

<div x-data="juego()">
    <h1 >
        <span x-text="intentos"></span>
        Intentos
    </h1>
    <div class="grid grid-cols-3 w-44">
        <template x-for="(carta, index) in cartas" :key="index">                
                <div class="bg-amber-500 w-10 p-2 m-2" @click="voltear(carta)">                        
                    <button>
                        <i x-bind:class="(carta.volteada ? carta.icon : '')"></i>
                    </button>                        
                </div>                
        </template>
    </div>    
</div>

<div id="dialogo" x-data="{show:true, message:''}" x-show="show" 
            x-transition:enter="transition ease-out duration-300"
            x-transition:enter-start="opacity-0 scale-90"
            x-transition:enter-end="opacity-100 scale-100"
            x-transition:leave="transition ease-in duration-300"
            x-transition:leave-start="opacity-100 scale-100"
            x-transition:leave-end="opacity-0 scale-90"
        >
            <h1 x-text="message"></h1>
</div>   

<script>
    function juego() {
        return {
            cartas: [
                { icon: 'fa-solid fa-baseball-bat-ball', volteada: false, encontrada: false },
                { icon: 'fa-solid fa-baseball-bat-ball', volteada: false, encontrada: false },
                { icon: 'fa-solid fa-medal', volteada: false, encontrada: false },
                { icon: 'fa-solid fa-medal', volteada: false, encontrada: false },                     
                { icon: 'fa-solid fa-bicycle', volteada: false, encontrada: false },
                { icon: 'fa-solid fa-bicycle', volteada: false, encontrada: false },                      
                ].sort(() => Math.random() - .5),
            intentos:0,
            voltear(carta) {
                carta.volteada = true ;  
                if( this.cartasVolteadas.length == 2 )
                {
                    this.intentos++;
                    if (this.cartasVolteadas[0].icon ==  this.cartasVolteadas[1].icon )
                    {
                        this.mensaje("Encontraste una pareja!!");     
                        this.cartasVolteadas.forEach(carta => carta.encontrada = true);
                        if (this.cartasEnJuego.length == 0) 
                        {
                            alert("Ganaste!!!");
                            // Resetear el juego
                            this.baraja.forEach(carta => {carta.volteada = false; carta.encontrada = false});
                        }                                       
                    }
                    else
                    {

                        setTimeout(() => 
                        {
                            this.cartasVolteadas.forEach(carta => carta.volteada = false);                                
                        }, 500);
                    }
                }                   
            },
            get cartasVolteadas() 
            {
                return this.cartas.filter(carta => (carta.volteada && !carta.encontrada));
            },    

            get cartasEnJuego()
            {
                return this.cartas.filter(carta => (!carta.encontrada));
            },

            get baraja()
            {
                return this.cartas;
            }   ,  
            mensaje(msg)
            {
                console.log(  msg );
                document.getElementById('dialogo')._x_dataStack[0].show = 1;
                document.getElementById('dialogo')._x_dataStack[0].message = msg;
                setInterval(() => {
                    document.getElementById('dialogo')._x_dataStack[0].show = 0;
                }, 2500);
            }                        
        }        
    };
</script>
 
</body>
</html>

Conclusión

Al crear este juego has aprendido de forma práctica algunas de las funcionalidades de Alpine.js, te recomiendo que sigas practicando, por ejemplo podrías mejorar este juego y permitir seleccionar otra dificultad con más cartas.

Si tienes dudas o simplemente deseas comentar lo que piensas de este artículo, te pido que me dejes un comentario aquí abajo.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.