Como criar efeitos de cursor animado com JavaScript

Aprenda a criar efeitos de cursor animado com JavaScript

São cinco efeitos animados — nessa aula estaremos usando a Demo 4. A integração foi feita com Paper.js e Simplex Noise.

Veja a demo

A chamada para o cursor

<body class="tutorial">
<main class="page">
<div class="page__inner">

<!-- elementos do cursor -->
<div class="cursor cursor--small"></div>
<canvas class="cursor cursor--canvas" resize></canvas>

</div>
</main>
</body>

Personalizando o layout

body.tutorial {
--color-text: #F4F0E7;
--color-bg: #1E2D40;
--color-link: #FF196A;
background-color: var(--color-bg);
}
.page {
position: absolute;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.page__inner {
display: flex;
justify-content: center;
width: 100%;
}

O estilo do cursor

.cursor {
position: fixed;
left: 0;
top: 0;
pointer-events: none;
}
.cursor--small {
width: 5px;
height: 5px;
left: -2.5px;
top: -2.5px;
border-radius: 50%;
z-index: 11000;
background: var(--color-text);
}
.cursor--canvas {
width: 100vw;
height: 100vh;
z-index: 12000;
}

Os links

<nav class="nav">
<a href="#" class="link">
<svg class="settings-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g class="settings-icon__group settings-icon__group--1">
<line class="settings-icon__line" x1="79.69" y1="16.2" x2="79.69" y2="83.8"/>
<rect class="settings-icon__rect" x="73.59" y="31.88" width="12.19" height="12.19"/>
</g>
<g class="settings-icon__group settings-icon__group--2">
<line class="settings-icon__line" x1="50.41" y1="16.2" x2="50.41" y2="83.8"/>
<rect class="settings-icon__rect" x="44.31" y="54.33" width="12.19" height="12.19"/>
</g>
<g class="settings-icon__group settings-icon__group--3">
<line class="settings-icon__line" x1="20.31" y1="16.2" x2="20.31" y2="83.8"/>
<rect class="settings-icon__rect" x="14.22" y="26.97" width="12.19" height="12.19"/>
</g>
</svg>
</a>
<!-- você pode adicionar mais links aqui -->
</nav>

Estilo dos links e seus efeitos de transição

.nav {
display: flex;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.link {
display: flex;
width: 75px;
height: 75px;
margin: 0 5px;
justify-content: center;
align-items: center;
}
.settings-icon {
display: block;
width: 40px;
height: 40px;
}
.settings-icon__line {
stroke: var(--color-text);
stroke-width: 5px;
transition: all 0.2s ease 0.05s;
}
.settings-icon__rect {
stroke: var(--color-text);
fill: var(--color-bg);
stroke-width: 5px;
transition: all 0.2s ease 0.05s;
}
.link:hover .settings-icon__line,
.link:hover .settings-icon__rect {
stroke: var(--color-link);
transition: all 0.2s ease 0.05s;
}
.link:hover .settings-icon__group--1 .settings-icon__rect {
transform: translateY(20px);
}
.link:hover .settings-icon__group--2 .settings-icon__rect {
transform: translateY(-20px);
}
.link:hover .settings-icon__group--3 .settings-icon__rect {
transform: translateY(25px);
}
Esse é o resultado que teremos até agora.

Adicionando o Paper e o SimplexNoise

<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.12.0/paper-core.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/simplex-noise/2.4.0/simplex-noise.min.js"></script>

Ocultando o cursor padrão

.page, .page a {
cursor: none;
}

Animando o cursor

let clientX = -100;
let clientY = -100;
const innerCursor = document.querySelector(".cursor--small");
const initCursor = () => {
document.addEventListener("mousemove", e => {
clientX = e.clientX;
clientY = e.clientY;
});
const render = () => {
innerCursor.style.transform = `translate(${clientX}px, ${clientY}px)`;
// se você já estiver usando o TweenMax no seu projeto,
// é preciso colocar TweenMax.set()
// TweenMax.set(innerCursor, {
// x: clientX,
// y: clientY
// });

requestAnimationFrame(render);
};
requestAnimationFrame(render);
};
initCursor();

Configurando a borda no Canvas

let lastX = 0;
let lastY = 0;
let isStuck = false;
let showCursor = false;
let group, stuckX, stuckY, fillOuterCursor;
const initCanvas = () => {
const canvas = document.querySelector(".cursor--canvas");
const shapeBounds = {
width: 75,
height: 75
};
paper.setup(canvas);
const strokeColor = "rgba(255, 25, 106)";
const strokeWidth = 1;
const segments = 8;
const radius = 15;
// nós iremos precisar disso para animar a borda
const noiseScale = 150; // velocidade
const noiseRange = 4; // campo de distorção
let isNoisy = false; // comando
// elemento que irá determinar a forma da borda
const polygon = new paper.Path.RegularPolygon(
new paper.Point(0, 0),
segments,
radius
);
polygon.strokeColor = strokeColor;
polygon.strokeWidth = strokeWidth;
polygon.smooth();
group = new paper.Group([polygon]);
group.applyMatrix = false;
const noiseObjects = polygon.segments.map(() => new SimplexNoise());
let bigCoordinates = [];
const lerp = (a, b, n) => {
return (1 - n) * a + n * b;
};
const map = (value, in_min, in_max, out_min, out_max) => {
return (
((value - in_min) * (out_max - out_min)) / (in_max - in_min) + out_min
);
};
paper.view.onFrame = event => {
// usando a interpolação linear, o círculo se moverá 0,2 (20%)
// da distância entre sua posição atual com a do mouse
// as coordenadas são definidas por frame
lastX = lerp(lastX, clientX, 0.2);
lastY = lerp(lastY, clientY, 0.2);
group.position = new paper.Point(lastX, lastY);
}
}
initCanvas();
Se você fez tudo certo, verá que agora o cursor estará se movendo da maneira que configuramos.

Configurando o estado hover

const initHovers = () => {
// vá até ao centro na parte do link e coloque stuckX e stuckY
// isso é necessário pra definir a posição da animação na borda
const handleMouseEnter = e => {
const navItem = e.currentTarget;
const navItemBox = navItem.getBoundingClientRect();
stuckX = Math.round(navItemBox.left + navItemBox.width / 2);
stuckY = Math.round(navItemBox.top + navItemBox.height / 2);
isStuck = true;
};
// resetar o isStuck quando o mouse sair de cima
const handleMouseLeave = () => {
isStuck = false;
};
const linkItems = document.querySelectorAll(".link");
linkItems.forEach(item => {
item.addEventListener("mouseenter", handleMouseEnter);
item.addEventListener("mouseleave", handleMouseLeave);
});
};
initHovers();

Animando a borda

// loop da animação do Paper.js
// (60fps por frame com o requestAnimationFrame)
paper.view.onFrame = event => {
// usando a interpolação linear, o círculo se moverá 0,2 (20%)
// da distância entre sua posição atual com a do mouse
// as coordenadas são definidas por frame
if (!isStuck) {
// mover a borda normalmente
lastX = lerp(lastX, clientX, 0.2);
lastY = lerp(lastY, clientY, 0.2);
group.position = new paper.Point(lastX, lastY);
} else if (isStuck) {
// fixar a posição da nav
lastX = lerp(lastX, stuckX, 0.2);
lastY = lerp(lastY, stuckY, 0.2);
group.position = new paper.Point(lastX, lastY);
}

if (isStuck && polygon.bounds.width < shapeBounds.width) { // aumentar o tamanho polygon.scale(1.08); } else if (!isStuck && polygon.bounds.width > 30) {
// remover animação da borda
if (isNoisy) {
polygon.segments.forEach((segment, i) => {
segment.point.set(bigCoordinates[i][0], bigCoordinates[i][1]);
});
isNoisy = false;
bigCoordinates = [];
}
// personalizar a forma
const scaleDown = 0.92;
polygon.scale(scaleDown);
}

// quando estiver fixa, animar com simplex noise
if (isStuck && polygon.bounds.width >= shapeBounds.width) {
isNoisy = true;
// pegar as coordenadas da borda maior
if (bigCoordinates.length === 0) {
polygon.segments.forEach((segment, i) => {
bigCoordinates[i] = [segment.point.x, segment.point.y];
});
}

// criar loop em todos os pontos do polígono
polygon.segments.forEach((segment, i) => {

// conseguir novos efeitos na animação
// iremos dividir o event.count com o noiseScale pra ter um efeito bem suave
const noiseX = noiseObjects[i].noise2D(event.count / noiseScale, 0);
const noiseY = noiseObjects[i].noise2D(event.count / noiseScale, 1);

// mapear o valor da borda para o range
const distortionX = map(noiseX, -1, 1, -noiseRange, noiseRange);
const distortionY = map(noiseY, -1, 1, -noiseRange, noiseRange);

// aplicar distorção nas coordenadas
const newX = bigCoordinates[i][0] + distortionX;
const newY = bigCoordinates[i][1] + distortionY;

// definir uma nova coordenada (animada) no pontinho
segment.point.set(newX, newY);
});

}
polygon.smooth();
};
Temos então esse resultado supimpa.

Considerações finais

Ah, se você gostou desse tutorial, agradeça ao Stefan no twitter, o projeto original (em inglês) foi disponibilizado por ele.

E aí dev, deu certo? Gostou? Deixa um feedback nos comentários. 😀

Clique aqui para baixar os arquivos usados neste tutorial.

Após anos de experiência em criação web, resolvi compartilhar dicas sobre a carreira em uma agência de desenvolvimento. — https://studioromes.com

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store