Detalhes

Com que frequência você acessa 404 páginas? Geralmente, eles não têm estilo e permanecem padrão. Recentemente, encontrei test.do.am, que personagem interativo atrai a atenção e anima a página de erro.

Provavelmente, havia apenas uma foto de gato, então eles pensaram que o movimento dos olhos e o desenvolvedor implementaram a idéia. imagem Agora, o usuário visita a página e verifica o efeito. É um recurso pequeno e agradável, que captura, e o usuário o discute com colegas ou amigos e até repete o recurso. Poderia ser assim tão fácil, se não:

  1. O ponto central não está sendo renovado quando o usuário redimensiona a janela. Abra a janela do navegador com uma pequena janela de visualização de largura e redimensione para tela cheia, o gato não olha para o cursor.
  2. O ponto central é colocado no olho esquerdo, não no centro binocular do círculo.
  3. Quando o usuário passa o cursor entre os olhos, as maçãs dos olhos não se juntam e não se concentram. Os olhos estão olhando para o infinito, é por isso que o gato não olha para o usuário, olha através dele.
  4. Os movimentos dos olhos são imediatos, eles precisam ser suaves.
  5. Os movimentos das maçãs acontecem por causa da alteração da margem esquerda / margem superior. Está incorreto, encontre a explicação abaixo.
  6. Os olhos não se movem se o cursor estiver no rodapé.

O que eu sugiro

Para começar, vamos implementar o movimento impecável dos olhos.

1. Prepare a marcação

<div class="cat"> <div class="cat__eye _left"></div> <div class="cat__eye _right"></div> </div> 

2. Obtenha links para os elementos dos olhos

 const cat = document.querySelector('.cat'); const eyes = cat.querySelectorAll('.cat__eye'); const eye_left = eyes[0]; const eye_right = eyes[1]; 

3. Registre o ouvinte de eventos do mousemove e obtenha as coordenadas do cursor:

 let mouseX; let mouseY; window.addEventListener('mousemove', e => { mouseX = e.clientX; mouseY = e.clientY; }) 

Eu adiciono o ouvinte mousemove no objeto da janela, não no corpo do documento, porque preciso usar toda a tela para obter as coordenadas do mouse.

4. Movimento
Como vou suavizar os movimentos, não posso gerenciá-los no manipulador de remoção de rato.

Adicione o método de atualização que será buscado pelo requestAnimationFrame que é sincronizado com a renovação do navegador. Normalmente, as renovações acontecem 60 vezes por segundo; portanto, vemos 60 fotos por segundo a cada 16,6 ms.

Se o desenvolvedor supor que o navegador do usuário não pode suportar requestAnimationFrame, o desenvolvedor poderá usar o fallback setTimeout ou o polyfill pronto

 window.requestAnimationFrame = (function () { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function (callback) { window.setTimeout(callback, 1000 / 60); }; })(); 

