Zmienne w css, ewolucja czy rewolucja?

18.02.2022 | Autor: Przemysław

Co to są zmienne ?

Zwięźle i upraszczając, w programowaniu zmienną jest konstrukcja składająca się z dwóch podstawowych atrybutów: nazwy i wartości.
Technicznie sprawa jest nieco bardziej skomplikowana, gdyż sama nazwa i jej wartość nie wystarczy. Zmienne posiadają również takie atrybuty jak miejsce przechowywania oraz typ, określający rodzaj przypisanych danych.
Z praktycznego punktu widzenia zazwyczaj interesuje nas tylko nazwa i wartość, choć przy zaawansowanym programowaniu gdzie zmienne ulegają modyfikacją, bardzo ważną kwestią jest również typ, na podstawie którego można manipulować zmiennymi.
Tworzenia zmiennych daje nam nieograniczone możliwości ich późniejszego wykorzystania w kodzie w dowolny sposób. Możemy je modyfikować, możemy je przypisywać do innych zmiennych, możemy je wyświetlać. Zmiennymi mogą być zbiory danych takie jak tabele czy obiekty, jak również fragmenty kodu, funkcje i procedury.
W różnych językach programowania sama deklaracja zmiennej może się różnić, ale wszędzie idea pozostaje ta sama.
Więcej o zmiennych możemy poczytać tu: zmienne-wiki

CSS

Kaskadowe arkusze stylów (ang. Cascading Style Sheets „CSS”) są uproszczonym językiem, a raczej zbiorem dyrektyw określających warstwę prezentacji dokumentu HTML.
Na przestrzeni niespełna dwudziestu paru lat rozwoju CSS, opracowanego przez W3C w 1996 r., ewoluowało z prostej listy dyrektyw określających wygląd podstawowych elementów/znaczników (x)Html’owych do dzisiejszej, bardziej zaawansowanej, wersji ze sporymi możliwościami, zbliżającymi się do innych zaawansowanych języków programistycznych, czerpiąc i standaryzując pewne rozwiązania znane od lat w innych językach, jak i preprocesorach (np. Less, Sass) poszerzających możliwości zwykłego arkusza stylów.
Aktualnie najpopularniejszą wersją jest trzecia (CSS 3), która zachowuje pewną kompatybilność z poprzednimi, ale w odróżnieniu od poprzednich nie jest już jednym pełnym dokumentem. Została podzielona na oddzielne moduły, które niezależnie od stopnia opracowania, czy stabilności innych, mogą być publikowane jako obowiązujące standardy.

CSS-var

Jednym z takich modułów, opisywanych w tym artykule, są właśnie zmienne, które całkiem niedawno wyszły z fazy eksperymentalnej do fazy pierwszej, z całkiem niezłym wspierciem w większości nowoczesnych przeglądarek desktopowych i mobilnych. (css-vars)
Wprowadzenie zmiennych, na chwilę obecną, umożliwia nam na ich podstawową funkcjonalność. Dzięki nim możemy zapisywać konkretne interesujące nas wartości, przykładowo dowolny kolor, który potem możemy wykorzystać do ustawienia fontu, tła, czy obramowania.
Deklaracja zmiennej polega na nadaniu jej konkretnej nazwy z zastrzeżeniem, że musi zaczynać się od dwóch myślników, czyli: --[nazwa]: [wartość];

Podobnie jak w innych językach zmienne mogą być globalne, dostępne dla całego arkusza, zapisując je w dyrektywie :root :root { --color: red; } lub lokalne dla konkretnej deklaracji header { --color: red; }.
Odwołujemy się do zmiennych poprzez dyrektywę var(--nazwa-zmiennej)
Zmienne są dziedziczone kaskadowo jak inne dyrektywy w CSS.

Teraz zaczyna się cała zabawa i magia, zmienną może być dowolna wartość lub zbiór i możemy je dowolnie modyfikować.
Przykładowo możemy w zmiennej zapisać url obrazka:

