Opens in a new tab

Bufnita — personajul care se uita dupa mouse

Pagina live: /bufnita/. Nu e un port si nu e un fisier cumparat: personajul e desenat direct in SVG, aici, in pagina. Nu se incarca nicio librarie — doar GSAP, care e oricum global pe site.

Ce face

  • Bufnita sta pe loc si se uita dupa cursor. Trei piese se misca, in cantitati diferite, si tocmai diferenta dintre ele e ce face gestul sa para o privire si nu o alunecare:
    • pupilele merg cel mai mult, in interiorul ochiului
    • fata aluneca peste craniu si se comprima — asta e capul care se intoarce
    • craniul se misca putin, cat sa aiba adancime
    • corpul se apleaca cel mai putin, pivotand la picioare
  • Capul se intoarce in ax, nu se inclina. Prima varianta rotea tot capul si arata ca si cum bufnita si-ar culca capul pe umar — alt gest, gresit. Solutia: capul e taiat in doua grupuri. Craniul (cercul negru + smocurile) sta aproape pe loc, iar fata (discul facial, ochii, ciocul) aluneca lateral si isi reduce latimea pana la 91%, exact ce face partea din fata a unei sfere cand se roteste.
  • Pupilele sunt limitate la o elipsa, nu la un dreptunghi. Cu limita patrata ar ajunge mai departe pe diagonala decat lateral, iar ochii arata stramb in colturi. Vectorul se normalizeaza la cercul unitate inainte de a fi inmultit cu raza.
  • Clipeste la 3–7 secunde, aleator, prin turtirea grupului ochiului. Fara asta pare inghetata.
  • Cand nu ai mouse — pe telefon, sau pur si simplu inainte sa-l misti — se uita singura in jur, pe doua sinusoide lente. Aceeasi solutie ca la Albina, fara nicio detectie de dispozitiv.

Cum e desenata

Un singur <svg> cu viewBox="0 0 400 540", scris de mana. Structura conteaza, pentru ca JS-ul gaseste piesele dupa nume:

ow-body        <- se apleaca, pivot la picioare
  ow-skull     <- cercul negru + smocurile
  ow-face      <- disc facial + ochi + cioc
    ow-eye-l / ow-eye-r      <- se turtesc la clipit
      ow-pupil-l / ow-pupil-r  <- iris + pupila + licarire
ow-feet        <- INAFARA lui ow-body: bufnita se apleaca, labele stau

Ochiul e construit in trei cercuri concentrice: alb r=50, iris r=34, pupila r=24, plus o licarire alba decentrata. Grupul iris+pupila+licarire e cel care se misca, nu doar pupila — asa cum face un ochi adevarat. Diferenta dintre alb si iris, 16px, e tot bugetul de miscare; constantele folosite sunt 15 pe orizontala si 12 pe verticala, deci irisul nu atinge niciodata marginea.

Culori

Patru culori, toate deja existente pe site — nu s-a inventat nicio paleta:

Culoare Unde De unde vine
#fff318 fundalul paginii paginile cu albina
#111111 craniu, corp, pupile negrul paginilor
#ffffff disc facial, burta, albul ochiului idem
#FECB00 iris, cioc, labe galbenul din albina Lottie

Reglaje

Patru numere sus in JS schimba caracterul:

Constanta Acum Ce face
FACE_X 22px cat de mult se intoarce capul
FACE_SQUASH_X 0.10 cat se comprima fata la intoarcere completa
PUPIL_RX / PUPIL_RY 15 / 12 cat merg pupilele
EASE 0.12 cat de repede se uita dupa tine

Capcane

  • Capcana zilei: gsap.ticker.add() nu porneste un ticker adormit. La prima publicare pagina era complet moarta si nimic nu semnala de ce: elementele existau, gsap.set() mergea perfect, consola era goala, dar gsap.ticker.frame ramanea 0. GSAP isi adoarme ticker-ul cand nu are nimic de animat, iar aici nu exista niciun tween care sa-l trezeasca — spre deosebire de Albina, unde lottie pornea oricum animatii. Fixul e o linie, gsap.ticker.wake(), la finalul initializarii. Daca o animatie pe gsap.ticker nu porneste, verifica intai gsap.ticker.frame: daca e 0, asta e.
  • Interpolarea pe cadru depinde de monitor. x += (tinta - x) * 0.12 merge de doua ori mai repede la 120Hz. Toate cele patru urmariri trec prin approach(), corectat cu 1 - Math.pow(1 - ease, dt * 60).
  • Ancora nu se citeste din pozitia ochilor. Ochii se misca; o ancora care se misca odata cu ei se auto-alimenteaza si bufnita incepe sa tremure singura. Ancora se calculeaza din chenarul <svg>-ului plus coordonatele ochilor in unitati viewBox — un punct fix.
  • Matricea citita din getComputedStyle nu iti da inapoi valorile pe care le-ai scris. Cand folosesti svgOrigin, GSAP coace deplasarea fata de origine in componentele de translatie. Vei citi 34.8 acolo unde ai scris 22. Nu e o eroare — verifica rotatia si scara, care sunt curate, si lasa translatia in pace.
  • Personajul din referinta initiala era Jerry (Warner Bros./MGM). De aceea bufnita e desenata de la zero: un personaj protejat n-ar fi putut fi refolosit nicaieri.

Pasi de reproducere

  1. Deseneaza personajul in SVG cu piesele mobile in grupuri numite. Regula: orice se misca separat are nevoie de propriul id. Labele stau inafara grupului care se apleaca.
  2. Ochiul: trei cercuri concentrice, iar grupul iris+pupila e cel care se misca. Bugetul de deplasare e raza albului minus raza irisului — alege constantele sub aceasta valoare.
  3. Calculeaza ancora din chenarul svg-ului plus coordonatele ochilor in unitati viewBox. Recalculeaz-o la resize, niciodata din pozitia curenta a ochilor.
  4. Normalizeaza vectorul spre cursor la cercul unitate, apoi inmulteste-l cu razele. Interpoleaza cu o functie corectata de rata de cadre.
  5. Capul: nu roti. Aluneca fata peste craniu si redu-i scaleX. Rotatia e alt gest.
  6. Cheama gsap.ticker.wake() dupa ce ai inregistrat callback-ul.
  7. Verifica cu masuratori, nu din ochi: pozitia pupilei in cele patru colturi (simetrica si sub limita), rotation zero pe fata in toate pozitiile, gsap.ticker.frame > 0 imediat dupa incarcare.