Sabemos que para conseguir un trabajo dentro de la industria tenemos armas que nos permiten presentar lo que sabemos, el CV y el portafolio, siendo el último de vital importancia, puesto que, como los frontends profesionales que somos 😎, debemos buscar diferenciadores que hagan lucir a nuestro portafolio único frente a otras solicitudes, y en este caso, te voy a mostrar cómo hacer un cursor personalizado sin necesidad de ninguna librería externa.

Pero antes te diré por qué el cursor personalizado es una gran opción para tu portafolio, no solo por lo sencillo de implementar, sino porque le dará un toque especial y estético a tu portafolio o proyecto personal, y, además, le dará a entender a los entrevistadores y reclutadores tus habilidades para manipular el DOM, habilidad indispensable para un frontend, permitiendo adquirir más valor como profesional, dando a entender que haces más cosas que centrar un div 😂.

Solo JS y CSS nos basta para este proyecto de Vue

Sin nada más que agregar, te comento que, para este post, tenemos que estar preparado para hacer uso de Vuejs, Vite, y el cursor que desarrollaremos se trabajará con CSS y JS, sin más introducción, abramos nuestro proyecto 🦾.

Nos posicionaremos en la carpeta donde crearemos el proyecto y colocaremos los siguientes comandos en la terminal para crear el proyecto con Vite.

vuejs -> npm create vite@latest
? Project name: > customize-cursor

Tomamos la opción de Vue y javascript como lenguaje de programación

✔︎ Project name: ...customize-cursor
? Select a framework > - Use arrow -keys. Return to submit.
Vanilla
>Vue
React
Preact
Lit
Svelte
Others
✔︎ Project name: ...customize-cursor
✔︎ Select a framework > - Vue
? Select a variant > - Use arrow-keys. Return to submit.
>Javascript
Typescript
Customize with create-vue
Nuxt

Ahora ingresemos a nuestra carpeta y corramos nuestro proyecto

-> vuejs ls
customize-cursor
-> vuejs cd customize-icons && code . && npm install && npm run dev

Con esto listo, ingresamos a nuestro navegador a el puerto que nos indica la consola, este caso es el puerto localhost:5173, y tenemos lo siguiente

Y para efecto prácticos, nos quedamos con estas vistas porque nuestro interés es crear un componente encima de nuestro portafolio una vez esté finalizado, como la cereza del pastel 🍰.

Ahora que comience la acción 🦾

Comencemos creando una carpeta con el nombre global dentro de nuestra carpeta components, de esta forma organizamos el proyecto como todos unos profesionales, y dentro, crearemos el archivo Cursor.vue y escribimos lo siguiente

<script setup>
import { onMounted, onUnmounted, ref } from 'vue';

let x = ref(0);
let y = ref(0);

</script>

<template>
  <div class="cursor-layout">
    <div :style="{ top:y, left:x }" class="cursor" />
  </div>
</template>

<style>
.cursor-layout {
  width: 100vw;
  height: 100vh;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 99;
  background: transparent;
  pointer-events: none;
}
.cursor {
  width: 50px;
  height: 50px;
  position: absolute;
  z-index: 999;
  border-radius: 50%;
  pointer-events: none;
  border: 2px solid green
}

</style>

Con ester elemento ya creado, quiero que notes que en la sección style, tengo en ambas clases un pointer event seteado none, esa es la clave para que nuestros clicks hagan efecto en la aplicación sin que intervengan los elementos, ahora, importamos nuestro componente dentro de nuestro archivo principal, que en este caso es app.vue y hacemos las siguientes modificaciones

<script setup>
import HelloWorld from './components/HelloWorld.vue'
import Cursor from './components/global/Cursor.vue'
</script>

<template>
  <div class="main">
    <Cursor />
    <a href="https://vitejs.dev" target="_blank">
      <img src="/vite.svg" class="logo" alt="Vite logo" />
    </a>
    <a href="https://vuejs.org/" target="_blank">
      <img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
    </a>
  </div>
  <HelloWorld msg="Vite + Vue" />
</template>

<style scoped>
.main {
  position: relative;
}
.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}
</style>

Cons estos pequeños cambios, lograremos ver lo siguiente en nuestra pantalla, sé que tenemos un aburrido circulo verde en una esquina, por ahora lo dejaremos así, para saber cuál es nuestro elemento por trabajar, y pasemos a la funcionalidad, no te preocupes, ya quedará un cursor digno de tu portafolio😉

