Détails

À quelle fréquence obtenez-vous 404 pages? Habituellement, ils ne sont pas stylisés et restent par défaut. Récemment, j'ai trouvé test.do.am quel personnage interactif attire l'attention et anime la page d'erreur.

Probablement, il n'y avait qu'une photo de chat, puis ils ont pensé au mouvement des yeux et le développeur a mis en œuvre l'idée. image Maintenant, l'utilisateur visite la page et vérifie l'effet. C'est une petite fonctionnalité cool et agréable, elle attrape, puis l'utilisateur en discute avec des collègues ou des amis et répète même la fonctionnalité. Cela pourrait être aussi simple, sinon:

  1. Le point central n'est pas renouvelé lorsque l'utilisateur redimensionne la fenêtre. Ouvrez la fenêtre du navigateur avec une fenêtre de petite largeur et redimensionnez en plein écran, le chat ne regarde pas le curseur.
  2. Le point central est placé sur l'œil gauche, pas au centre binoculaire du cercle.
  3. Lorsque l'utilisateur passe le curseur entre les yeux, les pommes des yeux ne se rejoignent pas et ne se concentrent pas. Les yeux sont tournés vers l'infini, c'est pourquoi le chat ne regarde pas l'utilisateur, il regarde à travers lui.
  4. Les mouvements des yeux sont immédiats, ils doivent être lisses.
  5. Les mouvements des pommes se produisent en raison du changement de marge gauche / marge supérieure. C'est incorrect, trouvez l'explication ci-dessous.
  6. Les yeux ne bougent pas si le curseur est sous le pied de page.

Ce que je propose

Pour commencer, implémentons un mouvement des yeux sans faille.

1. Préparez le balisage

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

2. Obtenez des liens vers les éléments des yeux

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

3. Enregistrez l'écouteur d'événement mousemove et obtenez les coordonnées du curseur:

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

J'ajoute l'écouteur mousemove sur l'objet fenêtre, pas sur le corps du document, car j'ai besoin d'utiliser tout l'écran pour obtenir les coordonnées de la souris.

4. Mouvement
Comme je vais lisser les mouvements, je ne peux pas les gérer dans le gestionnaire de déplacement de souris.

Ajoutez une méthode de mise à jour qui sera récupérée par requestAnimationFrame qui est synchronisée avec le renouvellement du navigateur. Habituellement, les renouvellements se produisent 60 fois par seconde, donc nous voyons 60 photos par seconde toutes les 16,6 ms.

Si le développeur suppose que le navigateur de l'utilisateur ne peut pas prendre en charge requestAnimationFrame, le développeur peut utiliser le repli setTimeout ou un polyfill prêt à l'emploi

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

Afin de renouveler ou de récupérer la mise à jour dans le temps, j'enregistre la variable démarrée

 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); } 

De cette façon, je récupérais constamment la méthode de mise à jour et les coordonnées du curseur. Ensuite, j'ai besoin d'obtenir des valeurs de mouvements de pommes dans les yeux.

J'essaye de bouger les deux yeux comme un seul élément

 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)'; 

Assez simple: trouver dx et dy, qui sont la différence de coordonnées entre le centre des yeux et la souris, trouver l'angle du centre au curseur, en utilisant les méthodes Math.cos et Math.sin, obtenez la valeur de mouvement pour l'horizontale et la verticale. Utilisez un opérateur ternaire et limitez la zone de mouvement des yeux.


La valeur Y est donnée en premier pour la méthode Math.atan2, puis la valeur x. En conséquence, l'utilisateur remarque des mouvements des yeux non naturels et aucune mise au point.

Faites bouger et regarder chaque œil sans référence l'un à l'autre.

 // 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)'; 


Intéressant mais pire que le résultat précédent, les yeux montent et descendent indépendamment. J'ai donc utilisé la première démo comme un mécanisme de base pour faire du mouvement et faire se rapprocher les pommes des yeux lorsque le curseur se trouve au centre du personnage.

Je ne décrirai pas tout le code, veuillez trouver ci-dessous un résultat:


