Kawałek Kodu

Tym razem powrót do lat 90-tych i demosceny. Dla relaksu głowy zajmiemy się gimnastyką ekranu. Przy pomocy dzisiejszych technologii odwzorujemy efektowny efekt - D.Y.C.P. Nazwa pochodzi od: Different Y Character Position. No i dlatego, że piątek.

Bezruch.

Zacznjimy od odpowiedniego przygotowania tekstu, który rozruszamy. Żeby efekt wyglądał przyzwoicie, tekst należy podzielić na elementy, gdzie każdy z nich będzie zawierał odrębną literę tekstu. Tu też warto pokusić się o użycie fontu o równej długości każdej litery (jakiś font monospace). Dlaczego? O tym przekonasz się później.

<link href="https://fonts.googleapis.com/css?family=VT323" rel="stylesheet">

<p>Lorem Ipsum jest tekstem stosowanym jako przykładowy wypełniacz w przemyśle poligraficznym. Został po raz pierwszy użyty w XV w. przez nieznanego drukarza</p>
<div></div>

+

html,
body {
  height: 100%;
}

body {
  margin: 0;
  overflow:hidden;
  font-family: 'VT323', monospace;
  font-size:24px;
}

div {
  position: relative; 
}

span {
  position: absolute;
  left: 0;
  display: inline-block;
}

+

/* kontener z tekstem */
var sampleTextContainer = document.querySelector('p');
var sampleText = sampleTextContainer.innerText + ' ';
/* kontener z efektem */
var DYCPContainer = document.querySelector('div');

/* aktualne przesunięcie litery */
var offsetWidth = 0;
/* szerokość litery */
var charWidth = 0;

for (var i = 0; i < sampleText.length; i++) {
/* dla każdej litery tekstu tworzymy
   odrębny element
*/
  var span = document.createElement('span');
  var letter = sampleText[i];
  if (letter === ' ') {
    letter = '&nbsp;';
  }
  span.innerHTML = letter;
  DYCPContainer.appendChild(span);

/* ustawiamy literze przesunięcie */
  span.style.left = offsetWidth + 'px';
/* ponieważ to monospace font,
   to zapisujemy szerokość tylko
   pierwszego znaku
*/
  if(i === 0){
    charWidth = span.offsetWidth;
  }
/* zwiększamy offset,
   kolejna litera będzie przesunięta
   o szerokość znaku
*/
  offsetWidth += charWidth;
}
/* usuwamy kontener z przykładowym tekstem */
sampleTextContainer.parentNode.removeChild(sampleTextContainer);

Przysiady.

Wprowadźmy teraz efekt bujania się każdej z liter tekstu z osobna. Mamy już podzielony tekst na osobne litery. Każdej z nich możemy nadać animację płynnego ruchu góra-dół. Aby wszytkie nie bujały się po jednakowej trajektorii, dodajemy do każdej delikatne opóźnienie animacji.

div {
  position: relative;
  height: calc(100% - var(--charHeight)); /* wysokość kontenera 100% ekranu -  wysokość litery */ 
}

span {
  position: absolute;
  left: 0;
  display: inline-block;
  animation: dycp 6s ease-in-out infinite;
}

@keyframes dycp {
  0% {
    top: 0%;
  }
  50% {
    top: 100%;
  }
  100% {
    top: 0%;
  }
}

+

var sampleTextContainer = document.querySelector('p');
var sampleText = sampleTextContainer.innerText + ' ';

var DYCPContainer = document.querySelector('div');

/* opóźnienie animacji
   dla każdej litery
*/
var animationDelay = 0;
var offsetWidth = 0;
var charWidth = 0;

for (var i = 0; i < sampleText.length; i++) {
  var span = document.createElement('span');
  var letter = sampleText[i];
  if (letter === ' ') {
    letter = '&nbsp;';
  }
  span.innerHTML = letter;
  DYCPContainer.appendChild(span);
  span.style.left = offsetWidth + 'px';
/* nadajemy opóźnienie */
  span.style.animationDelay = animationDelay + 's';
/* następny znak będzie opóźniony
   o 0.03s w stosunku do poprzedniego
*/
  animationDelay += 0.03; 
  if(i === 0){
    charWidth = span.offsetWidth;
/* zapisujemy również zmienną CSS
   aby kontener z efektem był niższy
   od ekranu o wysokość znaku,
   aby efekt nie wychodził poza dolną
   krawędź
*/
    document.documentElement.style.setProperty('--charHeight', span.offsetHeight + 'px');
  }
  offsetWidth += charWidth;
}
sampleTextContainer.parentNode.removeChild(sampleTextContainer);

Kiwanie.

Teraz kolej na kolejny ruch. Dodajemy cykliczne przesuwanie całego kontenera o szerokość znaku.

