Tema
Efectos de cursor
Los efectos de cursor son muy populares en portafolios, agencias creativas y sitios de producto. Bien hechos, dan una sensación premium; mal hechos, hacen que el sitio sea difícil de usar. Aquí verás cómo hacerlos fluidos y responsables.
Concepto: seguir al mouse sin trabarse
Todos los efectos de esta sección siguen el mismo patrón:
- Escuchar
pointermovey guardar la posición del mouse en variables (sin tocar el DOM). - Actualizar el DOM en un bucle
requestAnimationFrame, como mucho una vez por fotograma. - Mover con
transform: translate(), nunca contop/left.
Para que un elemento siga al mouse "con retraso" elegante se usa interpolación lineal (lerp): en cada fotograma, el elemento recorre un porcentaje de la distancia que le falta.
js
// Con factor 0.15 recorre el 15 % de la distancia en cada fotograma:
// rápido cuando está lejos, suave al llegar
x += (mouseX - x) * 0.15;
y += (mouseY - y) * 0.15;
el.style.transform = `translate(${x}px, ${y}px)`;Responsabilidad con el cursor
- Nunca ocultes el cursor nativo en táctiles: usa
@media (hover: hover) and (pointer: fine)para aplicar el efecto sólo con mouse real. - El elemento decorativo debe tener
pointer-events: nonepara no bloquear los clics. - Respeta
prefers-reduced-motion: elimina el retraso o desactiva el efecto. - En sitios de cliente "serios" (bancos, gobierno, salud), mejor no usarlos.
Cursor personalizado
Un punto que sigue al mouse exactamente y un anillo que lo sigue con suavidad. Sobre enlaces y botones, el anillo crece; al hacer clic, se encoge.
Cómo funciona:
- El punto se mueve directamente en
pointermove(sin retraso). - El anillo se actualiza en el bucle
requestAnimationFramecon lerp (--suavidaden el CSS). e.target.closest('a, button')detecta si el mouse está sobre algo clicable y añade la clase.sobre-enlacealbody. El CSS cambia la variable--anilloy la transición hace el resto.mix-blend-mode: differenceinvierte los colores debajo del anillo, como en muchos portafolios de diseño.- El cursor nativo se oculta sólo si el dispositivo tiene mouse y después del primer movimiento (clase
.cursor-activo).
Estela del cursor (trail)
Una cadena de círculos que persiguen al mouse, cada uno más pequeño y transparente que el anterior. Funciona también con el dedo en táctiles.
El truco está en que cada círculo persigue al anterior, no al mouse:
js
let x = mx, y = my; // el primero persigue al mouse
puntos.forEach((p, i) => {
p.x += (x - p.x) * SEGUIMIENTO;
p.y += (y - p.y) * SEGUIMIENTO;
const escala = 1 - i / TOTAL;
p.el.style.transform = `translate(${p.x}px, ${p.y}px) scale(${escala})`;
x = p.x; y = p.y; // el siguiente persigue a este
});Cambia TOTAL y SEGUIMIENTO para estelas más largas o más "elásticas".
Spotlight (luz que sigue al mouse)
Una luz suave en el fondo y tarjetas cuyo interior y borde se iluminan donde está el cursor. Es el efecto que popularizaron sitios como Vercel o Linear.
Aquí JavaScript sólo actualiza variables CSS; los degradados los dibuja el CSS:
css
body {
background:
radial-gradient(280px circle at var(--x, 50%) var(--y, 30%), rgb(139 92 246 / .35), transparent 70%),
#050816;
}js
addEventListener('pointermove', (e) => {
document.body.style.setProperty('--x', e.clientX + 'px');
document.body.style.setProperty('--y', e.clientY + 'px');
});El borde iluminado de las tarjetas usa una técnica de máscara: un ::after con padding: 1px y dos máscaras combinadas con mask-composite: exclude, que eliminan el contenido y dejan visible sólo el anillo del padding, es decir, el borde.
Efecto decorativo = cero riesgo
El spotlight no oculta el cursor ni cambia cómo se interactúa con la página: si el JS no carga, todo sigue funcionando y sólo se pierde el brillo. Es la opción más segura para sitios de clientes.
Ejercicios
- Haz que el anillo del cursor muestre el texto "Ver" cuando esté sobre una imagen.
- Cambia la estela para que cada círculo tenga un color distinto del arcoíris (
hsl(i * 20 ...)). - Añade al spotlight una cuarta tarjeta y comprueba que el efecto funciona sin tocar el JS.
Ver solución del ejercicio 2
js
puntos.forEach((p, i) => {
p.el.style.background = `hsl(${i * 20} 90% 60%)`;
});Resumen
- Patrón base: guardar la posición en
pointermovey pintar enrequestAnimationFrame. - Lerp (
x += (destino - x) * factor) da movimiento suave sin librerías. - Mueve con
transform, usapointer-events: noney aplica el efecto sólo con(hover: hover) and (pointer: fine). - Actualizar variables CSS desde JS (spotlight) es lo más limpio y eficiente.