Par essais et erreurs, j'ai fait correspondre les paramètres nécessaires pour le mouvement des yeux et la mise au point. Alors maintenant, j'ai besoin de lissage.

Lissage

Lier la bibliothèque TweenMax et coder quelque chose comme ça?

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

Lier une bibliothèque entière pour une tâche simple n'a pas de sens, donc je fais du lissage à partir de zéro.

Mettez le cas qu'il n'y a qu'un seul élément d'oeil sur la page et sa zone de déplacement n'est pas du tout limitée. Pour lisser les coordonnées des souris, j'utilise cette mécanique:

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

J'utilise translate3d pour séparer les yeux d'un autre flux de rendu et les accélérer.

L'astuce est que toutes les 16,6 ms (60 photos par seconde) les variables x et y ont tendance à prendre les valeurs nécessaires. Chaque renouvellement ferme la valeur à sa valeur nécessaire pour 1/10 de différence.

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

Ensuite, tous les 16,6 ms renouvelés, nous obtenons un lissage simple et les valeurs x suivantes (environ):

 50 75 87.5 93.75 96.875 98.4375 99.21875 99.609375 100 

Quelques astuces plus évidentes:

- Commencez cet examen pour optimiser la charge de travail

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

Mais vous devez assimiler x à needX quand ils sont aussi proches que les yeux sont presque les mêmes

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

Sinon, les valeurs x et y atteindront needX et needY trop longtemps; il n'y aura pas de différences visuelles, mais chaque changement d'écran affectera le style des yeux. Btw vous pouvez jouer avec vous-même.

 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(); 

- Si la mécanique ci-dessus est claire, vous pouvez créer des effets plus complexes, par exemple le ressort. Le lissage et l'approximation du curseur les plus simples ressemblent à ceci:

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


Ajoutez un lissage de la différence entre les valeurs de coordonnées nécessaires et actuelles.


Parfois, la limitation d'approximation est logique. Il y a un exemple ci-dessus où la valeur passe de 0 à 100, donc dans la 1ère valeur d'itération atteint "50", c'est un chiffre assez énorme pour 1 étape. Cette mécanique rappelle un peu le paradoxe d'Achille et de la tortue


Un clin d'oeil

Cachez et montrez les pommes des yeux toutes les 2-3 secondes. La méthode la plus banale est «display: none;», «transform: scaleY (N)» avec une valeur dynamique d'échelle y est un peu plus complexe.

Créer 2 consts

const BLINK_COUNTER_LIMIT = 180; - nombre de renouvellements avant le début du clignotement,
const BLINKED_COUNTER_LIMIT = 6; - nombre de renouvellements en un clin d'œil.

Et 2 variables, dont les valeurs changeront à chaque renouvellement.

 let blinkCounter = 0; let blinkedCounter = 0; 

Code de clignotement

 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 est une variable de trait qui a une valeur vide entre le clignotement et les suivants pendant le clignotement

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

Tous les calculs donnent une transformation de clignotement variable, dont la valeur doit être ajoutée au code CSS de la transformation de position des yeux. Ainsi, une chaîne vide est ajoutée en cas de temps d'arrêt de 3 secondes et cela n'affecte pas l'échelle des yeux, la valeur CSS est ajoutée pendant le clignotement.

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

Leçon de l'histoire

Chaque jour, nous rencontrons des choses qui semblent simples et évidentes et nous ne comprenons même pas que cette simplicité externe cache une quantité colossale de questions et d'améliorations. À mon avis, le diable est dans les détails qui forment l'ensemble du résultat final. Muhammad Ali le meilleur boxeur du 20e siècle a soulevé le talon du pied arrière au moment du coup de poing droit. Cette manœuvre a augmenté la distance de coup efficace et lui a donné plus de chances de gagner. Cela a toujours fonctionné.

PS Je n'ai aucune incidence sur le site Web et j'espère que ses propriétaires ne s'en voudront pas à mes commentaires. Pour plus de commodité, j'ai nommé pomme de l'œil = œil dans le code.

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


All Articles