:root {
    --image-url: "https://picsum.photos/id/1/1200/640";
    --image-from-somewhere: url("https://picsum.photos/id/1/1200/640");
    --image-from-local: url("css2.jpg");
    --image-base: url("data:image/svg+xml;base64,PHN2ZyBjbGFzcz0id2hpdGUiIHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAwIDE0NiAzMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4gPGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4gPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTEpIiBmaWxsPSIjZmZmIj4gPGcgY2xhc3M9ImFuaW1hdGlvbiBibGluayIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTI5IDI2KSIgZmlsbC1ydWxlPSJub256ZXJvIj4gPHJlY3QgeD0iLjU0MiIgeT0iLjUzMiIgd2lkdGg9IjE2LjgwMyIgaGVpZ2h0PSIzLjczNCI+PC9yZWN0PiA8L2c+IDxwYXRoIGQ9Im01NS4zMDcgMjEuODIycy0yLjAyMiA0LjIyMy0zLjkxMSA0LjIyM2MtMS4wNjg0LTAuMDQ1NzExLTEuOTk3My0wLjc0Ny0yLjMzNC0xLjc2MmwtMi4yMjMtNC4yMDljLTAuMzA0MTEtMC42OTYzOS0wLjgyNTg4LTEuMjc1NC0xLjQ4Ny0xLjY1di0wLjA4MWMwLjYzOTc1LTAuNDI2MjMgMS4xNzU5LTAuOTkwMzggMS41NjktMS42NTFsNS4zMzEtNy45MzRoLTUuODIybC00LjMgNi42NDdjLTAuNDk0ODQgMC41Njg4Ni0xLjIzNDcgMC44NjI1OC0xLjk4NSAwLjc4OGgtMS41Njl2LTE1Ljc0aC01LjI1djI5LjM2OWg1LjI1di05LjIxMmgxLjM0NWMwLjY2MiAwIDEuNTQ3IDAuMDM3IDEuOTQxIDAuNzg3bDIuODkyIDUuNTE4YzAuODg3MjcgMS44NzA4IDIuODE3NSAzLjAxOTUgNC44ODUgMi45MDcgMi44IDAgNC44MjYtMS45NDggNi4xNjQtMy45MTkgMC42MjEyNiAyLjM5MjggMi44MzAxIDQuMDI2MiA1LjMgMy45MTkgMi4zMTIxLTAuMTI4NjMgNC40MjY1LTEuMzQ1IDUuNy0zLjI3OXYzLjI3OWg1LjIwNXYtOS42NjZjLTAuMDEyOTQ4LTAuOTUyOTIgMC4xMjc1MS0xLjkwMTcgMC40MTYtMi44MSAwLjY4MDgzLTIuNTU3MiAzLjAxMTktNC4zMjUxIDUuNjU4LTQuMjkxIDIuNjQ3IDAgMy4zMDkgMS43MzIgMy4zMDkgNC4yOTF2NS4zNzVjMCAwLjkyMiAwLjEzNCA3LjEgNS44IDcuMSAyLjk1OSAwIDUuMDU2LTIuMTc5IDYuMzk1LTQuMjY4IDEuNzc2IDIuODYyIDUuMDg1IDQuNzY2IDEwLjIzIDQuNzY2IDUuMzkxIDAgOS41LTQuMjIzIDEwLjY2OS01LjUxIDAuNDI0IDIuMTE5IDEuNzEgNS4wMTIgNS41OTEgNS4wMTIgNS42NjYgMCA4LjE4Ni04IDguMTg2LThoLTIuNTIxcy0yLjAyMiA0LjIyMy0zLjkxMSA0LjIyMy0yLjUyMS0xLjg4OS0yLjUyMS0zLjE0NSAwLjEzNC01LjggMC4xMzQtNS44bDAuMDc0LTQuMTcyaDQuOHYtNC4xNzFoLTQuOHYtNS43NDdoLTUuMTE3djUuNzQ3aC0yLjg0OHY0LjE3MWgyLjcyOXY4LjQyNWMtMC4zNzIgMC40NTQtNC4zMzUgNS4xNzUtMTAuMDYxIDUuMTc1LTQuNDQ2IDAtNi44MzMtMi42MDktNy4yMi02LjMyN2gxMy42ODFzMC4xMTktMS4zMjMgMC4xMTktMS45NGMwLTUuNTc2LTMuMTA3LTEwLTguNzg4LTEwLTUuOTQxIDAtMTAuMDYgNC41NDMtMTAuMDYgMTEuMDM0LTAuMDA4NjA1IDEuMTU2MiAwLjEzMzU5IDIuMzA4NiAwLjQyMyAzLjQyOC0wLjcxMyAxLjI0Mi0yLjEgMy4zMjMtMy40MjcgMy4zMjMtNi43MjIgMCAzLjgxNC0xNy43ODUtOS42NjYtMTcuNzg1LTIuOTg5NS0wLjA4Njc1NC01Ljc4NDEgMS40NzkxLTcuMjcxIDQuMDc0di0zLjU3NmgtNS4yMDV2MTMuMDY0YzAgMC4xMTEtMi4wMjkgNC4yMjMtMy45NDggNC4yMjMtMS44ODggMC0yLjUxMy0xLjg4OS0yLjUxMy0zLjE0NSAwLTQuNyAwLjE0OS05LjQyOSAwLjE0OS0xNC4xNDNoLTUuMTlsM2UtMyAxMy4wNjV6bS01NC4zMDctNi44N2MtMC4wODg5MzQgNS40NzQ4IDIuNzgwOSAxMC41NzIgNy41MDggMTMuMzM2IDQuNzI3MSAyLjc2MzQgMTAuNTc3IDIuNzYzNCAxNS4zMDQgMHM3LjU5Ny03Ljg2MDkgNy41MDgtMTMuMzM2Yy0wLjEzNDQ1LTguMjc2Ni02Ljg4MjMtMTQuOTE2LTE1LjE2LTE0LjkxNi04LjI3NzcgMC0xNS4wMjYgNi42MzkxLTE1LjE2IDE0LjkxNnptNS42MTMgMGMtMC4xMzM5NS0zLjQ5NjYgMS42NTUxLTYuNzg2NCA0LjY2MjgtOC41NzQ1IDMuMDA3OC0xLjc4ODEgNi43NTI2LTEuNzg4MSA5Ljc2MDMgMCAzLjAwNzggMS43ODgxIDQuNzk2OCA1LjA3NzkgNC42NjI4IDguNTc0NSAwIDUuODY2LTQuMjUyIDEwLjMyNy05LjU0IDEwLjMyN3MtOS41NDYtNC40NjEtOS41NDYtMTAuMzI3em00OC45MzgtOS42MjFoNC41NTF2LTQuODMxaC00LjU1MXY0LjgzMXptNDEuNjA3IDExLjIzNGMwLjU0My0yLjY0NyAyLjMzNS00LjI1MyA0Ljg1NS00LjI1MyAyLjAyMiAwIDMuNjU4IDEuNzMyIDMuNzMyIDQuMjUzaC04LjU4N3oiPjwvcGF0aD4gPC9nPiA8L2c+IDwvc3ZnPgo=");
}

