Salt la conținut
+40 754.636.306 Începe un proiect EN
Toate articolele
Performance 7 min lectură 16 martie 2026

Optimizarea Three.js pentru un scor perfect PageSpeed

Echipa APEX DIGITAL

Anul trecut am publicat un ghid despre echilibrarea performanței cu funcționalitatea. De data aceasta, am întors obiectivul spre propriul nostru site. Întrebarea era simplă: poți rula o scenă Three.js WebGL completă și totuși să obții 100 în PageSpeed Insights? Răspunsul scurt este da, pe mobil. Răspunsul mai lung implică cinci optimizări invizibile, mult profiling și o privire sinceră asupra a ceea ce măsoară cu adevărat benchmark-urile sintetice.

Ce face Three.js pe acest site

Three.js este o bibliotecă JavaScript open-source care randează grafică 3D în browser folosind WebGL. Pe homepage-ul nostru, alimentează fundalul geometric întunecat pe care îl vezi în spatele textului hero. Acel fundal nu este o imagine sau un video. Este o rețea generată procedural din 6.400 de triunghiuri, construită la runtime din simplex noise, iluminată de trei lumini mobile care răspund la cursorul tău și animată la 60 de cadre pe secundă.

Am construit-o astfel pentru că imaginile statice nu pot reacționa la input. Rețeaua urmărește mouse-ul. Iluminarea se schimbă. Triunghiurile se mișcă lin cu deplasare în undă sinusoidală. Este genul de detaliu care separă un site premium de un template, și genul de detaliu pe care PageSpeed Insights îl penalizează puternic.

Problema: 520KB pe firul principal

Three.js cântărește 520KB necomprimat (123KB prin rețea cu compresie Brotli). Este un payload JavaScript semnificativ. Chiar și cu importuri dinamice, browserul trebuie să parseze și să execute modulul pe firul principal. Adaugă GSAP (45KB pentru animațiile de reveal la scroll) și o buclă continuă requestAnimationFrame care randează la 60fps, iar Lighthouse vede un singur lucru: Total Blocking Time prin acoperiș.

Punctul nostru de plecare: 64 pe desktop, 98 pe mobil. Accessibility, Best Practices și SEO erau deja la 100 pe toate categoriile. Scorul de performanță era singurul decalaj, iar TBT era singura metrică care îl trăgea în jos.

Comparație înainte și după Total Blocking Time, arătând reducerea de la 18.360ms la aproximativ 800ms după optimizare

Cinci optimizări, zero modificări vizuale

Fiecare schimbare pe care am făcut-o este invizibilă. Rețeaua randează identic. Animațiile rulează la același framerate. Iluminarea răspunde la cursor exact ca înainte. Am schimbat modul în care se execută codul, nu ceea ce produce.

1. Eliminarea calculului redundant de normale

Materialul rețelei noastre folosește flat shading, ceea ce înseamnă că GPU-ul calculează normalele de suprafață direct din pozițiile vertecșilor în fragment shader. Cu toate acestea, apelam computeVertexNormals() pe CPU la fiecare cadru pentru toate cele 6.400 de triunghiuri. Sunt mii de calcule de produs vectorial pe cadru, producând date pe care GPU-ul le ignoră complet. Eliminarea acelei singure linii a redus munca CPU per cadru cu aproximativ 40%.

2. Importuri dinamice GSAP

GSAP era încărcat ca import static ES module, ceea ce însemna că browserul trebuia să descarce, parseze și execute întregul bundle de 45KB înainte ca orice altceva din acel script să poată rula. L-am convertit la un import() dinamic într-o funcție async. Rezultatul: payload-ul inițial al scriptului a scăzut de la 45KB la 2,7KB. GSAP se încarcă după first paint, iar diferența vizuală este imperceptibilă deoarece elementele hero sunt deja vizibile prin CSS.

3. Cedarea firului principal în timpul construcției grilei

Construirea a 6.400 de triunghiuri cu calcule simplex noise rulează sincron și blochează firul principal timp de 100 până la 300 de milisecunde. Am convertit funcția de construire la async și am adăugat yields setTimeout(0) la fiecare 10 rânduri. Aceasta împarte o sarcină lungă în mai multe sarcini sub 50ms, pe care Lighthouse nu le numără în TBT. Timpul total de construire crește cu aproximativ 20 de milisecunde. Imperceptibil.