Y para la funcionalidad dentro del archivo Cursor.vue colocaremos lo siguiente

<script setup>
import { onMounted, onUnmounted, ref } from 'vue';

let x = ref(0);
let y = ref(0);

const mouseMove = () => window.addEventListener("mousemove", mouseLocation)
const mouseMoveOut = () => window.removeEventListener("mousemove", mouseLocation)

const mouseLocation = (event) => {
  x.value = event.pageX - 25 + 'px'
  y.value = event.pageY - 25 + 'px'
  console.log(x.value, y.value)
}

onMounted(() => {
  mouseMove()
})

onUnmounted(() => {
  mouseMoveOut()
})

</script>

<template>
  <div class="cursor-layout">
    <div :style="{ top:y, left:x }" class="cursor" />
  </div>
</template>

<style>
.cursor-layout {
  width: 100vw;
  height: 100vh;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 99;
  background: transparent;
  pointer-events: none;
}
.cursor {
  width: 50px;
  height: 50px;
  position: absolute;
  z-index: 999;
  border-radius: 50%;
  pointer-events: none;
  border: 2px solid green;
}
</style>

Y si todo lo que agregamos es correcto, para este punto nuestro cursor está siendo seguido por el circulo verde que hemos creado en el componente Cursor.vue, y con el console.log() que hemos agregado, quiero que veas en los devtools del navegador lo valores que se imprimen en la consola detectados en pantalla por medio del event listener que hemos colocado, y actualizando la posición del círculo por medio de la función mouseLocation() creada para los datos creados en X y en Y, para este punto ya sabes por dónde va el asunto.

Ahora, para que el cursor se encuentre centrado en el cículo, debemos colocar la mitad del valor que hemos agregado en el ancho y en el alto del círculos en nuestra función, mouseLocation, que en este caso es de 25px, con lo que las variables X y Y se refrescan cuando el event "mousemove" detecte movimiento en nuestro cursor, y esto valores serán pasados nuestro template por medio del binding que estamos colocando en el atributo style que hemos agregado en la etiqueta div del cursor.

Vamos, estamos por finalizar

Por último, ahora removeré el console.log() que agregamos para que veas lo que se buscaba en nuestra función, y ustedes también deberían removerla como los profesionales que son, y ahora nos disponemos a mejorar el círculo verde que hemos creado, dándole una animación, y tengas una referencia de lo que puedes lograr, pero ya de aquí, depende del gusto, la estética y la sazón que le quieras dar al elemento cursor 😏

<script setup>
import { onMounted, onUnmounted, ref } from 'vue';

let x = ref(0);
let y = ref(0);

const mouseMove = () => window.addEventListener("mousemove", mouseLocation)
const mouseMoveOut = () => window.removeEventListener("mousemove", mouseLocation)

const mouseLocation = (event) => {
  x.value = event.pageX - 25 + 'px'
  y.value = event.pageY - 25 + 'px'
}

onMounted(() => {
  mouseMove()
})

onUnmounted(() => {
  mouseMoveOut()
})

</script>

<template>
  <div class="cursor-layout">
    <div :style="{ top:y, left:x }" class="cursor" />
  </div>
</template>

<style>
.cursor-layout {
  width: 100vw;
  height: 100vh;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 99;
  background: transparent;
  pointer-events: none;
}
.cursor {
  width: 50px;
  height: 50px;
  position: absolute;
  z-index: 999;
  border-radius: 50%;
  pointer-events: none;
  animation: pulse 1500ms infinite;
}
@keyframes pulse {
  0% {
    transform: scale(1);
    border: 2px solid green;
  }
  50% {
    transform: scale(2);
    border: 2px solid yellow;
  }
  100% {
    transform: rotate(1);
    border: 2px solid green;
  }
}
</style>

Con este pequeño cambio en los styles, agregué un efecto de pulso y cambio de color, pero que tú puedes mejorar sin problemas.

Ha sido un placer estar contigo en el desarrollo de este componente y me despido, no sin antes recordarte que puedes visitar el proyecto en mi perfil de GitHub, nos vemos hasta la próxima 🥸

Plataforma de cursos gratis sobre programación