Kawałek Kodu

Kiedy poznawałeś wciągający świat transformacji 3D w CSS pewnie trafiłeś na efekt obracającego się sześcianu. Dziś możliwości CSS trochę spowszedniały, a my aby nie stać w miejscu zrobimy skok jednocześnie w przyszłość i przeszłość. Pokażę Ci odzworowanie retroefektu zwanego rubber vectors, korzystając z aktualnych możliwości transformacji 3D. Przy zderzeniu przeszłości i przyszłości zobaczysz coś niedzisiejszego. Właściwie to już widziałeś na powyższym wideo, ale za chwilę będziesz mógł cieszyć się tym oglądając na żywo.

Jak skręcić kostkę (nie swoją)?

Idea efektu jest dosyć prosta. Polega na rysowaniu kostki w standardowy sposób, natomiast co linię pokazujemy klatkę animacji o jeden (lub więcej) późniejszą. Musimy w tym celu mieć X buforów na poszczególne klatki animacji. Pierwszą klatkę rysujemy na pierwszym buforze, podczas kolejnej kopiujemy go (i wszystkie za nim) o jeden niżej, a klatkę rysujemy znów na pierwszym. Pobierając kolejne linie z coraz to "późniejszych" buforów sprawiają one wrażenie "ciągnięcia się" za tymi powyżej.

Nie będziemy jednak używać CANVAS, bo:

  • przeniesienie HTML do CANVAS nie pozwoli nam na użycie zewnętrznych arkuszy styli, a użycie styli inline odpada,
  • albo musielibyśmy pisać od zera procedury na transformacje 3D (obrót, translacja, perspektywa), a przy pokryciu ścian obrazkami, dodatkowo procedury na teksturowanie.

Skoro nie będziemy używać CANVAS i nie posiadając buforów, to jak zapewnić w każdej linii ekranu inną klatkę animacji kostki? Musimy narysować X kostek, każdą w innym momencie animacji. Nie będzie to rozwiązanie wydajne szybkościowo, ale efektowne. Ale, ale! Co z tego, że będzie tyle kostek na ekranie? Przecież wtedy ujrzymy zamiast jednego kilkadziesiąt/set sześcianów jeden pod drugim. Nie do końca, bowiem z każdego sześcianiu pokażemy tylko jedną linię dzięki właściwości overflow ustawionej na hidden.

Zacznijmy od podstawowego przykładu, czyli standardowej kostki 3D obracającej się w trzech osiach:

<div class="kontener">
 <div class="kostka">
  <div class="przod">1</div>
  <div class="tyl">2</div>
   <div class="prawa">3</div>
   <div class="lewa">4</div>
   <div class="gora">5</div>
   <div class="dol">6</div>
 </div>
</div>

+

.kontener {
    width: 200px;
    height: 200px;
    left: 100px;
    top: 100px;
    position: relative;
    perspective: 1000px;
}

.kostka {
    width: 100%;
    height: 100%;
    left: 0;
    position: absolute;
    top: 0;
    transform-style: preserve-3d;
    animation: obrot 3s infinite linear;
}

.kostka div {
    margin: 0;
    width: 100%;
    height: 100%;
    display: block;
    position: absolute;
    border: 2px solid #000;
    text-align: center;
    line-height: 200px;
    font-size: 200px;
}

.kostka .przod {
    transform: rotateY(0) translateZ(100px);
    background: #f00;
}
.kostka .tyl {
    transform: rotateX(180deg) translateZ(100px );
    background: #ff0;
}
.kostka .prawa {
    transform: rotateY(90deg) translateZ(100px);
    background: #f0f;
}
.kostka .lewa {
    transform: rotateY(-90deg) translateZ(100px);
    background: #0f0;
}
.kostka .gora {
    transform: rotateX(90deg) translateZ(100px);
    background: #0ff;
}
.kostka .dol {
    transform: rotateX(-90deg) translateZ(100px);
    background: #00f;
}

@keyframes obrot{
  0% {transform:rotateY(0deg) rotateX(0deg) rotateZ(0deg)}
  100% {transform:rotateY(359deg) rotateX(359deg) rotateZ(359deg)}
}

Krok 1

To już zapewne widziałeś, więc zabierzmy się za galaretowate smaczki.

