Animacja ładowania strony tylko w CSS – krok po kroku

Chciałem zrobić animacje ładowania strony, która składa się z kulek, które falują.

Jak zaplanowałem tak zrobiłem:

See the Pen Balls loading animation by soltys (@soltys) on CodePen.

Wygląd jest inspirowany malutką giereczką 140.

Podczas pisania nauczyłem się parę tricków w SASS i CSS.

Jak wycentrować coś pionie przy użyciu CSS?

Przy użyciu flexbox tak:

html,
body {
  height: 100%;
  background:#ddd;
}

.container {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
}

Po pierwsze dbamy o to, aby przestrzeń zajmowana przez html, body oraz div.container zajmowała całą dostępną wysokość w podglądzie CodePen. Następnie wszystko centrujemy przy użyciu flexboxa.

HTML, który jest potrzebny do naszej animacji wygląda, tak:

A tak to przekłada z klasy na kolor składowy każdej kulki:

css_animation_ball_annotations

Jak zrobić kulkę z kwadratu w CSS?

Trzeba ustalić border-radius na połowę szerokości/wysokości kwadratu.

.circle {
  width: $ball-size;
  height: $ball-size;
  border-radius: 50%;
  background: $outer-ball-color;

  margin: 0 5px;
}

Gdzie:

  • $ball-size jest rozmiarem naszej kulki – u mnie jest 30px
  • $outer-ball-color jest kolorem naszej kulki – u mnie jest to #4D2C71
  • ustalamy margin, aby dać każdej kulce trochę odstępu od siebie.

Jak zrobić kulkę w wewnątrz innej kulki?

Utwórz nowy div, a w nim ustal kulkę na połowę wielkości tej zewnętrznej.

.inner-circle {
  width: $ball-size/2;
  height:  $ball-size/2;
  border-radius:  50%;  
  background: $inner-ball-color;

  margin: $ball-size/4;  
}

W porównaniu z zewnętrzną kulką ustalamy margin w celu wycentrowania jej wewnątrz fioletowej kulki.

U mnie $inner-ball-color ma wartość #F3BB1C.

Jak pokolorować połowę kulki na inny kolor?

Trzeba nałożyć na połowę żółtej kulki kwadrat i odciąć to co wystaje poza żółta kulkę.

Aby przeglądarka pozbywała się czegoś co wychodzi poza obręb jakiegoś diva trzeba zdefiniować overflow: hidden. Ten atrybut trzeba dodać do .inner-circle!

reszta CSSa wygląda tak:

.part-cicle {
  position: relative;
  top: $ball-size/4;
  width: $ball-size/2;
  height: $ball-size/2;
  background: $part-ball-color;
}

Interesuje nas pozycja relatywna – position: relative względem rodzica, czyli .inner-child. Przesuwamy się o połowę wysokości .inner-child.

U mnie $part-ball-color ma wartość #FF0000.

Jak zrobić efekt fali w poruszaniu się kulek?

Najpierw zdefiniujmy jak ma wyglądać animacja poruszania się góra-dół.

@keyframes up {
  0% {
    transform: translate(0, $moving-amplitute);
  }
  50% {
    transform: translate(0, -$moving-amplitute);
  }
  100% {
    transform: translate(0, $moving-amplitute);
  }
}

$moving-amplitute to maksymalne wychylenie, które będzie zastosowane na każdą z kulek. U mnie ta wartość wynosi 5px. Animacja jest tak skonstruowana, aby zaczynała i kończyła się w tym samym miejscu, dzięki czemu kulki się poruszają w sposób płynny, nie ma przeskoków.

Efekt fali jest uzyskany dzięki temu, że każda animacja uruchomiona jest troszkę później niż poprzednia.

.circle:nth-child(1) {
  animation: up 1s -0.25s infinite ease-in-out;
}
.circle:nth-child(2) {
  animation: up 1s -0.5s infinite ease-in-out;
}
.circle:nth-child(3) {
  animation: up 1s -0.75s infinite ease-in-out;
}

