Zmienne CSS z animacjami 😶🌫️
Ten artykuł jest też dostępny w języku angielskim. Jeżeli chcesz to
Kiedy budujemy aplikacje typu single-page np. z pomocą Reakta to czasami zdarza się mieszać style z logiką JavaScripta. To nie jest dobre podejście ponieważ, to jak strona powinna wyglądać powinno należeć tylko do CSSa. Zwróćmy uwagę na pewien przykład.
export const EmojisList = () => {
const emojis = [
{ id: 1, emoji: '🥰' },
{ id: 2, emoji: '😎' },
{ id: 3, emoji: '🤨' },
{ id: 4, emoji: '🤗' },
{ id: 5, emoji: '😳' },
];
return (
<ul className="list">
{emojis.map(({ id, emoji }, index) => (
<li key={id} className="list__item">
I am {index + 1} emoji {emoji}
</li>
))}
</ul>
);
};
W powyższym przykładzie mapujemy tablicę z emoji a następnie je wyświetlamy, nic skomplikowanego. Ale uznajmy, że chcemy dodać tutaj jakąś super animacje, żeby elementy ujawniały się kolejno jeden za drugim (ang. staggered animation). Można każdemu z nich dodać odpowiednią klasę a następnie w stylach prawidłowo to obsłużyć.
return (
<ul className="list">
{emojis.map(({ id, emoji }, index) => (
<li key={id} className={cx('list__item', `list__item--${index}`)}>
I am {index + 1} emoji {emoji}
</li>
))}
</ul>
)
Elementom dodajemy klasy z przyrostkiem index
. Tylko tutaj pojawia się pewien problem, ponieważ trzeba tę każdą klasę dodać do pliku CSS 🤨
.list__item--1 {
animation-delay: 0.3s;
}
.list__item--2 {
animation-delay: 0.6s;
}
.list__item--3 {
animation-delay: 0.9s;
}
/* ... */
.list__item--123 {
/* Czy naprawdę potrzebujemy tyle elementów? 😫 */
}
Jak widać to rozwiązanie jest nawet dobre, ale tylko gdy nie ma wiele elementów. Jedną opcją, żeby ten problem uprościć może być zrezygnowanie z tworzenia nowych klas i skorzystanie z selektora nth-child()
. Ale dlugość pliku CSS dalej będzie taka sama. Z drugiej strony jeżeli jest potrzeba dodać kolejny element do listy to trzeba stworzyć nową klasę co bardzo utrudnia. To może powinniśmy dodać style bezpośrednio w plikach JavaScripta?
const DELAY = 0.3;
return (
<ul className="list">
{emojis.map(({ id, emoji }, index) => (
<li
key={id}
className="list__item"
style={{
animationDelay: `${index * DELAY}s`,
}}
>
I am {index + 1} emoji {emoji}
</li>
))}
</ul>
);
To już wygląda o wiele lepiej. Ale tak jak pisałem na początku tego artykułu, dodawanie reguł CSSa z poziomu JavaScripta nie jest dobre. Dlatego tutaj przychodzą nam z pomocą zmienne CSSowe.
return (
<ul className="list">
{emojis.map(({ id, emoji }, index) => (
<li key={id} className="list__item" style={{ '--i': index } as CSSProperties}>
I am {index + 1} emoji {emoji}
</li>
))}
</ul>
);
Jak widać zmienne CSS są naprawde bardzo pomocne. W powyższym przykładzie do każdego elementu przypisywana jest zmienna --i
z wartością aktualnego indeksu do którego póżniej można odnieść się z poziomu CSSa. Inaczej mówiąc z poziomu styli wiadomo jaki aktualny element listy jest brany pod uwagę.
Jeżeli robisz ten przykład z Reaktem i TypeScriptem dodaj asercje typu as CSSProperties
ponieważ inaczej kompilator nie pozwoli przepuścić Ci tej zmiennej.
.list__item {
--delay: 0.15s;
--duration: 0.3s;
/* Your custom animation */
animation-name: slide-in;
animation-fill-mode: both;
animation-duration: var(--duration);
animation-timing-function: ease-in-out;
animation-delay: calc(var(--i) * var(--delay));
}
Teraz wszystko znajduję się w pliku CSS i ilość emoji już nie jest problem. Zmienna --i
może być używana w obliczeniach 🤯