Na terceira parte, implemento a interação com JS.
Seções do JS
Separei o arquivo JS em seis seções:
- State: objeto que guarda as posições do cursor
- Elements: elementos HTML que serão manipulados
- Functions: funções usadas para manipular os elementos HTML e lidar com as posições do cursor
- Rendering: função que renderiza o texto tridimensional
- Handlers: funções que lidam com eventos do
window
- Events: define funções de callback para os eventos do
window
State
Objeto usado para guardar a posição atual e a posição alvo do cursor. A posição alvo é a posição real do cursor, enquanto a posição atual é a posição com inércia. Isso cria uma movimentação suave ao mover o cursor. A inércia é calculada pela função getPositionWithInertia
.
/* ======================================== State======================================== */const state = { current: { x: 0, y: 0 }, target: { x: 0, y: 0 },};
Elements
O elemento container
tem a propriedade transform
manipulada, enquanto o elemento text
tem a propriedade text-shadow
manipulada.
/* ======================================== Elements======================================== */const container = document.querySelector(".container");const text = document.querySelector(".text");
Functions
getTextShadowValue
A função getTextShadowValue
gera o valor para a propriedade text-shadow
do elemento div.text
, o valor é baseado na posição do cursor.
A função recebe os parâmetros xPercentage
e yPercentage
, onde xPercentage
varia de -100% (esquerda) a 100% (direita), e yPercentage
varia de -100% (fundo) a 100% (topo).
A porcentagem diz respeito à posição do cursor. Por exemplo, se o cursor está no centro do viewport, ambas as porcentagens são 0%. Se o cursor está no topo esquerdo do viewport, xPercentage
é -100% e yPercentage
é 100%.
A função cria 50 sombras, onde a última sombra se posiciona no máximo até 2vw
(2 unidades de largura do viewport).
getTransformValue
A função getTransformValue
gera o valor para a propriedade transform
do elemento div.container
, o valor é baseado na posição do cursor.
Assim como a função getTextShadowValue
, a função getTransformValue
recebe os parâmetros xPercentage
e yPercentage
.
A função cria uma rotação de no máximo 20 graus.
getPositionWithInertia
A função getPositionWithInertia
gera o valor para state.current.x
e state.current.y
, recebendo os parâmetros current
e target
.
A função calcula a distância entre os dois pontos. Se a distância for muito pequena, a posição alvo é retornada. Do contrário, 10% da distância é somada à posição atual, criando uma movimentação suave.
updateTarget
A função updateTarget
mapeia a posição do cursor para state.target.x
e state.target.y
. A função é usada no evento mousemove
do window
.
/* ======================================== Functions======================================== */function getTextShadowValue(xPercentage, yPercentage) { const shadowAmount = 50; const maxVw = 2; let result = ""; for (let index = 0; index < shadowAmount; index++) { const isLast = index === shadowAmount - 1; const percentage = (index + 1) / shadowAmount; const xValue = maxVw * percentage * (xPercentage * -1); const yValue = maxVw * percentage * yPercentage; result += `${xValue}vw ` + `${yValue}vw ` + `var(--foreground-color)${isLast ? "" : ","}`; } return result;}function getTransformValue(xPercentage, yPercentage) { const maxDegrees = 20; const rotateXValue = yPercentage * maxDegrees; const rotateYValue = xPercentage * maxDegrees; return `rotateX(${rotateXValue}deg) rotateY(${rotateYValue}deg)`;}function getPositionWithInertia(current, target) { const distance = target - current; if (Math.abs(distance) < 0.01) { return target; } return current + distance * 0.1;}function updateTarget(event) { state.target.x = event.x; state.target.y = event.y;}
Rendering
A função render
é executada em todo quadro de animação usando requestAnimationFrame
, que sincroniza a renderização com a taxa de atualização da tela. Sendo os destaques:
- Linha 6: a função faz uma nova renderização apenas quando a posição atual do cursor é diferente da posição alvo
- Linhas 10, 11, 22 e 23: usa as funções
getPositionWithInertia
,getTransformValue
egetTextShadowValue
para gerar os novos valores das propriedades dos elementosdiv.container
ediv.text
- Linhas 26 e 27: atualiza a posição atual do cursor
- Linha 31: solicita um quadro de animação chamando a si mesma, criando um loop infinito de renderização
/* ======================================== Rendering======================================== */function render() { if ( state.current.x !== state.target.x || state.current.y !== state.target.y ) { const x = getPositionWithInertia(state.current.x, state.target.x); const y = getPositionWithInertia(state.current.y, state.target.y); const halfWidth = window.innerWidth * 0.5; const halfHeight = window.innerHeight * 0.5; // left is negative, right is posivite const xPercentage = (x - halfWidth) / halfWidth; // bottom is negative, top is posivite const yPercentage = ((y - halfHeight) / halfHeight) * -1; // update dom container.style.transform = getTransformValue(xPercentage, yPercentage); text.style.textShadow = getTextShadowValue(xPercentage, yPercentage); // update state state.current.x = x; state.current.y = y; } // render loop requestAnimationFrame(render);}
Atualização apenas da propriedade transform
Atualização apenas da propriedade text-shadow
Atualização das propriedades transform
e text-shadow
juntas
Handlers e Events
Nesta seção os callbacks para os eventos mousemove
e load
do window
são definidos. No evento mousemove
, a posição alvo do cursor é atualizada, e no evento load
, a renderização é iniciada.
/* ======================================== Handlers======================================== */function handleMouseMove(event) { updateTarget(event);}function handleLoad(event) { render();}/* ======================================== Events======================================== */window.addEventListener("mousemove", handleMouseMove);window.addEventListener("load", handleLoad);
Inércia: comparação
A inércia traz suavidade ao movimento.
Sem inércia
Com inércia
Conclusão
Tendo o JS definido, dei o projeto como concluído.
Na conclusão, escrevo meus pensamentos finais: Conclusão DO IT: texto 3D com CSS e JS.