Kategoria:CSS

Nowoczesny CSS - CSS Variables

Chociaż CSS nie jest językiem programowania, to posiada swoje zmienne. Czym są i jak się ich używa? Do jakich celów możesz je wykorzystać?

Zmienne CSSowe to, tak samo, jak w językach programowania, takie pudełka, do których możemy wpisać daną wartość/jednostkę. Następnie taką zmienną wykorzystujemy w wielu miejscach w naszym kodzie, unikając tym samym niepotrzebnych powtórzeń.

Spis treści

Jak to działa?

Każda zmienna musi zaczynać się od dwóch myślników. Podobnie jak z innymi właściwościami, po dwukropku podajemy jej wartość:

.box {
  --primary-color: purple;
}

Taką zmienną możemy później wykorzystać za pomocą var():

.box {
  --primary-color: purple;
  background-color: var(--primary-color);
}

Zasięg i dziedziczenie

Zmienne w CSSie, tak samo jak te w JavaScripcie, posiadają swój zasięg. Spójrzmy na przykład:

:root {
  --primary-color: pink;
}

.box {
  --primary-color: blue;
  background-color: var(--primary-color);
}

Skorzystaliśmy tutaj z pseudoklasy root i w niej zadeklarowaliśmy zmienną o takiej samej nazwie jak w klasie .box. Jaki będzie wynik? Tło naszego boxa będzie miało kolor niebieski!

Zobaczmy na bardziej zaawansowany przykład. Stwórzmy trzy boxsy, gdzie box-1 i box-2 będą rodzeństwem, a box-3 będzie dzieckiem tego pierwszego:

<div class="box-1">
  <div class="box-3"></div>
</div>
<div class="box-2"></div>

W :root nadajemy naszej zmiennej kolor różowy, zmienna o tej samej nazwie posiada kolor niebieski. Wszystkim divom ustawiamy kolor tła odpowiadający zmiennej --primary-color.

:root {
  --primary-color: pink;
}

.box-1 {
  --primary-color: blue;
}

.box-1,
.box-2,
.box-3 {
  background-color: var(--primary-color);
}

Jaki będzie wynik tym razem? Div z klasą box-1 będzie koloru niebieskiego, tutaj sytuacja wygląda dokładnie tak samo, jak poprzednio. Przez to, że box-3 jest dzieckiem wspomnianego box-1, to dostanie również kolor niebieski. Nasz drugi box nie może dostać koloru niebieskiego, więc patrzy wyżej i tutaj znajduję :root, z którego zaciąga zmienną z kolorem różowym.

Zmienne w praktyce!

Stan elementu

Świetnym przykładem wykorzystania zmiennych jest zwykły hover na przycisku.

.button {
  --primary-color: blue;
  background-color: var(--primary-color);
}

.button:hover {
  --primary-color: green;
}

Dzięki CSS Variables nie musimy już nigdy więcej powtarzać się w naszym kodzie, po prostu zmieniamy wartość zmiennej i całość magicznie działa, tło buttona zmieniło swój kolor!

Palety kolorów i gradienty

Podczas tworzenia nowej odsłony Frontlive miałem bardzo ciekawy problem do rozwiązania. Karty kategorii na blogu mają różne warianty kolorystyczne, ale wszystkie posiadają wspólne wartości takie jak kąt nachylenia. CSS Variables na ratunek!

:root {
  --gradient-angle: 120deg;
  --gradient-primary-color-percent: 99.6%;
  --gradient-secondary-color-percent: 13.5%;
  --purple-gradient: linear-gradient(
    var(--gradient-angle),
    var(--purple-gradient-accent) var(--gradient-secondary-color-percent),
    var(--purple) var(--gradient-primary-color-percent)
  );
  --yellow-gradient: linear-gradient(
    var(--gradient-angle),
    var(--yellow-gradient-accent) var(--gradient-secondary-color-percent),
    var(--orange) var(--gradient-primary-color-percent)
  );
}

Jak widzisz, do zmiennych CSSowych możemy podawać najróżniejsze jednostki! Tutaj tylko raz zadeklarowałem wartości dla nachylenia i procentów odpowiednich kolorów, tylko po to, aby je użyć ponownie we wielu wariantach karty.

Dark mode

Najbardziej znany przypadek wykorzystania zmiennych, nie ma co się dziwić to idealny case dla nich! Pomijając całą logikę po stronie JavaScripu, stworzenie ciemnego motywu to łatwizna!

:root {
  --background-color: white;
  --text-color: black;
}

[data-theme='dark'] {
  --background-color: black;
  --text-color: white;
}

Media queries