div {
  position: relative;
  height: calc(100% - var(--charHeight));  
  animation: scroll .25s linear infinite;  
}

@keyframes scroll {
  0% {
    margin-left: 0;
  }
  100% {
    margin-left: var(--scrollOffset);
  }
}

+

/* do warunku dodajemy tylko jedną linijkę */

if(i === 0){
  charWidth = span.offsetWidth;
  document.documentElement.style.setProperty('--charHeight', span.offsetHeight + 'px');
/* zapisujemy szerokość litery
   do zmiennej CSS
*/
  document.documentElement.style.setProperty('--scrollOffset', -charWidth + 'px');
}

Teraz widać, że efekt zaczyna działać, ale jakby się zacinał. Dlaczego przesuwamy cyklicznie tylko o jedną literę? Ano, dlatego, że nie wiemy ile liter ma nasz tekst, więc nie możemy z góry nadać mu przesunięcia np. o 1000px, bo gdyby jego koniec pojawił się na ekranie, to co byłoby za ostatnią literą gdyby przesuwała się w lewo ekranu?
Tu również czcionka monospace ułatwia nam całe zadanie. I oprócz tego sprawia, że efekt wygląda... równomiernie. W przypadku czcionki o zmiennej szerokości znaków, fala była ściśnięta w miejscach gdzie mamy do czynienia z wąskimi znakami, albo odwrotnie, zbyt rozciągnięta tam gdzie znaki są szerokie.

Let's D.Y.C.P.!

Nie bądź zawiedziony ostatnim krokiem. Teraz już wychodzimy na prostą, choć może raczej na sinusoidę. Kiedy animacja przesunie cały tekst o szerokość znaku, czyli zakończy się, my w tym momencie zamienimy miejscami kolejne litery (pierwsza wpadnie na miejsce ostatniej, druga wpadnie na pierwszej, trzecia na drugiej, itd.) i kiedy animacja wystartuje od początku, my będziemy przesunięci już o jedną literę. Zakładając szerokość litery X, w pierwszym kroku przesuniemy się więc od 0 do -X, a w drugim od 0-X do -X-X. W ten sposób w ogóle nie musimy się martwić o długość naszego tekstu i odpowiednie dobieranie właściwości animacji.

Do naszego JavaScript dodajemy:

/* czekamy na zakończenie iteracji
   jakiejkolwiek animacji */
DYCPContainer.addEventListener("animationiteration", function(e) {
/* sprawdzamy czy to animacja scroll */
  if (e.animationName === 'scroll') {
    var letters = document.querySelectorAll('span', DYCPContainer);
/* zapamiętujemy pierwszy znak */
    var firstChar = letters[0].innerHTML;
/* a kolejne przesuwamy w lewo */
    for (var i = 0; i < letters.length - 1; i++) {
      letters[i].innerHTML = letters[i + 1].innerHTML;
    }
/* na miejsce ostatniego wrzucamy pierwszy */
    letters[letters.length - 1].innerHTML = firstChar;
  }
});

Hawajski naszyjnik, palmowa spódniczka i girlanda.

Jak widzisz nasz tekst jest trochę smutny i przypomina bardziej statecznego walca niż hawajską hulę. Jeśli uznać nasze litery za małych serferów, to lepiej by wyglądali gdyby wznosząc się i opadając na fali trochę się wyluzowali, a nie robili jogijskie świece. Kiedy litera opada na fali możemy ją obrócic o jakiś kąt, podobnie kiedy się wznosi.

document.documentElement.style.setProperty('--screenHeight', document.body.offsetHeight);

+

@keyframes dycp {
  0% {
    top: 0%;
    transform:  rotate(0);
    color:rgb(0, 165, 223);
  }
  25% {
    transform: rotate(calc(-600 / var(--screenHeight) * 45deg)) /* opadamy więc głowa do przodu */
    color: rgb(62, 20, 123);
  }
  50% {
    top: 100%;
    transform:  rotate(0); /* dotarliśmy na dół, więc pionowo */
    color: rgb(226, 0, 121);
  }
  75% {
   transform: rotate(calc(600 / var(--screenHeight) * 45deg)) /* zaczynamy się wznosić, więc plecy do tyłu */
   color:rgb(223, 19, 44);
  }
  100% {
    top: 0%;
    transform:  rotate(0);
    color:rgb(0, 165, 223);
  }
}

Kąt pochylenia jest dobrany eksperymentalnie i zależy od wysokości ekranu. Bo powinien zależeć - kiedy amplituda sinusoidy jest mniejsza, to i kąt pochylenia musi być mniejszy, aby litery przy opadaniu i wznoszeniu nie wyglądały jakby miały położyć się poziomo.

Wyluzuj, w następnym odcinku nie będziemy już uczyć się tańczyć. Będzie to co lubią geeki!

 

Przydatne linki:
D.Y.C.P. Ultimate - ostatni przykład