.lorem1 {
    background-image: url(var(--image-from-local));
}

.lorem2 {
    background-image: var(--image-from-somewhere);
}

.lorem3 {
    background-image: var(--image-from-local);
}

.lorem4:before {
    content: var(--image-base);
}

Mogą to również być dowolne inne wartości, przykłądowo wartości pixelowe.

:root {
    // zbiór wartości dla każdej ze stron elementu;
    --all-paddings: 10px 15px 20px 15px;

    // zbiór wartości dla jednej ze stron elementu;
    --bottom-padding: 10px;
}

.lorem1 {
    padding: var(--all-paddings);
}
.lorem2 {
    padding: 0 0 var(--bottom-padding) 0;
}

Zmienne mogą być gradientem, cieniem, filtrem lub schematem kolorów dostosowanym do preferencji użytkowników.

:root {
    --grad: linear-gradient(90deg, rgba(131,58,180,1) 0%, rgba(253,29,29,1) 50%);
    --basic-shad: 0 0 5px 5px rgba(255,255,255,1);
    --multi-shad: 0 0 5px 10px rgba(255,0,0,1), 0 0 5px 15px rgba(0,255,0,1), 0 0 5px 20px rgba(0,0,255,1);

    --f-shad: drop-shadow(8px 8px 10px gray);
    --f-invert: invert(100%);
}

.lorem1 {
    background: var(--grad);
    box-shadow: var(--basic-shad);
}
.lorem2 {
    box-shadow: var(--multi-shad);
}
.lorem3 {
    box-shadow: var(--basic-shad), var(--multi-shad);
}
.lorem4 {
    filter: var(--f-shad) var(--f-invert);
}

@media (prefers-color-scheme: light) {
    :root {
        --global-color: #0d2f52;
        --global-background: #fff;
    }
    body {
        background: var(--global-background);
        color: var(--global-color);
    }
}
@media (prefers-color-scheme: dark) {
    :root {
        --global-color: #fff;
        --global-background: #0d2f52;
    }
    body {
        background: var(--global-background);
        color: var(--global-color);
    }
}

Mogą być również wynikiem predefiniowanej funkcji, jak również bardziej zaawansowane, mogą zawierać w sobie inne zmienne lub wyniki innych funkcji.

:root {
    --calc-result: calc((100vw - 1920px) / 2);
    --center: 0 auto;

    --min-font: 16;
    --max-font: 90;
    --min-res: 320;
    --max-res: 1920;

    --font-dif: calc(var(--max-font) - var(--min-font));
    --res-dif: calc(var(--max-res) - var(--min-res));
    --advanced-font-size: calc( (var(--min-font) * 1px) + var(--font-dif) * (100vw - (var(--min-res) * 1px)) / var(--res-dif) );
}

.lorem1 {
    display: block;
    width: 100%;
    max-width: var(--calc-result);
    margin: var(--center);
}

