Een van de dingen die ons in verwarring brachten toen we onze nieuwe website bouwden, waren de morphed waves die je op verschillende plaatsen op de pagina's kunt zien. We hadden veel ideeën over hoe we ze op de juiste manier konden implementeren zonder al te veel moeite. De meeste oplossingen waren echter traag en we moesten vanaf nul iets opbouwen dat sneller zou zijn dan de al bestaande bibliotheken.
Oplossingsvoorstellen
We begonnen met een gewoon SVG-object dat werd geanimeerd met verschillende bibliotheken. Omdat we 3 objecten op één pagina wilden hebben, was het resultaat niet zo bevredigend. Alle animaties waren gewoon traag - alle paden van een enkel SVG-object moesten in zeer korte tijd worden bijgewerkt, waardoor de hele pagina zo traag werd als een slak. We moesten de oplossing met pure SVG ingevoegd in een document afwijzen. Toen hadden we nog twee andere oplossingen om uit te kiezen.
De video
element was de tweede optie. We begonnen met twee problemen:
- transparante achtergrond, die niet kan worden toegepast bij de populairste videoformaten zoals .mp4 of .webm,
- responsiviteit, wat een echt probleem was omdat video's als zodanig niet schaalbaar zijn.
We besloten deze oplossing op de lange baan te schuiven - "als we niets anders vinden, kiezen we deze".
De laatste optie was om canvas
met WebGL
rendering. Het was zo'n ongebruikelijke optie omdat we alle rendermechanismen zelf moesten ontwerpen. Dat kwam omdat de morfische golven die we hadden op maat gemaakt waren - dat dwong ons om een op maat gemaakte oplossing te ontwerpen. En dat was de optie die we wilden volgen en waar we ons echt op wilden richten.
Architectuur van oplossing
Laten we bij nul beginnen. Wat was het materiaal dat we moesten gebruiken om deze golven te bouwen? Het idee was dat alle golven een SVG-bestand ter grootte van 1×1 waren met specifieke paden rondom dit gebied. De animatie van deze SVG werd opgebouwd door een aantal vormen van toestanden in dit bestand. Alle animaties werden dus voorgesteld als een reeks bestanden die de stadia van het bewegen van een vorm bevatten.
Kijk eens dieper naar wat de toestanden zijn - alle paden zijn gewoon een soort matrix met specifieke waarden in een specifieke volgorde. Het veranderen van die waarden op specifieke posities binnen deze matrix verandert de vorm in zijn specifieke delen. We kunnen dit vereenvoudigen met het volgende voorbeeld:
toestand 1: [1, 50, 25, 40, 100]
toestand 2: [0, 75, -20, 5, 120]
toestand 3: [5, 0, -100, 80, 90]
We kunnen dus aannemen dat de vorm die we willen weergeven bestaat uit een array met 5 elementen die veranderen met de lineaire versoepeling in specifieke tijdsperioden. Als de animatie klaar is met de laatste fase, begint hij weer met de eerste om ons de indruk te geven van een oneindige animatie.
Maar... wacht - wat is de bovenstaande array precies? Zoals ik al zei, is het een pad dat verantwoordelijk is voor het weergeven van een specifieke vorm. Alle magie is opgenomen in de d
eigenschap van het SVG-pad. Deze eigenschap bevat een reeks "commando's" om een vorm te tekenen en elk commando heeft een soort argumenten. De bovenstaande array bestaat uit alle waarden (argumenten) die aan deze commando's zijn gekoppeld.
Het enige verschil tussen deze "statusbestanden" waren de waarden van specifieke commando's, omdat de volgorde van de commando's hetzelfde was. Alle magie bestond dus uit het verkrijgen van alle waarden en het animeren ervan.
De wizard heet Natuurkunde
In de paragraaf hierboven zei ik dat de enige magie bij het animeren van een object het maken van overgangen tussen alle stadia van een vorm is. De vraag is - hoe doe je dat met canvas?
De functie die iedereen die met canvas
moet weten is requestAnimationFrame. Als je dit voor het eerst ziet, geloof ik oprecht dat je moet beginnen met dit te lezen. Dus, het ding met deze functie waarin we geïnteresseerd zijn is het argument - DOMHighResTimeStamp
. Het ziet er echt angstaanjagend uit, maar in de praktijk is het niet zo moeilijk te begrijpen. We kunnen zeggen dat het een tijdstempel is van de verstreken tijd vanaf de eerste render.
Ok, maar wat kunnen we hiermee doen? Als de requestAnimationFrame
functie recursief moet worden aangeroepen, hebben we toegang tot een tijdsdelta tussen de aanroepen. En daar gaan we met de wetenschap! ⚛️ (ok, misschien geen raketwetenschap... maar toch)
De natuurkunde leert ons dat de delta van een afstand gelijk is aan de delta van tijd vermenigvuldigd met snelheid. In ons geval is de snelheid constant omdat we het eindpunt in een bepaalde tijd willen bereiken. Laten we dus eens kijken hoe we dit kunnen weergeven met de bovenstaande toestanden:
Laten we zeggen dat we de overgang tussen deze toestanden in duizend milliseconden willen doen, dus de snelheidswaarden zullen de volgende zijn:
delta: [ -1, 25, -45, -35, 20]
snelheid: [-1/1000, 25/1000, -45/1000, -35/1000, 20/1000]
De snelheid hierboven vertelt ons: voor elke milliseconde verhogen we de waarde met -1/1000. En hier is het punt waarop we terug kunnen gaan naar onze requestAnimationFrame
en tijdsdelta. De waarde van een specifieke positie die we willen verhogen is de tijdsdeltas vermenigvuldigd met de snelheid van de positie. Nog iets om probleemloos te bereiken is om de waarde te beperken zodat deze niet groter wordt dan de toestand waar hij naartoe gaat.
Als de overgang eindigt, roepen we een andere op, enzovoort. En het lijkt niet zo moeilijk om te implementeren, maar een van de belangrijkste regels in softwareontwikkeling is om geen tijd te besteden aan dingen die al geïmplementeerd zijn. Dus - we kozen een kleine bibliotheek waarmee we deze overgangen moeiteloos kunnen maken.
Zo hebben we een geanimeerde golf gemaakt die eruitziet als een levend wezen.
Een paar woorden over het klonen van vormen
Zoals je kunt zien, zijn The Codest merkgolven niet één geanimeerd figuur. Ze bestaan uit vele figuren met dezelfde vorm maar een verschillende grootte en positie. In deze stap zullen we snel bekijken hoe je op zo'n manier kunt dupliceren.
Met de canvascontext kunnen we dus schaal tekengebied (onder de motorkap - we kunnen zeggen dat het alle dimensies die worden doorgegeven aan drawable methods vermenigvuldigt met "k", waarbij "k" een schaalfactor is, standaard ingesteld op "1"), canvas vertaald makenHet is alsof je het ankerpunt van een tekengebied verandert. En we kunnen ook tussen deze toestanden springen met deze methodes: sla en herstellen.
Met deze methoden kunnen we de toestand van "nul wijzigingen" opslaan en vervolgens een specifiek aantal golven in de lus renderen met correct geschaald en vertaald canvas. Direct hierna kunnen we teruggaan naar de opgeslagen toestand. Dat is alles over het klonen van figuren. Veel gemakkelijker dan het klonen van schapen, nietwaar?
Kers op de top
Ik zei al dat we een van de mogelijke oplossingen hebben afgewezen vanwege de prestaties. De optie met canvas is behoorlijk snel, maar niemand heeft gezegd dat het niet nog verder geoptimaliseerd kan worden. Laten we beginnen met het feit dat we ons niet echt zorgen maken over de overgang van vormen als ze zich buiten de viewport van de browser bevinden.
Er is nog een browser-API waar programmeurs dol op zijn - IntersectionObserver. Hiermee kunnen we specifieke elementen van de pagina volgen en gebeurtenissen afhandelen die worden opgeroepen wanneer die elementen verschijnen of verdwijnen uit de viewport. Op dit moment - hebben we een vrij eenvoudige situatie - laten we de staat van zichtbaarheid creëren, deze veranderen door IntersectionObserver event handlers en eenvoudig het render systeem aan of uit zetten voor specifieke vormen. En ... boem de prestaties zijn een stuk verbeterd! We renderen alleen de dingen die zichtbaar zijn in de viewport.
Samenvatting
Een manier kiezen om dingen te implementeren is vaak een moeilijke keuze, vooral wanneer de beschikbare opties vergelijkbare voor- en nadelen lijken te hebben. De sleutel tot het maken van een juiste keuze is om ze allemaal te analyseren en diegene die we als minder voordelig beschouwen uit te sluiten. Niet alles is duidelijk - de ene oplossing vergt meer tijd dan de andere, maar is misschien eenvoudiger te optimaliseren of beter aanpasbaar.
Hoewel er bijna elke minuut nieuwe JS libraries verschijnen, zijn er dingen die ze niet kunnen oplossen. En daarom zou elke front-end engineer (en niet alleen zij) de browser-API's moeten kennen, op de hoogte moeten blijven van technisch nieuws en soms gewoon moeten denken "hoe zou mijn implementatie van deze bibliotheek eruit zien als ik dit moest doen?". Met meer kennis over browsers kunnen we echt leuke dingen bouwen, goede beslissingen nemen over tools die we gebruiken en meer vertrouwen krijgen in onze code.
Lees meer:
– Ruby 3.0. Ruby en minder bekende privacycontrolemethoden
– Hou je mond en neem je geld #1: Verborgen kosten en echte flexibiliteit in het productontwikkelingsproces
– CTO uitdagingen - schaalvergroting en groei van softwareproducten