Para renovar ou buscar a atualização de forma estável a tempo, registro a variável iniciada

 let started = false; let mouseX; let mouseY; window.addEventListener('mousemove', e => { mouseX = e.clientX; mouseY = e.clientY; if(!started){ started = true; update(); } }) function update(){ // Here comes eyes movement magic requestAnimationFrame(update); } 

Dessa forma, eu constantemente buscava o método de atualização e as coordenadas do cursor. Então eu preciso obter valores dos movimentos das maçãs dentro dos olhos.

Eu tento mover os dois olhos como elemento único

 let dx = mouseX - eyesCenterX; let dy = mouseY - eyesCenterY; let angle = Math.atan2(dy, dx); let distance = Math.sqrt(dx * dx + dy * dy); distance = distance > EYES_RADIUS ? EYES_RADIUS : distance; let x = Math.cos(angle) * distance; let y = Math.sin(angle) * distance; eye_left.style.transform = 'translate(' + x + 'px,' + y + 'px)'; eye_right.style.transform = 'translate(' + x + 'px,' + y + 'px)'; 

Bem simples: encontre dx e dy, que são a diferença de coordenadas entre o centro dos olhos e o mouse, encontre o ângulo do centro para o cursor, usando os métodos Math.cos e Math.sin para obter o valor do movimento para horizontal e vertical. Use o operador ternário e limite a área de movimento dos olhos.


O valor Y é fornecido primeiro para o método Math.atan2, depois o valor x. Como resultado, o usuário percebe falta de naturalidade dos movimentos dos olhos e não focaliza.

Faça com que cada um se mova e observe sem referência um ao outro.

 // left eye let left_dx = mouseX - eyesCenterX + 48; let left_dy = mouseY - eyesCenterY; let left_angle = Math.atan2(left_dy, left_dx); let left_distance = Math.sqrt(left_dx * left_dx + left_dy * left_dy); left_distance = left_distance > EYES_RADIUS ? EYES_RADIUS : left_distance; let left_x = Math.cos(left_angle) * left_distance; let left_y = Math.sin(left_angle) * left_distance; eye_left.style.transform = 'translate(' + left_x + 'px,' + left_y + 'px)'; // right eye let right_dx = mouseX - eyesCenterX - 48; let right_dy = mouseY - eyesCenterY; let right_angle = Math.atan2(right_dy, right_dx); let right_distance = Math.sqrt(right_dx * right_dx + right_dy * right_dy); right_distance = right_distance > EYES_RADIUS ? EYES_RADIUS : right_distance; let right_x = Math.cos(right_angle) * right_distance; let right_y = Math.sin(right_angle) * right_distance; eye_right.style.transform = 'translate(' + right_x + 'px,' + right_y + 'px)'; 


Interessante, mas pior que o resultado anterior, os olhos se movem para cima e para baixo de forma independente. Então, eu usei a primeira demo como mecânica básica de movimento e fiz com que as maçãs dos olhos se juntassem quando o cursor estivesse sobre o centro do personagem.

Não vou descrever o código inteiro, encontre um resultado:


Por tentativa e erro, combinei os parâmetros necessários para o movimento e o foco dos olhos. Então agora eu preciso de suavização.

Suavização

Vincular a biblioteca TweenMax e codificar algo como isto?

 TweenMax.to( eye, 0.15, {x: x, y: y}); 

Vincular toda a biblioteca para tarefas simples não faz sentido; portanto, eu faço a suavização do zero.

Coloque o caso de que há apenas um elemento de olho na página e sua área de deslocamento não é limitada. Para suavizar os valores das coordenadas do mouse, eu uso esta mecânica:

 const SMOOTHING = 10; x += (needX - x) / SMOOTHING; y += (needY - y) / SMOOTHING; eye.style.transform = 'translate3d(' + x + 'px,' + y + 'px,0)'; 

Eu uso o translate3d para separar os olhos para outro fluxo de renderização e acelerá-los.

O truque é que cada 16,6ms (60 fotos por segundo) variável x e y tendem aos valores necessários. Cada renovação fecha o valor ao necessário por 1/10 da diferença.

 let x = 0; let needX = 100; let SMOOTHING = 2; function update(){ x += (needX - x) / SMOOTHING; console.log(x); } 

A cada 16,6 ms de renovação, obtemos uma suavização simples e os próximos valores de x (aprox.):

 50 75 87.5 93.75 96.875 98.4375 99.21875 99.609375 100 

Mais alguns truques não óbvios:

- Inicie este exame para otimizar a carga de trabalho

 if(x != needX || y != needY){ eye.style.transform = 'translate3d(' + x + 'px,' + y + 'px,0)'; } 

Mas você precisa equiparar x a needX quando chegarem tão perto quanto as posições dos olhos forem quase as mesmas

 if(Math.abs(x - needX) < 0.25){ x = needX; } if(Math.abs(y - needY) < 0.25){ y = needY; } 

Caso contrário, os valores xey atingirão needX e needY por muito tempo; não haverá diferenças visuais, mas cada alteração na tela afetará o estilo dos olhos. Btw você pode mexer com ele mesmo.

 let x = 0; let needX = 100; let smoothing = 2; function update(){ x += (needX - x) / smoothing; if( Math.abs(x - needX) > 0.25 ){ // replace 0.25 with anything else and check number of x renewals. window.requestAnimationFrame(update); } else { x = needX; } console.log( x.toString(10) ); } update(); 

- Se a mecânica acima estiver clara, você poderá criar efeitos mais complexos, por exemplo, primavera. A suavização mais simples e a aproximação do cursor são assim:

 x += (mouseX - x) / smoothing; y += (mouseY - y) / smoothing; 


Adicione suavização à diferença entre os valores das coordenadas necessárias e atuais.


Às vezes, a limitação de aproximação faz sentido. Há um exemplo acima, em que o valor muda de 0 a 100; portanto, no 1º valor da iteração atinge “50”, é um número bastante grande para 1 etapa. Essa mecânica lembra o paradoxo de Aquiles e a tartaruga


Piscando

Esconda e mostre maçãs dos olhos a cada 2-3 segundos. O método mais trivial é “display: none;”, “transform: scaleY (N)” com valor dinâmico da escala y é um pouco mais complexo.

Crie 2 consts

const BLINK_COUNTER_LIMIT = 180; - número de renovações antes do início do piscar,
const BLINKED_COUNTER_LIMIT = 6; - número de renovações durante uma piscada.

E 2 variáveis, cujos valores mudarão a cada renovação.

 let blinkCounter = 0; let blinkedCounter = 0; 

Código de piscamento

 let blinkTransform = ''; blinkCounter++; if(blinkCounter > BLINK_COUNTER_LIMIT){ blinkedCounter++ if(blinkedCounter > BLINKED_COUNTER_LIMIT){ blinkCounter = 0; } else { blinkTransform = ' scaleY(' + (blinkedCounter / BLINKED_COUNTER_LIMIT) + ')'; } } else { blinkedCounter = 0; } 

BlinkTransform é uma variável de traçado que possui valor vazio entre o piscamento e os seguintes durante o piscamento

 ' scaleY(0.17)' ' scaleY(0.33)' ' scaleY(0.50)' ' scaleY(0.67)' ' scaleY(0.83)' ' scaleY(1.00)' 

Todos os cálculos fornecem transformação de piscada variável, cujo valor deve ser adicionado à transformação de posição do código css dos olhos. Assim, uma string vazia é adicionada em caso de 3s de inatividade e não afeta a escala dos olhos, o valor de css é adicionado durante o piscar.

 eye_left.style.transform = 'translate(' + xLeft + 'px,' + y + 'px)' + blinkTransform; eye_right.style.transform = 'translate(' + xRight + 'px,' + y + 'px)' + blinkTransform; 

Lição da história

Todos os dias encontramos coisas que parecem simples e óbvias e nem entendemos que essa simplicidade externa oculte uma quantidade colossal de perguntas e melhorias. Na minha opinião, o diabo está nos detalhes que formam todo o resultado final. Muhammad Ali, o melhor boxeador do século 20, levantou o calcanhar do pé traseiro no momento do soco direto. Essa manobra aumentou a distância efetiva do golpe e deu a ele mais chances de vencer. Sempre funcionou.

PS: Eu não tenho nenhuma influência no site e espero que seus proprietários não se ofendam com meus comentários. Por conveniência, chamei de maçã do olho = olho no código.

Source: https://habr.com/ru/post/pt443720/


All Articles