.lorem2 {
    font-size: var(--advanced-font-size);
}

Nawet jesteśmy w stanie zrobić uproszczony counter/zegar, wykorzystując kilka tricków związanych z animacją i zmiennymi. Niestety nie jest on idealny, gdyż same animacje w css nie dają nam gwarancji na dokładność czasu, ale o tym w następnym artykule.

<!--
/*
    definiujemy nową własność będącą liczbą całkowitą
    z początkową wartością = 0
*/
@property --num {
    syntax: '<integer>';
    inherits: true;
    initial-value: 0;
}
/*
    definiujemy animacje
*/
@keyframes sec {
    0% { --num: 0; }
    100% { --num: 60; }
}
@keyframes min {
    0% { --num: 0; }
    100% { --num: 60; }
}
@keyframes ho {
    0% { --num: 0; }
    100% { --num: 24; }
}

/*
    i tworzymy counter
*/
.counter *:before {
    counter-reset: ho var(--num) min var(--num) sec var(--num);

    animation-delay: 0s;
    animation-timing-function: linear;
    animation-iteration-count: infinite;

    content: '';
    display: inline-block;
}
.counter .h:before {
    content: counter(ho);

    animation-name: ho;
    animation-duration: 86400s;
}
.counter .m:before {
    content: counter(min);

    animation-name: min;
    animation-duration: 3600s;
}
.counter .s:before {
    content: counter(sec);

    animation-name: sec;
    animation-duration: 60s;
}
-->

Modyfikowanie i operacje na zmiennych

W kwestii modyfikacji nie ma dużej filozofii, jeśli tego potrzebujemy, możemy w dowolnym miejscu nadpisać wcześniej zadeklarowaną wartość na inną, z naturalnymi konsekwencjami, głębiej w drzewie deklaracji nowa wartość będzie dziedziczona.
Możemy je modyfikować z poziomu CSS, jak i wykorzystując do tego JavaScript.

:root {
    --basic-var: 10px;
    --pos-x: 0;
    --pos-y: 0;
}

.dot {
    position: absolute;
    top: var(--pos-y);
    left: var(--pos-x);

    padding: var(--basic-ver);
}
.lorem2 {
    --basic-var: 15px;
    padding: var(--basic-ver);
}

Modyfikacji zmiennej przez JavaScript dokonujemy w następujący sposób.

let doc = document.documentElement;
let elem = document.querySelector(".dot");

doc.addEventListener("mousemove", e => {
    elem.style.setProperty('--mouse-x', e.clientX + "px");
    elem.style.setProperty('--mouse-y', e.clientY + "px");
});

Ponadto, możemy jeszcze modyfikować zmienn,e wykorzystując do tego predefiniowane funkcje CSS jak w poprzednich przykładach lub jak poniżej:

:root {
    --zmienna1: 100vw;
    --zmienna2: 1024px;

    --container-width: min(var(--zmienna1), - var(--zmienna2));

    --font-size: 14px;
    --scale-factor: 1.2;

    --fz: calc(var(--font-size) * var(--scale-factor));
}

.lorem1 {
    max-width: var(--container-width);

    font-size: var(--fz);
    line-height: var(--scale-factor);
}

Podsumowanie

Jak dla mnie, na chwilę obecną, brakuje jeszcze możliwości zapisywania całych bloków styli z wieloma deklaracjami odpowiadających za wygląd kompletnych elementów, które później można byłoby przekazywać innym elementom i ewentualnie modyfikować w zależności od potrzeb.
Brakuje mi również możliwości definiowania własnych funkcji w celu zwrócenia konkretnych wartości opartych na dowolnej liczbie danych wejściowych, ale zapewne i to kiedyś wejdzie do standardów.

Same zmienne wiele nie zmieniają przy drobnych projektach, dają pewne możliwości i ułatwienia w organizacji kodu, i ewentualnych przyszłych zmian.
W większych projektach i tak zazwyczaj do organizacji i optymalizacji kodu wykorzystujemy różnego rodzaju frame-worki, preprocesory, i inne narzędzia usprawniające cały proces budowania strony internetowej/ sklepu,
natomiast ciągły rozwój urządzeń, przeglądarek, technologi, wymusza takie zmiany na podstawowych rzeczach jak arkusz styli.
Czy tego typu innowacje zrewolucjonizują całkowicie podejście do spraw frontendu i budowania layoutów?
Pojedynczo raczej nie, jednak w połączeniu z całą resztą dają spore możliwości.
Nie bójcie się eksperymentować, a jeśli czegoś nie da się zrobić, zlećcie to nam ;).
Do następnego pzdr.

Porozmawiaj z nami
o swoim projekcie

+48 506 160 480
[email protected]

lub napisz