Teknologi / 6 minutter /
En animert pil på blogg.kantega.no
Dette er historien om en tilsynelatende enkel oppgave, et par skjulte utfordringer, og tankereisen for å finne den beste løsningen.
Problemet
På blogg.kantega.no kan man søke etter artikler med en gitt tagg. Disse taggene er samlet i en boks under overskriften på forsiden. Lista med tagger er skjult, og vises når man trykker på boksen. For å visualisere om boksen er åpen eller lukket, er det en pil på høyre side, som peker ned når boksen er lukket, og opp når den er åpen. Det er denne pila jeg skal fortelle om.
Alle som er kjent på internett, har helt sikkert sett en lignende pil før. Den peker som sagt ned for å si "her er det noe under", og opp for å si "denne boksen kan du lukke". For å gjøre det enda bedre, er den ofte animert. Så også på bloggen vår. Når du klikker på boksen, snurrer pila 180 grader rundt til sin nye posisjon.
Det er mange måter å lage en slik pil. Den kan være et bilde, for eksempel en PNG, eller enda bedre, en SVG. Det kan være SVG som er kodet rett inn i HTML-en. Eller man kan tegne den med CSS. Vår pil er tegnet med CSS, og er en gjennomsiktig firkant, hvor to av kantene er fargelagte. Det er enkelt, greit, og en kilde til et par utfordringer.
Øyet vårt ser dette som en pil, og nettleseren ser det som en firkant, og begge har rett. Problemet dukker opp når vi roterer pila. For når vi roterer den til å peke nedover, ser vi bare den nederste delen av firkanten, så det ser ut som pila blir liggende litt under midten. Det samme skjer når vi roterer den oppover, da ser vi bare den øverste delen av firkanten, og det ser ut som pila ligger litt over midten. Når vi animerer dette blir det ekstra tydelig, og det ser ut som hele pila beveger seg opp og ned når den går fra å peke nedover til å peke oppover.
Første forsøk
Vi har altså en pil som er litt utenfor riktig posisjon, og vi ønsker å rette på det. Da flytter vi den litt. Dette ble den første løsningen:
.arrow {
position: relative;
}.arrow.pointing-down {
bottom: 5px;
}.arrow.pointing-up {
top: 5px;
}
Dette funker, på en måte, men det er ikke godt nok. Animasjonen har en rar bevegelse, som kommer av at vi flytter hele figuren opp og ned, samtidig som den roterer. Vi kan også spørre oss hvorfor akkurat 5 piksler? Sannheten er at jeg bare prøvde meg fram til jeg fant noe som så ut som det var riktig. Dette kan vi gjøre bedre.
En grundigere analyse
UX-designerne i Kantega har lært meg at når vi skal finne løsningen på et problem, må vi først finne ut hva det egentlige problemet er. Tilsynelatende er problemet vårt at pila blir liggende litt over, eller under midten når den roterer. Hvorfor gjør den det? Fordi vi roterer ikke en trekant, men en firkant, og senter i firkanten er ikke på samme sted som senter i trekanten. Senteret i firkanten er nok kjent for alle. Hvis vi tegner opp de to diagonalene i firkanten, møtes de i senteret. Senteret i en trekant kan vi finne på samme måte, ved å trekke en linje fra hvert hjørne til midtpunktet på den motsatte siden. De tre linjene vil møtes i et punkt, som er senteret i trekanten.
Vi har altså løst et symptom på problemet, ikke selve problemet, og derfor ble også resultatet bare sånn passe bra.
For å rotere pila, bruker vi CSS transforms, mer spesifikt disse reglene:
.arrow.pointing-up {
transform: rotate(225deg);
}.arrow.pointing-down {
transform: rotate(45deg);
}
Når vi roterer en figur, må den ha et punkt å rotere rundt. Dette punktet kan vi definere med transform-origin, og hvis vi ikke definerer det, bruker nettleseren transform-origin: center;, altså midten av figuren. Som vi allerede har oppdaget er ikke senter til firkanten og senteret til pila på samme sted, altså må vi flytte rotasjonspunktet. Men hvor skal punktet ligge?
Andre forsøk
Hvis vi slår opp i Wikipedia på centroid, kan vi lese at ... the centroid or geometric center of a plane figure is the arithmetic mean position of all the points in the shape. For en trekant trenger vi ikke regne ut snittet av alle punktene, det er nok med de tre hjørnene.
Firkanten som definerer pila vår er 15 piksler høy, og 15 piksler bred. I nettleseren er origo øverst i høyre hjørne, den positive y-aksen peker nedover, og den positive x-aksen peker mot høyre. Altså er de tre punktene som definerer trekanten (0, 15), (15, 15) og (15, 0). Senter i trekanten blir da (10, 10).
Den forbedrede løsningen blir da
.arrow {
transform-origin: 10px 10px;
}.arrow.pointing-up {
transform: rotate(225deg);
}.arrow.pointing-down {
transform: rotate(45deg);
}
En siste forbedring
Nå roterer pila perfekt rundt sin egen akse. Det siste vi må fikse for at det skal bli helt perfekt, er å sentrere pila i boksen. Det er ikke så mye som skal til, men i små doser er pixel paranoia en god ting.
Firkanten er sentrert i boksen, men siden vi bare ser den ene halvdelen, ser det ut som pila ligger en anelse under midten. Siden firkanten er 15×15, er senteret i firkanten i (7.5, 7.5). Senteret i pila er som kjent i (10, 10), altså er ikke pila sentrert i boksen. Det kan vi fikse ved å flytte pila 2.5 piksler opp og 2.5 piksler til venstre.
.arrow {
transform-origin: 10px 10px;
position: relative;
bottom: 2.5px;
right: 2.5px;
}.arrow.pointing-up {
transform: rotate(225deg);
}.arrow.pointing-down {
transform: rotate(45deg);
}
Til slutt
Når det dukker opp et problem som du ikke helt vet hvordan du skal løse, lønner det seg å ta en ekstra runde i tenkeboksen og lete etter rotårsaken. Løsningen blir sannsynligvis enklere, og garantert bedre!