Kontynuujemy naukę obiektowego podejścia do pisania kodu JavaScript, wykonując hover effect z kolorem na każdej literze tagu <h2> z pewnym opóźnieniem. Piszemy klasę JS, która obiekt o podanej klasie CSS zamieni na jednoliterowe elementy <span>, które z odpowiednim opóźnieniem będą wykonywały efekt zmiany koloru.
Początkowa templatka – HTML, CSS, JavaScript
Nasz HTML jest bardzo prosty, mamy w zasadzie pustą templatkę z elementem <h1> z odpowiednią klasą nadaną (przyda się później) oraz podpiętymi kodami CSS i JS:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="./test5.css">
</head>
<body>
<h1 class="each-letter-effect">My heading</h1>
<script src="./test5.js"></script>
</body>
</html>
JavaScript na razie pusty, zaś w CSS mamy reset stylów, wycentrowanie elementu w pionie i poziomie oraz podstawowy hover:
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
height: 100vh;
background-color: #ACE1AF;
display: flex;
justify-content: center;
align-items: center;
}
h1 {
text-transform: uppercase;
transition: all .5s;
}
h1:hover {
color: #004526;
}
Na razie nasz efekt działa na całym elemencie h1, czego nie chcemy – każda litera ma się animować osobno. Tym zajmiemy się w naszym JavaScript a potem dostosujemy CSS. Do dzieła.
Piszemy klasę EachLetterEffect – zarys naszego mini-projektu
Nasza klasa w formie takiego szkieletu będzie wyglądać mniej więcej tak, i choć może to nas przytłaczać na początku – nie powinno, to łatwiejsze, niż się wydaje:
class EachLetterEffect {
constructor(){
//łapie elementy z klasą .each-letter-effect
//przechodzi po każdym pętlą for..of
//prekazuje do turnContentToSpans, które przejmuje pałeczkę
}
turnContentToSpans(heading){
//przyjmuje heading
//zamienia każdą literę na listę jednoliterowych elementów
//czyści innerHTML
//dodaje fragment ospanowanych liter
//przekazując tablicę liter do
//createSpansFromArray
}
createSpansFromArr(arr){
//przyjmuje tablicę jednoliterowych elementów
//tworzy document fragment
//przechodzi w pętli map po tablicy
//dodaje do fragmentu span
//utworzony przez funkcję createSpanElement
//zwraca fragment
}
createSpanElement(letter, i){
//przyjmuje literę oraz indeks
//tworzy span element
//dodaje textContent - literę
//dodaje transition-delay - indeks * 40ms
//zwraca span
}
}
const effect = new EachLetterEffect();
Chodzi o to, aby nasza klasa w konstruktorze złapała wszystkie elementy o klasie CSS, jaką chcemy i przekazała do funkcji, która obrobi te elementy, zamieniając ich treść na jednoliterowe spany.
Piszemy konstruktor i główną funkcję naszej klasy
Okej, wiemy mniej więcej, co chcemy zrobić, pora napisać konstruktor:
constructor(){
const headings = document.querySelectorAll('.each-letter-effect');
for (const heading of headings) {
this.turnContentToSpans(heading);
}
}
Łapiemy wszystkie elementy o szukanej klasie CSS i przechodząc po nich w pętli przekazujemy funkcji głównej, która ma za zadanie obrobić je, zamienić treść na jednoliterowe elementy <span>.
Teraz nasza funkcja, a w zasadzie metoda:
turnContentToSpans(heading){
const lettersArr = heading.textContent.split('');
heading.innerHTML = '';
heading.append(this.createSpansFromArr(lettersArr));
}
Przyjmuje heading przekazany w pętli. Z textContent pobiera litery i zamienia na tablicę jednoliterową przez split. Czyści innerHTML i dodaje fragment HTML ze spanami utworzony przez funkcję pomocniczą, której tablicę liter przekazuje.
Funkcje pomocnicze – createSpansFromArr oraz createSpan
Pora napisać funkcję createSpansFromArr, która przyjmie tablicę liter i utworzy document fragment pełen elementów <span>, który dodamy w miejsce zawartości tekstowej w funkcji głównej:
createSpansFromArr(arr){
const fragment = new DocumentFragment();
arr.map((letter, i) => {
fragment.append(this.createSpanElement(letter, i));
});
return fragment;
}
Tworzymy fragment, który potem zwracamy. Przez map przechodzimy po tablicy liter i dodajemy do fragmentu span, który utworzy druga funkcja pomocnicza, przyjmując literę i indeks (będzie potrzebny do wyliczania opóźnienia animacji).
Druga funkcja pomocnicza, tworząca jeden element span:
createSpanElement(letter, i){
const span = document.createElement("span");
span.textContent = letter;
span.style.transitionDelay = `${i*40}ms`;
return span;
}
Wielkiej filozofii tu nie ma – przyjmuje literę i indeks, tworzy span, textContent to litera, opóźnienie to indeks razy 40ms, zwraca span. Ten element jest dodawany razem z innymi w pierwszej funkcji pomocniczej do fragmentu, który zwracany jest i przekazywany do naszej głównej funkcji, która podmienia tekst na ospanowane litery.
Teraz nasz <h1> powinien wyglądać tak:
<h1 class="each-letter-effect">
<span style="transition-delay: 0ms;">M</span>
<span style="transition-delay: 40ms;">y</span>
<span style="transition-delay: 80ms;"> </span>
<span style="transition-delay: 120ms;">h</span>
<span style="transition-delay: 160ms;">e</span>
<span style="transition-delay: 200ms;">a</span>
<span style="transition-delay: 240ms;">d</span>
<span style="transition-delay: 280ms;">i</span>
<span style="transition-delay: 320ms;">n</span>
<span style="transition-delay: 360ms;">g</span>
</h1>
Pora nadać odpowiedni efekt w CSS.
h1 {
text-transform: uppercase;
}
h1 span {
transition: all .5s;
}
h1:hover span {
color: #004526;
}
Cała klasa oraz CSS – ostatni rzut oka
Nasza klasa w całości, wcale nie jest tak, że kod nieobiektowy byłby czytelniejszy, wręcz przeciwnie:
class EachLetterEffect {
constructor(){
const headings = document.querySelectorAll('.each-letter-effect');
for (const heading of headings) {
this.turnContentToSpans(heading);
}
}
turnContentToSpans(heading){
const lettersArr = heading.textContent.split('');
heading.innerHTML = '';
heading.append(this.createSpansFromArr(lettersArr));
}
createSpansFromArr(arr){
const fragment = new DocumentFragment();
arr.map((letter, i) => {
fragment.append(this.createSpanElement(letter, i));
});
return fragment;
}
createSpanElement(letter, i){
const span = document.createElement("span");
span.textContent = letter;
span.style.transitionDelay = `${i*40}ms`;
return span;
}
}
const effect = new EachLetterEffect();
Zaletą obiektowego podejścia jest to, że teraz możemy sobie ten efekt jedną linijką aktywować. Można też dokonywać w niej zmian, na przykład wprowadzając jakieś argumenty, które przyjmie konstruktor i odpowiednio przekaże (np. więcej kontroli nad opóźnieniem albo możliwość przekazania własnej klasy CSS).
Nasza klasa zamienia treść elementu HTML na szereg elementów <span> zawierających jeden znak i odpowiednie opóźnienie, a taki szereg łatwiej animować, co czynimi w naszym kodzie CSS:
(...)
h1 {
text-transform: uppercase;
}
h1 span {
transition: all .5s;
}
h1:hover span {
color: #004526;
}