Musimy stworzyć kopię 200. kostek korzystając z powyższego fragmentu HTML i dodając parę linii w CSS. Kopie zrobimy w JavaScript. Tu też ustawimy opóźnienie animacji dla każdej kostki - to zastąpi nam pobieranie poprzedniej klatki dla danej linii (bufory). To, że używamy JavaScript nie zmienia faktu, że jest to efekt "pure CSS". Kopiowanie fragmentu HTML możesz zrobić ręcznie, możesz też użyć jakiegoś preprocesora do HTML (lub PHP). Podobnie możesz postąpić z ustawianiem kilku właściwości CSS, które muszą być odrębne dla każdej linii (np. korzystając z SCSS).

var plansza = document.createElement('div');
plansza.id = 'plansza';
var wzor = document.querySelector('div.kontener');

for(var i=0; i<200; i++){
    var linia = document.createElement('div');
    linia.className = 'linia';
    linia.innerHTML = wzor.outerHTML;
    plansza.appendChild(linia);
    linia.querySelector('.kontener').style.top = -i + 'px';
    linia.querySelector('.kostka').style.animationDelay = i*0.002 + 's';
}

wzor.parentNode.removeChild(wzor);
document.body.appendChild(plansza);

Dla wszystkich linii tworzymy dodatkowy kontener (.plansza). Następnie każda kopię kostki owijamy w element o klasie linia. Element .linia w CSS ma wysokość 1px i posiada właściwość overflow:hidden, dzięki temu widzimy tylko wycinek danej kostki. Aby jednak zobaczyć odpowiedni wycinek, każda kostkę pozycjonujemy przesuwając ją o kolejny 1px w górę. W pierwszej linii chcemy zobaczyć pierwszy pasek kostki, więc ta ma przesunięcie 0px, ale już w drugiej linii chcemy zobaczyć właśnie drugą linię kostki, więc jest przesunięta o 1px. Kolejna o 2px, itd. Dla każdej kostki w linii ustawiamy jakieś opóźnienie animacji. Czym większe opóźnienie, tym większe "skręcenie" kostki. Elementy .linia dodajemy do .plansza, tą następnie dodajemy do BODY, a wzór usuwamy.

CSS lekko zmieniamy:

.linia {
    height: 1px;
    overflow: hidden;
    width: 200px;
}
.kontener {
    width: 200px;
    height: 200px;
    position: relative;
    left: 0;
    top: 0;
    perspective: 1000px;
}

Dodaliśmy tu klasę dla .linia, natomiast z .kontener zostało wyrzucone przesunięcie (we wcześniejszej wersji kostki przesunąłem ją, aby było widać w całości obracające się wierzchołki).

Krok 2

Coś tam widać, ale kostka jest obcięta przez to, że nadaliśmy elementowi .linia właściwość overflow:hidden. Ponieważ poptrzebujemy tylko "kadrowanie" linii tylko w osi Y, to możemy spróbować nadać overflow-y:hidden, a overflow-x:visible dla .linia. Ale to niestety nie zadziała (sprawdź sam).

Jak więc pokazać całą kostkę z zachowaniem overflow:hidden?

Możemy zmniejszyć naszą kostkę używając właściwości transform. Fajnie, fajnie, ale kostka będzie... mniejsza, a przecież chcemy ją zobaczyć w oryginalnym rozmiarze. Skoro kostkę zmniejszymy, to możemy zwiększyć jeden z elementów ją przechowujących!

Dodajemy:

#plansza {
    transform: scale(2);
    transform-origin: 0 0;
}
@keyframes obrot{
  0% {transform:rotateY(0deg) rotateX(0deg) rotateZ(0deg) scale3d(.5,.5,.5)}
  100% {transform:rotateY(359deg) rotateX(359deg) rotateZ(359deg) scale3d(.5,.5,.5)}
}

Kostkę zmniejszamy dwukrotnie, więc tyle samo zwiększamy #plansza. Skalowanie kostki musimy dodać w klatkach animacji, bo nadanie jej bezpośrednio w klasie CSS spowoduje, że właściwość transform z klatek animacji ją nadpisze.

Krok 3

I oto jest!
Co prawda, tak jak pisałem wyżej, nie jest to rozwiązanie optymalne, więc na niektórych przeglądarkach efekt będzie wyglądać mizernie (na Chrome jednak całkiem, całkiem).

Nic nie stoi na przeszkodzie, aby dodać jeszcze więcej smaku naszej galaretce. Zamiast numeracji ścian i kolorowych teł dodajmy obrazki.

Krok 4

W obecnej sytuacji nie pozostaje mi nic innego jak nie przerywać Ci delektowania się słodyczą efektu. Smacznego!