W powyższym przykładzie definiujemy animacje dla każdego kolejnego elementu .circle. Składnia animation wygląda następująco

  • up – animation-name czyli wskazujemy na definicje animacji, zdefiniowanej przez @keyframes.
  • 1s – czas w sekundach na wykonanie animacji
  • -0.25s – czas w sekundach, opóźnienie animacji. Opóźnienie ujemne? Tego tricku nauczyłem dosłownie 5 minut temu 🙂 W momencie ustawienia animacji z opóźnieniem ujemnym, animacja rozpocznie się tak szybko jak może, ale zacznie od momentu 0.25s w animacji. Dzięki temu, przy ładowaniu strony kulki już są w odpowiednich miejscach i nie ma efektu, jakby one zaspały.
  • infinite – oznacza, że animacja będzie zapętlona
  • ease-in-out – oznacza, że przeglądarka wygładzi ruch kulki, będzie on przypominać odbicie od podłogi, niż ruch liniowy, bez hamowania.

Taki kod CSS może zostać wygenerowany przy użyciu SASSa, preprocesora CSS.

.circle {
  /* ... wyciete ... */
  @for $i from 1 to $ball-number+1 {
    &:nth-child(#{$i}) {
      animation: up 1s ($moving-up-delay * -$i) infinite ease-in-out;
    }    
  }
}

$ball-number to ilość kulek w naszej animacji czyli 3. $moving-up-delay to wartość opóźnienia, między kolejnymi kulkami u mnie ta wartość to 0.25s. Znak & oznacza odwołanie od rodzica w zapisie, czyli znak & zostanie zastąpiony .circle.

Bonus: Co zrobić jak nie wiesz, że animation-delay może przyjąć wartość ujemną? Co zrobić, aby wartości ustawione przez animację animacji zostały?

Podczas pisania tej animacji za pierwszym razem, nie wiedziałem, że animation-delay może przyjąć wartość ujemną. Efekt był taki, że kulki stoją pozycji startowej i czekają, aby zacząć się animować. Znalazłem na to rozwiązanie, jak ten brzydki efekt ukryć.

dodać do .circles taki fragment kodu CSS:

.circles {
  /* ... wyciete ... */
  animation: fadein 1s ($moving-up-delay*$ball-number)  ease-in;
  animation-fill-mode: forwards;
  opacity: 0;
}

@keyframes fadein {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

czyli dodałem animację przejścia przeźroczystości od 0 do 1 (od niewidocznego do widocznego). Po załadowaniu strony niewidoczne kulki startują swoją animacje, gdy już wszystkie powinny wystartować startuje animacja przechodzenie przeźroczystości.

Tutaj ważne jest ustawienie animation-fill-mode: forwards; dzięki niemu wartość opacity pozostaje ustawione na 1 po wykonaniu animacji.

Jak wykonać rotację środka?

To bardzo proste:

@keyframes rotate {
  100% {
    transform: rotate(360deg);
  }
}

.circle:nth-child(1) > .inner-circle {
  animation: rotate 1s 0.05s infinite;
}
.circle:nth-child(2) > .inner-circle {
  animation: rotate 1s 0.1s infinite;
}
.circle:nth-child(3) > .inner-circle {
  animation: rotate 1s 0.15s infinite;
}

/*
 * Czyli w SASS coś takiego
 */

.circle {
  /* ... wyciete ... */
  @for $i from 1 to $ball-number+1 {
    &:nth-child(#{$i}) > .inner-circle {
      animation: rotate 1s ($rotation-delay * $i) infinite;
    }
  }
}

Każda kulka obraca się w czasie 1s z maleńkim opóźnieniem wobec swojego sąsiada, według mnie, dzięki temu wygląda to lepiej, niż synchroniczne obroty.

u mnie $rotation-delay wynosi 0.05s.

Podsumowanie

Dzięki zrobieniu tak prostej animacji sporo się nauczyłem:

  • CodePen to fajne narzędzie do prototypowania swoich animacji (ALE raz Codepen zrobi mi totalną sieczkę z CSSa – narzędzie czasem nie jest idioto-odporne)
  • Wykorzystanie SASSa i zmiennych przyspiesza jeszcze bardziej przyspiesza proces prototypowania
  • CSS obsługuje centrowanie poziomie! Niech żyje flexbox.
  • animation-fill-mode: forwards; – dzięki niemu wartości wyliczone przez animację zostają zapisane.
  • animation-delay może przyjąć wartości ujemne.

Dzięki Łukasz za sprawdzenie posta przed publikacją.