En av de saker som gjorde oss förvirrade när vi byggde vår nya webbplats var de morfade vågorna som du kan se på olika ställen på sidorna. Vi hade många idéer om hur vi skulle kunna implementera dem på rätt sätt utan större ansträngning. De flesta lösningarna var dock långsamma och vi var tvungna att bygga något från grunden som skulle vara snabbare än redan befintliga bibliotek.
Lösningsförslag
Vi började med ett vanligt SVG-objekt som animerades med olika bibliotek. Eftersom vi ville ha tre objekt på en sida var resultatet inte så tillfredsställande. Alla animationer var bara långsamma - alla banor i ett enda SVG-objekt måste uppdateras på väldigt korta tidsperioder, vilket gjorde hela sidan långsam som en snigel. Vi var tvungna att förkasta lösningen med ren SVG som infogades i ett dokument. Det gav oss två andra lösningar att välja mellan.
Den video
element var det andra alternativet. Vi började med två problem:
- transparent bakgrund, vilket inte kan tillämpas med de mest populära videoformaten som .mp4 eller .webm,
- responsivitet, vilket var ett verkligt problem eftersom videor inte är skalbara som sådana.
Vi bestämde oss för att hålla den här lösningen i bakhuvudet - "om vi inte hittar något annat så väljer vi den här".
Det sista alternativet var att använda canvas
med WebGL
rendering. Det var ett så ovanligt alternativ eftersom vi var tvungna att utforma all renderingsmekanik själva. Det beror på att de morfiska vågorna vi har var anpassade - det tvingade oss att utforma en anpassad lösning Och det var det alternativ som vi ville följa och verkligen fokusera på.
Lösningens arkitektur
Låt oss börja från början. Vad var det för material som vi var tvungna att använda för att bygga dessa vågor? Tanken var att alla vågor var en SVG-fil i storlek 1×1 och specifika banor placerade runt detta område. Animeringen av denna SVG byggdes av vissa former av tillstånd till den här filen. Så alla animationer representerades som en uppsättning filer som innehöll stadierna för att flytta en form.
Ta en djupare titt på vad tillstånden är - alla vägar är bara ett slags matris med specifika värden placerade i specifik ordning. Om du ändrar dessa värden i specifika positioner inom denna matris ändras formen i dess specifika delar. Vi kan förenkla detta med följande exempel:
tillstånd 1: [1, 50, 25, 40, 100]
tillstånd 2: [0, 75, -20, 5, 120]
tillstånd 3: [5, 0, -100, 80, 90]
Så vi kan anta att den form vi vill återge består av en matris med 5 element som förändras med den linjära lättnaden under specifika tidsperioder. När animeringen avslutar det sista steget börjar den om från början för att ge oss intrycket av en oändlig animering.
Men... vänta - vad exakt är den matris som presenteras ovan? Som jag nämnde är det en sökväg som är ansvarig för att visa en specifik form. All magi ingår i d
egenskap för SVG:s bana. Denna egenskap innehåller en uppsättning "kommandon" för att rita en form och varje kommando har en sorts argument. Arrayen som nämns ovan består av alla värden (argument) som är kopplade till dessa kommandon.
Den enda skillnaden mellan dessa "state files" var värdena för specifika kommandon eftersom kommandonas ordning var densamma. Så allt det magiska handlade om att få fram alla värden och animera dem.
Guiden kallas Fysik
I stycket ovan nämnde jag att den enda magin i att animera ett objekt är att skapa övergångar mellan alla steg i en form. Frågan är - hur gör man det med canvas?
Den funktion som alla som arbetat med canvas
bör veta är begäranAnimationFrame. Om ni ser det här för första gången tycker jag verkligen att ni ska börja med att läsa det här. Så, det vi är intresserade av med den här funktionen är argumentet - DOMHighResTimeStamp
. Det ser verkligen skrämmande ut men i praktiken är det inte så svårt att förstå. Vi kan säga att det är en tidsstämpel för förfluten tid från den första renderingen.
Ok, men vad kan vi göra med detta? Eftersom begäranAnimationFrame
funktionen ska anropas rekursivt kan vi få tillgång till ett tidsdelta mellan dess anrop. Och här går vi med vetenskapen! ⚛️ (ok, kanske inte raketvetenskap ... ännu )
Fysiken lär oss att deltavärdet för ett avstånd är lika med deltavärdet för tiden multiplicerat med hastigheten. I vårt fall är hastigheten konstant eftersom vi vill nå slutpunkten inom en viss tidsperiod. Så låt oss ta en titt på hur vi kan representera det med ovanstående tillstånd:
Låt oss säga att vi vill övergå mellan dessa tillstånd på tusen millisekunder, så hastighetsvärdena blir följande:
delta: [ -1, 25, -45, -35, 20]
hastighet: [-1/1000, 25/1000, -45/1000, -35/1000, 20/1000]
Hastigheten ovan säger oss: för varje millisekund ökar vi värdet med -1/1000. Och här är den punkt där vi kan gå tillbaka till vår begäranAnimationFrame
och tidsdelta. Värdet för en specifik position som vi vill öka med är tidsdelta multiplicerat med hastigheten för dess position. Ytterligare en sak som kan uppnås utan problem är att begränsa värdet så att det inte överstiger det tillstånd det ska till.
När övergången är slut kallar vi på en ny och så vidare. Och det verkar inte vara så svårt att implementera, men en av huvudreglerna i Utveckling av programvara är att inte lägga tid på saker som redan är implementerade. Så - vi valde en litet bibliotek som gör det möjligt för oss att skapa dessa övergångar på ett enkelt sätt.
Det är så vi har skapat en animerad våg som ser ut som en levande varelse.
Några ord om kloning av former
Som du kan se är The Codest-märkesvågor inte en enda animerad figur. De består av många figurer med samma form men en annan storlek och position. I det här steget kommer vi att ta en snabb titt på hur man duplicerar på ett sådant sätt.
Canvas-kontexten ger oss alltså möjlighet att skala ritning område (under huven - vi kan säga att den multiplicerar alla dimensioner som skickas till ritbara metoder med "k", där "k" är en skalningsfaktor, som standard inställd på "1"), göra duken översattär det som att ändra ankarpunkten för ett ritområde. Och vi kan också hoppa mellan dessa tillstånd med dessa metoder: spara och återställa.
Dessa metoder gör att vi kan spara tillståndet "noll modifieringar" och sedan rendera ett specifikt antal vågor i loopen med korrekt skalad och översatt canvas. Direkt efter detta kan vi gå tillbaka till det sparade tillståndet. Det är allt om figurkloning. Mycket enklare än att klona får, eller hur?
Körsbär på toppen
Jag nämnde att vi förkastade en av de potentiella lösningarna på grund av prestandan. Alternativet med canvas är ganska snabbt men ingen sa att det inte kunde optimeras ännu mer. Låt oss börja med det faktum att vi egentligen inte bryr oss om att överföra former när de befinner sig utanför webbläsarens vyport.
Det finns ett annat API för webbläsare som programmerare älskade - Observatör av korsning. Det gör att vi kan följa specifika element på sidan och hantera händelser som åberopas när dessa element visas eller försvinner från vyport. Just nu - vi har en ganska enkel situation - låt oss skapa synlighetstillståndet, ändra det på grund av IntersectionObserver-händelsehanterare och helt enkelt slå på / av renderingssystemet för specifika former. Och ... boom prestandan har förbättrats mycket! Vi renderar de enda saker som är synliga på viewport.
Sammanfattning
Att välja ett sätt att genomföra saker är ofta ett svårt val, särskilt när de tillgängliga alternativen verkar ha liknande fördelar och nackdelar. Nyckeln till att göra ett korrekt val är att analysera vart och ett av dem och utesluta dem som vi ser som mindre fördelaktiga. Allt är inte självklart - en viss lösning kräver mer tid än de andra, men den kan vara enklare att optimera eller mer anpassningsbar.
Även om nya JS-bibliotek dyker upp nästan varje minut finns det saker som de inte kan lösa. Det är därför alla frontend-ingenjörer (och inte bara de) bör känna till webbläsarnas API:er, hålla sig uppdaterade med tekniska nyheter och ibland bara tänka "hur skulle min implementering av det här biblioteket se ut om jag var tvungen att göra det här?". Med mer kunskap om webbläsare kan vi bygga riktigt snygga saker, fatta bra beslut om vilka verktyg vi använder och bli mer självsäkra på våra kod.
Läs mer om detta:
– Ruby 3.0. Ruby och mindre kända metoder för sekretesskontroll
– Håll käften och ta dina pengar #1: Dolda kostnader och verklig smidighet i produktutvecklingsprocessen
– CTO-utmaningar - uppskalning och tillväxt av mjukvaruprodukter