CSS Variables świetne sprawdzają się, jeśli chodzi o responsywne style. Tutaj bardzo podobnie jak przy hoverze, by zmienić margines dla nagłówka wystarczy, że zmienimy wartość zmiennej.

.heading {
  --heading-margin: 2rem;
  margin: var(--heading-margin);
}

@media screen and (min-width: 800px) {
  .heading {
    --heading-margin: 5rem;
  }
}

Fallback

Co się stanie, gdy podana przez nas zmienna nie istnieje? Po prostu nie zobaczymy pożądanego przez nas efektu. Może się tak zdarzyć, że będziemy chcieli zapewnić sobie 100% pewność i sprawić, że otrzymamy dokładnie to, co chcemy, nawet gdy dana zmienna nie istnieje.

Do wcześniej wspomnianego var() możemy przekazać również tzw. fallback, w przypadku, gdy zmienna nie istnieje, zostanie on wykorzystany.

.box {
  background-color: var(--primary-color, var(--secondary-color, black));
}

Na powyższym przykładzie, możesz zobaczyć, że możemy również zagnieżdżać zmienne i tworzyć kolejne poziomy abstrakcji nawet dla samego fallbacku.

Integracja z JS

Jedną z największych zalet, jak nie największą, jest możliwość pobierania i zmieniania zmiennych w JavaScripcie!

Do pobrania zmiennej wykorzystujemy metodę obiektu globalnego window getComputedStyle. Naszym argumentem będzie document.documentElement, który zwraca element będący bezpośrednim dzieckiem dokumentu. No i na sam koniec wykorzystujemy metodę getPropertyValue, do której podajemy nazwę naszej zmiennej.

:root {
  --primary-color: blue;
}
// blue
const primaryColor = getComputedStyle(document.documentElement).getPropertyValue('--primary-color');

Znacznie ciekawiej wygląda modyfikowanie zmiennej. Tutaj, naprawdę, ogranicza nas tylko nasz kreatywność. Tym razem również korzystamy z document.documentElement, ale trochę w innym kontekście. Tutaj dostajemy się do obiektu style, a w nim do metody setProperty, w której możemy modyfikować naszą zmienną.

Spójrzmy na przykład. W JavaScripcie nasłuchujemy na zdarzenie mousemove, następnie pobieramy clientX i clientY i ustawiamy je jako wartości dla wcześniej przygotowanych zmiennych. Dzięki temu nasz box będzie podążał za kursorem myszki.

:root {
  --mouse-x: 0px;
  --mouse-y: 0px;
}

.box {
  left: var(--mouse-x);
  top: var(--mouse-y);
}
const root = document.documentElement;

root.addEventListener('mousemove', ({ clientX, clientY }) => {
  root.style.setProperty('--mouse-x', `${clientX}px`);
  root.style.setProperty('--mouse-y', `${clientY}px`);
});

CSS vs Sass Variables

Możliwe, że kojarzysz zmienne dostępne w preprocesorach takich jak Sass. Te zmienne przegrywają jednak z tymi, o których dzisiaj mowa, praktycznie na każdym polu.

CSS Variables nie wymagają od nas użycia żadnego preprocesora, są kaskadowe, możemy z nich korzystać w devtoolsach. I najważniejsze, mamy do nich dostęp z poziomu JavaScriptu. Zmienne w Sassie, są zamieniane na normalne podczas kompilacji, nie zostaje po nich żaden ślad.

Zmienne w CSSie są lepsze praktycznie pod każdym względem i można by tutaj pewnie jeszcze wymieniać i wymieniać zalety z nich płynące. Musisz zapamiętać jedną rzecz - wybieraj zawsze CSS Variables, chyba, że wspierasz wymierające przeglądarki (Internet Explorer...).

Styled Components i zmienne

Osoby, które piszą w React.js, na pewno kojarzą rozwiązania typu CSS-in-JS, np. Styled Components. Takie rozwiązania posiadają tzw. theme, dla uproszczenia powiedzmy, że to taki odpowiednik naszego :root. To tutaj trzymamy nasze kolory i inne zmienne, które później z łatwością możemy wykorzystać. Theme oparty jest o Reaktowy Context.

Może rodzić się pytanie, co lepsze? CSS Variables czy Theme z Styled Components? Jakiś czas temu Kent C. Dodds napisał artykuł na ten temat.

Okazuję się, że theme bardzo słabo wypada, jeśli chodzi o performance, w porównaniu do CSS Variables. Kolejna wygrana po stronie naszego, natywnego rozwiązania.

Przedstawienie wyników wydajności, w profilerze, w dev toolsach, dla rozwiązania typu Theme w CSS-in-JS.Przedstawienie wyników wydajności, w profilerze, w dev toolsach, dla CSS Variables

Ciekawostki

Wartości URLa