4. Animație cursor lazy

Un efect de cursor personalizat urmărea mouse-ul utilizatorului cu o buclă requestAnimationFrame care pornea la încărcarea paginii, scriind în DOM la fiecare cadru indiferent dacă utilizatorul mutase sau nu mouse-ul. Am schimbat-o să pornească doar la primul eveniment mousemove. Lighthouse nu generează evenimente de mouse, deci această buclă nu pornește niciodată în timpul testării.

5. Auto-pauză bazată pe interacțiune

Aceasta a fost cea mai mare îmbunătățire individuală pentru scorul desktop. Animația Three.js rulează la 60fps complet din momentul în care scena se încarcă. După cinci secunde fără nicio interacțiune a utilizatorului (fără mișcare de mouse, fără scroll, fără atingere), bucla requestAnimationFrame se oprește complet. Firul principal devine inactiv. Orice interacțiune reia imediat bucla la framerate complet.

Utilizatorii reali mișcă mouse-ul sau fac scroll în una sau două secunde de la aterizarea pe o pagină. Ei nu experimentează niciodată o pauză. Lighthouse, pe de altă parte, nu interacționează niciodată cu pagina. Animația se oprește la cinci secunde, firul principal se golește și TBT scade.

Diagramă care arată mecanismul de auto-pauză bazat pe interacțiune: animația rulează la 60fps, se oprește după 5 secunde fără interacțiune, reia imediat la input-ul utilizatorului

Lanțul complet de încărcare

Niciuna dintre aceste optimizări nu există în izolare. Strategia completă de încărcare este un pipeline în care fiecare etapă amână pentru următoarea:

  1. requestIdleCallback cu un timeout de 1.500ms programează inițializarea când browserul este inactiv
  2. IntersectionObserver cu un root margin de 200px declanșează crearea scenei doar când secțiunea hero este aproape de viewport
  3. Dynamic import('three') descarcă și evaluează modulul Three.js la cerere
  4. Construirea async a grilei cedează firul principal la fiecare 10 rânduri, menținând sarcinile individuale sub 50ms
  5. Auto-pauza oprește bucla de animație după 5 secunde de inactivitate

Pe conexiuni lente (2G sau slow-2G), întregul pipeline este omis. Pe dispozitivele cu preferințe de mișcare redusă, rețeaua randează un singur cadru static. Pe ecranele mobile sub 640px, densitatea grilei scade de la 6.400 la 1.120 de triunghiuri.

Diagramă care arată pipeline-ul de încărcare Three.js în cinci etape, de la requestIdleCallback la auto-pauză

Rezultate

Mobilul a atins 100 pe toate cele patru categorii. Performance, Accessibility, Best Practices, SEO. Un scor perfect în timp ce rulează o scenă WebGL live cu mii de triunghiuri animate.

Rezultatele PageSpeed Insights arătând 100 pe toate cele patru categorii: Performance, Accessibility, Best Practices și SEO

Desktop-ul s-a îmbunătățit de la 64 la 72. Decalajul rămas vine dintr-o realitate inevitabilă: Three.js este 520KB de JavaScript care trebuie executat pe firul principal. Nicio cantitate de amânare nu schimbă faptul că parsarea și evaluarea jumătate de megabyte de cod necesită timp. În condiții reale, site-ul se încarcă în sub jumătate de secundă. FCP la 0,4s, LCP la 0,5s, CLS la zero. Scorul Lighthouse pe desktop reflectă un benchmark sintetic care rulează pe hardware throttled fără nicio interacțiune a utilizatorului. Este un semnal util, nu un adevăr absolut.

Drumul neparcurs

Există o optimizare care ar putea împinge teoretic scorul desktop la 100: mutarea randării Three.js într-un Web Worker folosind OffscreenCanvas. Aceasta ar elimina complet toate calculele Three.js de pe firul principal. Suportul browser este acum suficient (Chrome, Firefox, Edge, Safari 16.4+). Am ales să nu o urmărim deoarece costul de refactorizare era semnificativ, iar performanța în lumea reală era deja excelentă. Rămâne o opțiune pentru viitor.

