Promenljive u CSS-u (pretprocesori i imenovana svojstva)
Uvod
Nedugo nakon što je CSS ušao u upotrebu i zauzeo mesto kao jedna od osnovnih tehnologija u web dizajnu, počelo je da se "razmišlja naglas" o tome da samom jeziku nedostaju promenljive (preko kojih bi CSS kodovi, koji su uglavnom čitljivi i pregledni - mogli postati još pregledniji).
Realizacija ideja o uvođenju promenljivih 'tapkala je u mestu' u početku, ali, posle izvesnog vremena (sredinom 2000-ih), prvo su u upotrebu uvedeni pojedini pretprocesori za CSS (od kojih su neki popularni i dan-danas), a 2017. godine (mnogi programeri bi rekli "konačno"), u specifikaciju samog jezika CSS uvrštena su i tzv. imenovana svojstva - "ne baš prave promenljive", ali - "prilično blizu".
U nastavku, osvrnućemo se na prednosti i (ni izdaleka 'zabrinjavajuće') nedostatke, oba navedena pristupa ....
Svrha promenljivih u CSS-u
Pretpostavljamo da je sasvim jasno na koji način bi upotreba promenljivih u CSS-u doprinela unapređenju preglednosti koda (u opštem smislu) i olakšala posao programerima, ali, razmotrićemo ipak (u svojstvu primera), jednu jednostavnu i uobičajenu situaciju.
Zamislićemo da se boja #4477ee koristi na određenom sajtu - za više različitih elemenata (prikazaćemo kod za navigacionu traku, dugmad i linkove, ali, zamislićemo da selektora ima mnogo više):
nav.#traka_header {
background: #4477ee;
}
.dugme {
background: #4477ee;
}
.linkovi {
color: #4477ee;
}
Problem može nastati ukoliko je potrebno naknadno menjati navedeni hex kod za boju, na svim mestima, a glavno pitanje je (naravno), kako najefikasnije rešiti problem.
Posao ažuriranja CSS koda svakako bi se mogao obavljati ručno - ako bi kod doslovno obuhvatao jednu vrednost i tri selektora, ali, za razliku od gornjeg primera, na ozbiljnijim sajtovima slični selektori se javljaju u mnogo većem broju (i gotovo je sigurno da su raspoređeni u nekoliko različitih CSS datoteka), što praktično znači da posao ažuriranja - nije baš "lagan i kratkotrajan".
U programskim jezicima kao što su C/Java/Python i sl, sličan problem rešili bismo upotrebom promenljivih, ili imenovanih konstanti (pri čemu bi određena promenljiva ili konstanta bila deklarisana i inicijalizovana na jednom mestu, na drugim mestima u kodu pozivali bismo se na navedenu promenljivu ili konstantu, a ažuriranje vrednosti tipično bi podrazumevalo izmenu u okviru jedne linije koda).
Na primer (u programskom jeziku C) ....
#include<stdio.h>
#define GRANICA 200
void main()
{
int i, n
scanf("%d", &n);
if (n > GRANICA) {
printf("Uneta vrednost prelazi dozvoljenu granicu!");
return;
}
int a[n];
for (i = 0; i< n; i++) {
scanf("%d", &a[GRANICA]);
}
}
.... vrednost promenljive (preko koje se određuje broj elemenata niza), unosi korisnik, a maksimalni broj elemenata određen je preko imenovane konstante GRANICA, i pri tom se obe vrednosti mogu menjati (na veoma pregledan način).
Razmotrićemo koliko se u okvirima CSS-a možemo približiti prikazanom nivou 'fleksibilnosti', za početak, uz upotrebu pretprocesora.
CSS pretprocesori
CSS pretprocesori su programi koji omogućavaju generisanje ('običnih') CSS datoteka, korišćenjem sintakse koja predstavlja kombinaciju: standardnog CSS-a, imenovanih promenljivih, i drugih jezičkih konstrukcija.
Da bismo što bolje razumeli pretprocesore koji se specifično koriste za sintaksu CSS-a, podsetićemo se prvo, preko primera sa kakvima smo se susretali u ranijim člancima, čemu (inače) služe pretprocesori pri prevođenju računarskih jezika.
Opšta uloga pretprocesora u računarskim jezicima
U svojstvu primera, osvrnućemo se na to kako pretprocesor u programskom jeziku C reaguje na pretprocesorske direktive #include i #define (koje smo koristili u prethodnim primerima):
- direktiva
#includepodrazumeva uključivanje (to jest 'uvoz') određene biblioteke (u gornjem primeru, uvezena je bibliotekastdio.h, koja sadrži funkcijeprintfiscanf) - direktiva
#definepredstavlja uputstvo pretprocesoru da svaku pojavu niskeGRANICA(u programskom kodu), treba zameniti niskom20- pre početka drugih operacija u prevođenju koda
Direktiva #include funkcioniše (manje-više) po principu "copy-paste", to jest, doslovno podrazumeva kopiranje sadržaja biblioteke u programski kod koji je korisnik zapisao (to nećemo prikazivati), dok direktiva #define (za nas u ovom trenutku mnogo zanimljivija), svodi npr. sledeći kod ....
if (n > GRANICA) {
// ....
}
.... na konačni oblik:
if (n > 20)
// ....
}
Pošto smo razumeli prethodno opisani princip, takođe neće biti teško da razumemo (skoro nimalo različit) princip funkcionisanja CSS pretprocesora.
Princip funkcionisanja CSS pretprocesora (osnovne mogućnosti)
U skladu sa prethodno navedenim principima, osnovni koncept upotrebe pretprocesora zarad generisanja CSS kodova, može se opisati na sledeći način:
- na početku je potrebno definisati "promenljive" - preko pretprocesorskih direktiva (kao i obično, promenljive su predstavljene preko identifikatora, koji su povezani sa konkretnim vrednostima)
- u "posredničkom CSS kodu" * se zatim mogu pozivati identifikatori prethodno definisanih "promenljivih" (a moguće je koristiti i druge jezičke konstrukcije (na šta ćemo se osvrnuti u sledećem odeljku članka))
- pre objavljivanja sajta, potrebno je pokrenuti CSS pretprocesor koji će kompajlirati 'posrednički' kod i kreirati običan CSS kod (u kome su imenovane promenljive, koje su prethodno bile zadate preko identifikatora - zamenjene pripisanim vrednostima)
Za primer ćemo koristiti sintaksu popularnog pretprocesora SASS (Syntactically Awesome Style Sheet).
Svojstva se definišu u datoteci sa ekstenzijom .sass ....
$pozadina_body: #fff;
$pozadina_dugme: #ea4;
$boja_teksta_body: #202020;
$boja_teksta_dugme: #fff;
body {
background: $pozadina_body;
color: $boja_teksta_body;
}
.dugme {
background: $pozadina_dugme;
color: $boja_teksta_dugme;
}
.... i potom se .sass datoteka (po potrebi) kompajlira preko pretprocesora - pri čemu je rezultat običan CSS kod ....
body {
background: #fff;
color: #202020;
}
.dugme {
background: #ea4;
color: #fff;
}
.... koji se dalje može koristiti isto kao i bilo koji standardni CSS kod koji je "pisan ručno".
Nekoliko dodatnih mogućnosti pretprocesora SASS
Iako članak nije posvećen pretprocesoru SASS, pogledajmo još nekoliko (naprednijih) mogućnosti ovog pretprocesora.
Recimo, preko tzv. mixin klasa (i direktive @include), može se lako kreirati nekoliko sličnih CSS selektora.
Prvo je potrebno zapisati kod u datoteci sa ekstenzijom .sass ....
@mixin dugme_generator($sirina, $visina, $pozadina) {
width: $sirina;
height: $visina;
background: $pozadina;
border: none;
font-weight: bold;
}
.dugme_crveno {
@include dugme_generator(240px, 56px, #e74);
}
.dugme_zeleno {
@include dugme_generator(240px, 56px, #4e7);
}
.dugme_plavo {
@include dugme_generator(240px, 56px, #47e);
}
.... posle čega se kod "provlači" kroz pretprocesor, što kao rezultat daje sledeći "običan" CSS kod:
.dugme_crveno {
width: 240px;
height: 56px;
background: #e74;
border: none;
font-weight: bold;
}
.dugme_zeleno {
width: 240px;
height: 56px;
background: #e74;
border: none;
font-weight: bold;
}
.dugme_plavo {
width: 240px;
height: 56px;
background: #e74;
border: none;
font-weight: bold;
}
Verujemo da nije teško uvideti svu lepotu pristupa koji podrazumeva generisanje običnog CSS koda (koji se može direktno koristiti u browseru), preko naočitog, sažetog koda sa imenovanim vrednostima (praktično - promenljivama), i različitim obrascima koji dodatno olakšavaju generisanje CSS kodova.
Međutim, CSS pretprocesori imaju i jednu (očiglednu) "manu": izrada sajta uz upotrebu pretprocesora zahteva instalaciju i upotrebu dodatnog specijalizovanog programa (SASS takođe zahteva i instalaciju Ruby servera), što je možda nešto što, određeni web developeri, u određenim situacijama - ipak žele da izbegnu.
Imenovana svojstva (custom properties) i funkcija var
Ukoliko pristup koji zahteva dodatne programe (SASS, Ruby i sl), predstavlja problem, zbog čega je potrebno ostati u okviru onoga što nudi sam CSS (bez dodataka), moguće je definisati imenovana svojstva (to jest - "prilagođena svojstva"), koja se naknadno mogu pozivati preko funkcije var.
Navedeni pristup ne zahteva pretprocesor (što je svakako prednost), ali, i u slučajevima kada se koriste imenovana svojstva, postoji jedan nezanemarljiv nedostatak - imenovana svojstva nisu podržana u starijim verzijama browsera (pre svega mislimo na Internet Explorer, pre verzije Edge).
Definisanje i pozivanje imenovanih svojstava
Sav kod koji se tiče imenovanih svojstava, može se zapisati u jednoj CSS datoteci, ali (kao i obično), preporučljivo je da se veći blokovi koda razdele u zasebne datoteke (zarad preglednosti), kao i to da najopštiji delovi koda budu izdvojeni u zasebnu konfiguracionu datoteku i sl:
:root {
--pozadina_body: #fff;
--pozadina_dugme: #ea4;
--boja_teksta_body: #202020;
--boja_teksta_dugme: #fff;
}
body {
background: var(--pozadina_body);
color: var(--boja_teksta_body);
}
.dugme {
background: var(--pozadina_dugme);
color: var(--boja_teksta_dugme);
}
U selektoru za pseudoklasu :root definisano je nekoliko imenovanih svojstava, koja se zatim - preko funkcije var - pozivaju u drugim selektorima (body i .dugme).
Ovoga puta, za razliku od situacije kada smo koristili pretprocesor, nema kompajliranja ("sve što treba je tu"), a browseri (računajući i Edge, novu verziju Internet Explorera), već duže vreme podržavaju imenovana svojstva - i stoga će elementi biti uredno prikazani.
Ili - možda neće?!
U (čuvenih) "99% slučajeva", gornji kod je dovoljan, ali (da se podsetimo): ukoliko se sajt otvara na starijem računaru koji koristi stariju verziju određenog browsera (pogotovo ako je u pitanju neka od starijih verzija Internet Explorera, kod kojih je podrška za imenovana svojstva 'demonstrativno izostavljena'), elementi neće biti prikazani na očekivani način.
Web dizajneri mogu odlučiti (iz praktičnih razloga), * da prestanu sa pružanjem podrške za starije verzije browsera, ili - u skladu sa popularnom pošalicom koja navodi da je Internet Explorer browser čija je svrha da omogući preuzimanje drugih browsera sa interneta - mogu i ukinuti podršku za starije verzije Internet Explorera; ali, postoje (naravno) elegantniji načini da se problem reši.
Prvi način da se "saniraju posledice nepodržanosti svojstava", može biti - korišćenje direktiva @supports i @supports not ....
@supports (--css: variables) {
:root {
--pozadina_body: #fff;
--pozadina_dugme: #ea4;
--boja_teksta_body: #202020;
--boja_teksta_dugme: #fff;
}
body {
background: var(--pozadina_body);
color: var(--boja_teksta_body);
}
.dugme {
background: var(--pozadina_dugme);
color: var(--boja_teksta_dugme);
}
}
@supports not (--css: variables) {
body {
background: #fff;
color: #202020;
}
.dugme {
background: #eb3;
color: #fff;
}
}
.... međutim - iako jesu u pitanju "zvanični" mehanizmi namenjeni rešavanju (baš) ovakvih situacija - jedan selektor (na primer body), mora se pojaviti u kodu dva ili više puta. *
Problemi u navedenom pristupu su sledeći:
- ako se kod zapiše dvaput (jednom unutar direktive
@supporta drugi put unutar direktive@supports not), oba puta mora se navesti sav kod - ako se zajednički delovi izdvoje u zaseban selektor, koji nije obuhvaćen direktivama
@supportsili@supports not(npr. onako kako smo prikazali u uvodnom članku o CSS direktivi @supports) - ponovo smo kreirali (bar) dva selektora
Drugi način (koji sam po sebi takođe nije 'skroz elegantan', ali ipak bismo rekli da je znatno elegantniji), praktično podrazumeva .... korišćenje opštih principa interpretacije CSS-a u browserima.
Ukoliko se u selektoru dvaput navede određeno svojstvo, prvo navođenje svojstva (zapravo, ne samo prvo, već "svako osim poslednjeg") - jednostavno će biti zanemareno (i time je problem podržanih/nepodržanih imenovanih svojstava, praktično rešen):
:root {
--pozadina_body: #fff;
--pozadina_dugme: #ea4;
--boja_teksta_body: #202020;
--boja_teksta_dugme: #fff;
}
body {
background: #fff;
background: var(--pozadina_body);
color: #202020;
color: var(--boja_teksta_body);
}
.dugme {
background: #eb3;
background: var(--pozadina_dugme);
color: #fff;
color: var(--boja_teksta_dugme);
}
Ukoliko browser podržava imenovana svojstva (koja su navedena), desiće se sledeće:
- pri prvoj dodeli vrednosti određenom svojstvu (uzećemo za primer boju pozadine dugmeta), browser će upamtiti vrednost (dugme dobija boju pozadine
#eb3) - pri drugoj dodeli vrednosti, prethodna vrednost biće zanemarena (dugme dobija novu boju -
#ea4)
Ukoliko browser (sa druge strane), ne podržava imenovana svojstva (ponovo posmatramo boju pozadine dugmeta), desiće se sledeće:
- pri prvoj dodeli vrednosti browser će upamtiti vrednost (dugme i ovoga puta dobija 'inicijalnu' boju pozadine
#eb3) - pri drugoj dodeli vrednosti - budući da browser ne prepoznaje funkciju
var- biće zanemaren ceo red CSS-a, i stoga će prethodna vrednost biti očuvana (boja dugmeta je i dalje#eb3)
Ovakav kod jeste pomalo redundantan (tj. nije baš skroz elegantan), ali - ukoliko je potrebno održati kompatibilnost sa starijim verzijama i/ili nepodržanim browserima - u pitanju je sasvim prihvatljiva opcija.
Opseg definisanosti imenovanih svojstava
Mogli ste primetiti da su u prethodnim primerima imenovana svojstva bila deklarisana unutar selektora :root - što znači da se navedena svojstva mogu koristiti u svim selektorima.
Pored navedenog pristupa (koji je daleko najuobičajeniji), imenovana svojstva mogu se definisati i unutar drugih selektora (najbolje ipak - i u takvim situacijama - na početku datoteke), čime se opseg definisanosti imenovanih svojstava sužava.
Ako definišemo sledeća imenovana svojstva ....
p {
--vertikalni razmak:
}
aside div > a {
--boja_slova: #47e;
--velicina_fonta_desktop: 12pt;
--velicina_fonta_mob: 11pt;
}
main {
--velicina_fonta_desktop: 13pt;
--velicina_fonta_mob: 12pt;
}
.... i pozovemo ih naknadno u drugim selektorima, dobićemo sledeći rezultat (objašnjenja su u komentarima):
main {
....
// U selektoru main (koji nije obuhvaćen
// media query direktivom), primenjujemo
// "mobile-first" pristup i definišemo
// veličinu fonta za mobilnu verziju sajta.
// Svojstvo će biti uredno prepoznato.
font-size: 12pt;
font-size: var(--velicina_fonta_mob);
font-weight. normal;
....
}
aside p {
....
margin: 20px 16px 24px 24px;
// Ovoga puta, budući da nismo ručno
// ažurirali vrednost atributa font-size
// i budući da imenovano svojstvo
// --velicina_fonta_mob važi samo za
// elemente u okviru tagova main, slova u
// paragrafima u okviru tagova aside će
// biti premala.
font-size: 10pt;
font-size: var(--velicina_fonta_mob);
....
}
aside h2 > a {
....
// Preko svojstva --velicina_fonta_mob,
// ovoga puta definišemo 11pt kao veličinu
// fonta (pri čemu se naravno koristi svojstvo
// --velicina_fonta_mob koje važi unutar
// selektora aside h2 > a), dok će u
// gornjem selektoru (main), veličina fonta
// biti 12pt.
font-size: var(--velicina_fonta_mob);
}
.dugme {
background: #eb3;
background: var(--pozadina_dugme);
color: #fff;
color: var(--boja_teksta_dugme);
}
Kao što smo već naveli, u praksi preovladava pristup koji podrazumeva imenovanje svojstava u okviru opsega :root, ali, razume se da dodatne opcije sasvim dobro dođu (i treba ih koristiti kada se ukaže potreba).
Za kraj ....
Namera nam je bila da u relativno neobimnom članku predstavimo dva pristupa koji omogućavaju upotrebu 'promenljivih' u CSS-u (na način koji je, doduše, ponešto različit u odnosu na pristup koji je svojstven jezicima kao što su C, Java i sl), međutim, ovoga puta nismo hteli da zalazimo u detalje (u većoj meri).
Napredne opcije CSS pretprocesora (sa jedne strane) i upotreba imenovanih svojstava u JavaScript-u (sa druge strane), svakako će biti teme kojima ćemo i u budućnosti posvećivati pažnju ....