W zmiennych możemy przechowywać najróżniejsze wartości, okazuję się, że nawet sam adres URL!

.box {
  --background-image: 'https://frontlive.pl/';
  background-image: url(var(--background-image));
}

Nie zadziała to jednak, gdy do zmiennej podamy adres wraz z samym url().

Wiele wartości

Wydawałoby się, że do zmiennej możemy podać tylko jedna wartość, cóż bardziej mylnego! Jeżeli wartości są poprawne, to możemy podać ich kilka! Dość trywialnym, ale pokazującym możliwości, jest przykład z podaniem wartości dla koloru w formacie rgba:

.text {
  --text-color: 255, 0, 92;
  color: rgb(var(--text-color));
}

Animacje

Przytoczę tutaj kolejny przykład z życia wzięty. Musiałem zaimplementować animację strzałki, przy najechaniu. Sama strzałka była oczywiście SVG i posiadała dwie linie. Każda z nich potrzebowała zostać zaanimowana podczas hovera. CSS Variables ponownie nie zawiodły!

.arrow {
  --arrow-hover-transition: 150ms cubic-bezier(0.215, 0.61, 0.355, 1);
  --arrow-hover-offset: translateX(3px);
  position: relative;
  top: 1px;
  margin-left: 8px;
  stroke-width: 2;
  fill: none;
  stroke: currentColor;

  &-line-path {
    opacity: 0;
    transition: opacity var(--arrow-hover-offset, var(--arrow-hover-transition));
  }

  &-tip-path {
    transition: transform var(--arrow-hover-offset, var(--arrow-hover-transition));
  }
}

Sprawa wygląda niestety znacznie inaczej, jeśli chodzi o animacje za pomocą keyframes. Zmienne CSSowe, na tą chwilę, po prostu nie mogą być do tego wykorzystane.

// ❌ nie zadziała

.box {
  width: 50px;
  height: 50px;
  --offset: 0;
  transform: translateX(var(--offset));
  animation: moveBox 1s infinite alternate;
}

@keyframes moveBox {
  0% {
    --offset: 0;
  }
  50% {
    --offset: 50px;
  }
  100% {
    --offset: 100px;
  }
}

Jako ciekawostkę ciekawostki dodam, że jest to możliwe, ale z wykorzystaniem CSS Houdini, a przykład wykorzystania możecie znaleźć na codepenie.

// ✅ wszystko okej

@property --offset {
  syntax: '<length-percentage>';
  inherits: true;
  initial-value: 0;
}

.box {
  width: 50px;
  height: 50px;
  transform: translateX(var(--offset));
  animation: moveBox 1s infinite alternate;
}

@keyframes moveBox {
  0% {
    --offset: 0;
  }
  50% {
    --offset: 50px;
  }
  100% {
    --offset: 100px;
  }
}

Wykorzystanie z clamp()

W poprzednim wpisie z tej serii pisałem o funkcjach logicznych w CSSie, jedna z nich to clamp(). Daje ona naprawdę spore możliwości, jeśli chodzi o tworzenie responsywnych styli i tzw. fluid typography. CSS Variables ponownie spisują się świetnie! Możemy zdefiniować zmienne dla minimalnej i maksymalnej wartości, następnie stworzyć zmienną z samym clamp() i całość zaserwować jako font-size.

.heading {
  --min: 3rem;
  --max: 8rem;
  --clamped-font-size: clamp(var(--min), 2.5vw, var(--max));
  font-size: var(--clamped-font-size);
}

DevToolsy

CSS Variables świetne współpracują z narzędziami developerskimi w przeglądarce! To naprawdę świetny feature, który przydał mi się nieraz podczas tworzenia projektu.

Ukazanie podglądu dla CSS Variables na różnych przeglądarkach(Chrome, Edge, Firefox i Safari)

Wsparcie przeglądarek

Wsparcie przeglądarek dla zmiennych CSSowych jest bardzo dobre. Nie muszę wspominać, że nie warto się przejmować przeglądarkami typu IE (chyba że naprawdę musimy...).

Wykres ze strony caniuse pokazujący wsparcie przeglądarek dla CSS Variables

Podsumowanie

CSS Variables to jedna z moich ulubionych rzeczy w całym CSSie. Zmienne dają nam naprawdę ogromne możliwości i warto z nich korzystać, gdzie tylko się da.

Na sam koniec zostawiam Cię z małym demem na codepenie, gdzie możesz przetestować opisywane dziś zagadnienia.

W przykładach, dla ułatwienia, kolory zostały podane jako nazwy, w prawdziwym zastosowaniu zalecałbym skorzystanie z innych formatów (hex,rgba,hsl).

Do usłyszenia!

Źródła