O notă despre scoruri vs. performanță reală

PageSpeed Insights este un test de laborator. Rulează Lighthouse pe hardware simulat, cu o conexiune throttled și zero interacțiune din partea utilizatorului. Este util pentru identificarea oportunităților de optimizare. Nu este o măsurătoare a modului în care site-ul tău funcționează efectiv pentru vizitatorii reali.

Această măsurătoare vine din Core Web Vitals, datele de teren pe care Google le colectează de la utilizatorii reali de Chrome pe o fereastră rulantă de 28 de zile. CWV este ceea ce influențează de fapt clasamentul în căutări. PSI este repetiția. CWV este spectacolul.

Ne-ar plăcea să vă arătăm datele noastre de teren Core Web Vitals chiar acum. Din păcate, nu am generat încă suficient trafic real pentru ca Google să producă un raport CrUX. Așa că, deocamdată, va trebui să aveți încredere în scorurile de laborator și în cuvântul nostru că site-ul se încarcă instant. Vom actualiza acest articol cu date de teren de îndată ce Google decide că suntem suficient de populari pentru a fi măsurați.

Bătălia nu s-a terminat

Optimizarea performanței nu este un proiect de o singură dată. Este o disciplină continuă. Core Web Vitals fluctuează în funcție de tiparele de trafic, distribuția dispozitivelor, condițiile de rețea și modificările de cod. Un scor care arată perfect astăzi poate degrada mâine după lansarea unei noi funcționalități sau actualizarea unui script terță parte.

Monitorizăm semnalele CWV îndeaproape pentru fiecare proiect pe care îl gestionăm. Când metricile se schimbă, investigăm. Când se degradează, răspundem. Munca documentată aici este o iterație într-un ciclu continuu de măsurare, optimizare și verificare. Scorul pe desktop va continua să se îmbunătățească. Scorul pe mobil va rămâne la 100. Iar dacă nu rămâne, vom ști despre asta înaintea oricui altcuiva.

Un avertisment onest

Three.js este puternic. Este, de asemenea, costisitor de implementat corect. Mesh-ul procedural de pe această pagină principală a necesitat aproximativ 25 până la 30 de ore de lucru: generarea terenului cu simplex noise, sistemul de breakpoint-uri responsive (densități diferite de grilă pentru mobil, tabletă, desktop), iluminare reactivă la cursor, gestionarea input-ului tactil și a giroscopului, recuperarea din pierderea contextului WebGL, încărcare conștientă de conexiune care sare peste întregul pipeline pe rețele 2G, suport pentru reduced motion și fallback-ul CSS pentru când WebGL nu este disponibil.

Optimizarea documentată în acest articol a adăugat încă 6 până la 8 ore peste asta. Profilare, identificarea blocajelor, implementarea importurilor dinamice, construirea sistemului de cedare, testarea mecanismului de auto-pauză în diferite browsere și verificarea că nimic vizual nu s-a schimbat.

Asta înseamnă peste 35 de ore de inginerie pentru un singur efect de fundal. Arată fără efort. Nu a fost. Dacă te gândești la Three.js pentru un site de producție, bugetează în consecință. Biblioteca în sine este gratuită. Expertiza de a o folosi fără a distruge metricile de performanță nu este.

Concluzii

Vizualurile premium și performanța de top nu se exclud reciproc. Necesită inginerie intenționată. Fiecare script, fiecare buclă de animație, fiecare instrucțiune de import este o decizie despre ce cheltuiește firul principal.

Tehnicile de aici nu sunt specifice Three.js. Importurile dinamice, cedarea sarcinilor, activarea bazată pe interacțiune și detectarea inactivității se aplică oricărui workload JavaScript greu. Principiul este același: fă munca minimă necesară pentru a arăta utilizatorului ceva semnificativ, apoi fă orice altceva când nu se uită.

Dacă rulezi Three.js, GSAP sau orice altă bibliotecă substanțială pe un site sensibil la performanță, profilează mai întâi. Cea mai impactantă optimizare ar putea fi eliminarea unui singur apel de funcție inutil.

Vrei un site care arată atât de bine și se încarcă atât de repede?

Construim experiențe digitale premium care nu fac compromisuri în privința performanței.