Wprowadzenie do SASS

Wprowadzenie do SASS

Tworząc aplikacje internetowe niezależnie czy są to strony internetowe, sklepy czy systemy wewnętrzne. W pewnym momencie można zauważyć jak bardzo rozbudowane stają się arkusze styli CSS. O ile na początku i w czasie trwania projektu jesteśmy w stanie dbać o strukturę i porządek. O tyle po powrocie do projektu po kilku tygodniach czy miesiącach i konieczności dodania jednej “małej” funkcjonalności zaczyna wkradać się bałagan.

Chcąc ułatwić sobie pracę z rozbudowanymi plikami CSS, które w większości moich projektów doczekały się grubo ponad dwóch tysięcy linii kodu. Postanowiłem przetestować pre procesor SASS-a, który jak liczyłem w przyszłości ułatwi wprowadzanie zmian. I powiem wam, że się nie myliłem :)

Co to jest SASS ?

SASS jest pre procesorem CSS-a, a bardziej po ludzku jest to rozszerzenie możliwości zapisu CSS-a. Ile razy człowiekowi witki opadają, gdy projekt jest już zakodowany i przychodzi do nas szef, że jest do wprowadzenia malutka zmiana w kolorystyce. To co musimy zrobić, to przejść przez cały plik i wprowadzić zmiany. Gdybym użyli pre procesor SASS (lub innego) musieli byśmy wprowadzić zmiany w jednej lub kilku linijkach kodu, a wynikowy plik CSS został by odpowiednio wygenerowany.

SASS i SCSS w jednym stali domku

Pracując z pre procesorem SASS spotkacie się z dwoma formatami jakimi można się posługiwać. Pierwszy z nich to oczywiście sass i dla osób przyzwyczajonych do CSS-a może być to delikatny szok, bo nie mamy tam nawiasów mówiących o początku i końcu bloku kodu oraz brakuje średnika.

W przypadku tego formatu liczą się wcięcia w kodzie i to one mówią, gdzie znajduje się początek, a gdzie koniec danego selektora. Na początku może być ciężko się przyzwyczaić, dlatego mamy drugi format do dyspozycji czyli scss i co ciekawe każdy plik css jest poprawnym plikiem scss. Format ten wygląda już dużo przystępniej bowiem zawiera klamry określające początek i koniec definicji danego selektora.

W związku z przyjaznym wyglądem osoby zaczynające przygodę zachęcam do korzystania z formatu scss. I w tym wpisie właśnie z tego zapisu będziemy korzystali.

Przygotowanie środowiska

Pre procesor SASS nie jest częścią CSS-a i przeglądarki nie potrafią go interpretować. W związku z czym konieczne jest dodanie do naszego środowiska deweloperskiego jakiegoś narzędzia, które będzie dbało o wygenerowanie z plików SCSS poprawnego pliku CSS.

Do dyspozycji mamy mnóstwo rozwiązań, których część pokazano na stronie http://sass-lang.com/install.

W zależności od predyspozycji maci kilka możliwości:

  • instalacja dedykowanej aplikacji,
  • korzystanie z narzędzi online,
  • instalacja pluginów do waszego IDE
  • wykorzystanie task runnerów np. Gulp

Poniżej postaram się przybliżyć wam każdą z opcji.

Gotowe aplikacje – instalatory

Tutaj niestety za bardzo wam nie pomogę, gdyż nie przepadam za aplikacjami, które muszę instalować żeby wykonać tak prostą czynność jak kompilacja plików SCSS do CSS. Wyjątkiem od tej reguły są małe aplikacje dostępne z wiersza poleceń, takie coś jest dla mnie ok 😉

Jednak jeśli ktoś chce z czegoś takiego korzystać to mogę powiedzieć, że jednym z najpopularniejszych rozwiązań jest Compass. Więcej podobnych rozwiązań znajdziecie na stronie http://sass-lang.com/install.

Kompilatory online

Kolejne rozwiązanie to kompilatory online. Tego rozwiązania także nie mogę polecić, lecz w sytuacjach kryzysowych warto wiedzieć że jest taka opcja.

A dlaczego nie polecam? Ponieważ będziecie wykonywać podwójną robotę. Najpierw będziecie pisać kod w IDE, a następnie owy kod kopiować do kompilatora online i pobierać skompilowany kod do właściwego pliku CSS. Dodatkowo w większości kompilatorów nie skorzystacie z opcji importu innych plików SCSS.

Kiedy warto? Gdy chcecie jedynie przetestować to rozwiązanie bez zbędnego konfigurowania IDE, ściągania aplikacji czy pluginów na waszą maszynę. W innych przypadkach możecie tylko się niepotrzebnie zrazić.

Dla chcących potestować SASS-a w wersji on-line, polecam stronę https://sass.js.org.

Pozwala ona na wykorzystanie importu innych plików sass. Jest to niezmiernie ważne, gdyż pozwala nam to odwzorować pracę jaką bym wykonywali w prawdziwym środowisku.

Dodatkowo w ustawieniach mamy możliwość określenia czy chcemy pracować ze składnią SCSS czy SASS poprzez odpowiednie ustawienie parametru indentedSyntax.

Możemy także określić jak ma wyglądać wynikowy kod poprzez parametr style.

Do dyspozycji dostajemy 4 możliwe opcje: expanded, nested, compact, compressed. Zobaczmy jak będzie wyglądał kod po skompilowaniu dla każdego ustawienia.

expanded

Wersja najbardziej zbliżona do tego co my piszemy tworząc pliki CSS.

nested

W tym przypadku pojawiają się dodatkowe wcięcia, które mają na celu lepszą wizualizację zależności pomiędzy stylami.

compact

Wersja kompaktowa powoduje, że każda definicja znajduje się w nowej linii.

compressed

Tutaj nazwa mówi wszystko ;) Style są “skompresowane”, a właściwie zbite do jednej linii. Dodatkowo zostały usunięte wszystkie białe znaki oraz usunięte ostatnie średniki.

Wersja compressed ma tę zaletę, że pliki tak przygotowane zdecydowanie mniej ważą od innych. Jednak mają jedną zasadniczą wadę, narzędzia deweloperskie zawsze będą wskazywały na style w pierwszej linii pliku CSS. To właściwie uniemożliwia nam pracę, jednak dodając sourcemap problem ten jest rozwiązywany automatycznie.

IDE

Zakładam że większość z was nie pisze kodu w notatniku tylko do tego celu wykorzystuje jakieś IDE. Większość z nich powinno mieć jakieś pluginy pozwalające na kompilację plików sass lub scss do pliku css.

Jako że na codzień pracuję w phpStorm, to na jego przykładzie pokażę wam jak można przygotować sobie to IDE do pracy z SASS-em.

Instalacja (Mac)

Do pracy z SASS-em potrzebujemy małej zewnętrznej aplikacji sass, którą możemy w bardzo prosty sposób zainstalować jednym poleceniem z terminala (wymaga zainstalowanego Ruby).

gem install sass

Teraz możemy przejść do IDE i sprawdzamy czy mamy zainstalowany odpowiedni plugin. W tym celu przechodzimy do PhpStorm -> Preferences…

Przechodzimy do sekcji Plugins, gdzie wpisujemy SASS i sprawdzamy czy plugin jest zainstalowany i włączony.

Jak widać powyżej jest on zainstalowany i włączony, gdyż obsługa SASS-a jest wbudowana w to IDE.

Konfiguracja

Kiedy mamy wszystko zainstalowane czas skonfigurować nasze IDE. W przypadku phpStorm-a wystarczy wejść lub utworzyć plik SCSS / SASS, a IDE samo zaproponuje nam ustawienie kompilacji SASS-a.

Po kliknięciu Yes zostaniemy przeniesieni do gotowej konfiguracji przygotowanej dla pliku SCSS / SASS.

Właściwie możemy w tym momencie zatwierdzić ustawienia i cieszyć się z kompilacji plików SCSS / SASS. Pliki będą tworzone automatycznie, gdy dokonamy zmian w pliku źródłowym. Nie należy edytować plików CSS, gdyż będą one nadpisywane przez IDE.

W pliku dodałem ustawienie tła dla strony i od razu zostały wygenerowane dwa pliki. Pierwszy to CSS, a drugi to mapa dająca nam możliwość łatwego przeglądania styli w narzędziach deweloperskich przeglądarki.

Jak widać powyżej, plik wynikowy jest w formacie nested, a w przypadku gdy IDE odwala za nas całą robotę, to niech to robi, najlepiej jak można. W tym celu zmienimy sobie format na compressed. Przechodzimy do ustawień PhpStorm -> Preferences…, a następnie Tools -> File Watchers.

Wybieramy SCSS i klikamy edytuj, a następnie zmieniamy wartość pola Arguments na:

--no-cache --style compressed --update $FileName$:$FileNameWithoutExtension$.css

Jedyna zmiana pomiędzy nową wartością tego pola, a starą, to dodany zapis --style compressed. Po wprowadzeniu zmian zatwierdzamy wszystko i wprowadzamy jakąkolwiek modyfikację w pliku SCSS, aby wygenerował się nowy plik CSS. Po zmianach powinien on wyglądać następująco.

I tak to można pracować ;)

Node.js + NPM + Gulp

Planując pracę z takimi rozwiązaniami jak SASS, czy też inne rozwiązania front-endowe. Prędzej czy później, przyjdzie nam wykorzystywać jakiegoś task menadżera. Task menadżery są aplikacjami, które mają automatyzować pewne powtarzalne procesy. Takim procesem może być np. kompilacja plików SASS / SCSS do wynikowego pliku CSS. Oczywiście potrafią one wiele więcej jednak w tym przypadku skupimy się na tym konkretnym zadaniu. Do tego celu wykorzystamy Gulp-a, który jest właśnie takim task menadżerem.

Część z was może zastanawiać się po co tyle zachodu, nie lepiej wykorzystać IDE czy jakąś zewnętrzną aplikację ? Otóż śmiem twierdzić że nie. Użycie Gulp-a czy innego task menadżera w projekcie da nam tę wygodę, że jesteśmy w pełni niezależni od IDE czy konkretnej aplikacji instalowanej na naszej maszynie. Każdy kto pobierze projekt znajdzie w nim plik gulpfile.js i wiadomo z czego skorzystać ;)

Instalacja

Gulp wymaga od nas posiadania zainstalowanego Node.js oraz NPM-a, który w większości przypadków zostanie zainstalowany z Node.js. Jeśli jeszcze nie macie tego zestawu to na stronie https://nodejs.org/en/download/ znajdziecie wszystko co będzie wam potrzebne do instalacji.

Kiedy mamy Node.js i NPM to przechodzimy do terminala i instalujemy Gulp-a globalnie, czyli tak aby był dostępny dla wszystkich. W tym celu posłużymy się poleceniem:

npm install gulp -g

Teraz możemy zainstalować odpowiednie paczki w naszym projekcie. W związku z czym przechodzimy do katalogu z projektem w terminalu, a następnie wywołujemy polecenie:

npm init

Polecenie inicjalizuje projekt w katalogu w którym się znajdujemy, w celu określenia szczegółów projektu jesteśmy proszeni o podanie kilku informacji jak nazwa projektu, licencja, autor itd. Po wypełnieniu wszystkiego i zatwierdzeniu operacji zostanie wygenerowany plik package.json w którym te informacje znajdziemy. Teraz możemy przejść do doinstalowania pakietów potrzebnych w projekcie. Zaczynamy od Gulp-a:

npm install gulp --save-dev

Flaga --save-dev mówi, że z pakietu korzystają deweloperzy. Dalej doinstalowujemy pakiet gulp-sass, gulp-sourcemaps oraz gulp-autoprefixer i robimy to poleceniem:

npm install gulp-sass gulp-sourcemaps gulp-autoprefixer

Po zakończeniu procesu powinniśmy znaleźć odpowiednie wpisy w pliku package.json. W tak przygotowanym środowisku możemy przejść do stworzenia naszego pliku gulpfile.js.

Jak używać

Gulp wymaga od nas stworzenia specjalnego pliku gulpfile.js, który jest wykorzystywany przez niego do realizacji poszczególnych zadań. I tak nasz plik w najprostszej formie będzie wyglądał następująco.

const gulp = require('gulp');
const sass = require('gulp-sass');
 
gulp.task('sass', () => {
  return gulp.src('./build/sass/main.scss')
    .pipe(sass({outputStyle: 'compressed'}))
    .pipe(gulp.dest('./dist/css'))
});

W telegraficznym skrócie, do zmiennych przypisujemy odpowiednie pakiety. Następnie tworzymy zadanie gulp.task o nazwie sass, które szuka pliku gulp.src('./build/sass/main.scss') następnie plik ten jest przepuszczany przez SASS-a .pipe(sass({outputStyle: 'compressed'})) ustawionego na formatowanie compressed. Kiedy plik zostanie przemielony do wyjściowego formatu zostaje on przekazany do .pipe(gulp.dest('./dist/css')), gdzie następuje zapis w podanej lokalizacji.

Przed uruchomieniem zadania koniecznie upewnijcie się, że wasza struktura katalogów wygląda jak poniżej.

Teraz jedyne co musimy zrobić to przejść do terminala i uruchomić nasze zadanie o nazwie sass.

gulp sass

W wyniku operacji powinniśmy dostać nasz wynikowy plik CSS.

Jednak jest to o tyle niewygodne rozwiązanie, że wymusza na nas za każdym razem uruchomienie zadania ręcznie. Ale nie martwcie się jest rozwiązanie…

Automatyczne generowanie CSS

Ręczne uruchamianie Gulpa, gdy zostanie zmodyfikowany któryś plik SCSS jest mało efektywne. W związku z tym dodamy sobie nowe zadanie o nazwie watch.

gulp.task('watch', () => {
    gulp.watch('./build/sass/**/*.scss', ['sass'])
});

Zadanie to ma znaleźć w katalogu i podkatalogach wszystkie pliki SCSS, a następnie jeśli coś się w nich zmieni to uruchomić zadanie sass. Całość pliku gulpfile.js będzie wyglądała następująco:

const gulp = require('gulp');
const sass = require('gulp-sass');
 
gulp.task('sass', () => {
    return gulp.src('./app/sass/main.scss')
        .pipe(sass({outputStyle: 'compressed'}))
        .pipe(gulp.dest('./dist/css'))
});
 
gulp.task('watch', () => {
    gulp.watch('./app/sass/**/*.scss', ['sass'])
});

Teraz możemy uruchomić zadanie watch w terminalu i zobaczyć jak będzie się ono zachowywało.

gulp watch

Po uruchomieniu polecenia widzimy, że aplikacja jest cały czas uruchomiona i nasłuchuje na zmiany w plikach.

Teraz jakakolwiek zmiana w plikach scss powoduje uruchomienie zadania sass, a co za tym idzie kompilację plików do CSS.

sourcemaps

Jak już wspominałem wcześniej pewnym problemem przy pisaniu arkuszy styli z wykorzystaniem pre procesorów, jest późniejsza analiza styli w narzędziach deweloperskich.

Zobaczmy jak wygląda plik CSS skompilowany z prostego SCSS-a.

body
{
    background-color: #f5f5f5;
    font-size: 14px;
}
 
p
{
    margin-bottom: 15px;
    color: #222;
}

Plik ten powinien zostać skompilowany do jednolinijkowego pliku CSS.

body{background-color:#f5f5f5;font-size:14px}p{margin-bottom:15px;color:#222}

Co znacznie utrudni odwołania do odpowiednich fragmentów styli w narzędziach deweloperskich. Zobaczcie czego dowiemy się o elemencie p i jego stylach.

Sama informacja o stylach jest ok, ale wskazanie jest na pierwszą linię pliku main.css. To już jest problem, gdyż w pierwszej linii znajdują się wszystkie style.

Rozwiązaniem tego problemu jest kolejny moduł, czyli sourcemaps. W celu dodania map do generowanego pliku CSS, konieczne będzie rozszerzenie zadania. W tym celu importujemy pakiet gulp-sourcemaps.

const sourcemaps = require('gulp-sourcemaps');

Kiedy mamy pakiet zaimportowany to przed przekazaniem danych do modułu sass inicjalizujemy moduł map.

gulp.task('sass', () => {
    return gulp.src('./app/sass/main.scss')
        .pipe(sourcemaps.init())
        .pipe(sass({outputStyle: 'compressed'}))
        .pipe(gulp.dest('./dist/css'))
});

Moduł map zebrał informacje i teraz potrzebuje zapisać je do pliku. Dlatego przed operacją zapisu danych do pliku dodajemy zapis z modułu sourcemaps.

gulp.task('sass', () => {
    return gulp.src('./app/sass/main.scss')
        .pipe(sourcemaps.init())
        .pipe(sass({outputStyle: 'compressed'}))
        .pipe(sourcemaps.write())
        .pipe(gulp.dest('./dist/css'))
});

Po wykonaniu modyfikacji plik gulpfile.js powinien wyglądać następująco.

const gulp = require('gulp');
const sass = require('gulp-sass');
const sourcemaps = require('gulp-sourcemaps');
 
gulp.task('sass', () => {
    return gulp.src('./build/sass/main.scss')
        .pipe(sourcemaps.init())
        .pipe(sass({outputStyle: 'compressed'}))
        .pipe(sourcemaps.write())
        .pipe(gulp.dest('./dist/css'))
});
 
gulp.task('watch', () => {
    gulp.watch('./build/sass/**/*.scss', ['sass'])
});

Jeśli wszystko się zgadza to zatrzymujemy usługę w terminalu i uruchamiamy ją ponownie. W wyniku restartu zostanie wygenerowany ponownie plik CSS, który powinien zawierać już mapy.

body{background-color:#f5f5f5;font-size:14px}p{margin-bottom:15px;color:#222}
/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbi5jc3MiLCJzb3VyY2VzIjpbIm1haW4uc2NzcyJdLCJzb3VyY2VzQ29udGVudCI6WyJib2R5XG57XG4gIGJhY2tncm91bmQtY29sb3I6ICNmNWY1ZjU7XG4gIGZvbnQtc2l6ZTogMTRweDtcbn1cblxucFxue1xuICBtYXJnaW4tYm90dG9tOiAxNXB4O1xuICBjb2xvcjogIzIyMjtcbn1cbiJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxBQUFBLElBQUksQUFDSixDQUNFLGdCQUFnQixDQUFFLE9BQU8sQ0FDekIsU0FBUyxDQUFFLElBQUksQ0FDaEIsQUFFRCxBQUFBLENBQUMsQUFDRCxDQUNFLGFBQWEsQ0FBRSxJQUFJLENBQ25CLEtBQUssQ0FBRSxJQUFJLENBQ1oifQ== */

Po takich zmianach i odświeżeniu strony powinniśmy zobaczyć dużo czytelniejszą informację w narzędziach deweloperskich.

autoprefixer

Ostatni pakiet jaki dodamy to gulp-autoprefixer, który dodaje prefiksy dla przeglądarek np. -webkit, -o, -ms.

Przykładowy zapis

.example {
    display: grid;
    transition: all .5s;
    user-select: none;
    background: linear-gradient(to bottom, white, black);
}

Jest w stanie nam zmienić na:

.example {
    display: -ms-grid;
    display: grid;
    -webkit-transition: all .5s;
    -o-transition: all .5s;
    transition: all .5s;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    background: -webkit-gradient(linear, left top, left bottom, from(white), to(black));
    background: -webkit-linear-gradient(top, white, black);
    background: -o-linear-gradient(top, white, black);
    background: linear-gradient(to bottom, white, black);
}

Aby go użyć oczywiście musimy zacząć od importu.

const autoprefixer = require('gulp-autoprefixer');

W kolejnym kroku dodajemy autoprefixer po operacji kompilacji kodu, gdyż to na wynikowym kodzie trzeba wykonać operację dodania prefiksów.

gulp.task('sass', () => {
    return gulp.src('./app/sass/main.scss')
        .pipe(sourcemaps.init())
        .pipe(sass({outputStyle: 'compressed'}))
        .pipe(autoprefixer({
            browsers: ['last 2 versions'],
            cascade: false
        }))
        .pipe(sourcemaps.write())
        .pipe(gulp.dest('./dist/css'))
});

Do modułu zostały przekazane dodatkowo dwa parametry, pierwszy browsers mówi ile wersji wstecz ma być wspieranych. Parametr cascade mówi o kaskadowym wywołaniu, które nie jest potrzebne w tym przypadku dlatego jego wartość jest ustawiona na false.

Gdybyście chcieli pobawić się i zobaczyć jakie prefiksy będą dopisywane do waszego kodu CSS, to możecie wejść na stronę autoprefixer.github.io i zobaczyć wprowadzane modyfikacje bez instalacji jakiegokolwiek pakietu.

Po tych wszystkich zmianach nasz plik będzie wyglądał jak poniżej.

const gulp = require('gulp');
const sass = require('gulp-sass');
const sourcemaps = require('gulp-sourcemaps');
const autoprefixer = require('gulp-autoprefixer');
 
gulp.task('sass', () => {
    return gulp.src('./build/sass/main.scss')
        .pipe(sourcemaps.init())
        .pipe(sass({outputStyle: 'compressed'}))
        .pipe(autoprefixer({
            browsers: ['last 2 versions'],
            cascade: false
        }))
        .pipe(sourcemaps.write())
        .pipe(gulp.dest('./dist/css'))
});
 
gulp.task('watch', () => {
    gulp.watch('./build/sass/**/*.scss', ['sass'])
});

Wprowadzenie do SASS-a

Zanim przejdziemy do konkretnych rozwiązań jakie zapewnia nam SASS, powinniśmy poznać i trzymać się pewnych konwencji.

Ciągi znaków a cudzysłów

Sam CSS nie wymaga od nas, abym łańcuchy znaków umieszczali w cudzysłowach. Podobnie jest w przypadku SASS-a i dla niego Arial i 'Arial' są równoważne. Jednak dobrą praktyką jest umieszczanie ciągów znaków w cudzysłowiu.

$color: 'red';
 
$color: red;

Pamiętajcie, opcja pierwsza jest lepsza ;)

Liczby i jednostki

SASS liczby traktuje jako osobny typ danych, co pozwala na przeprowadzanie obliczeń. Jednak należy zwracać uwagę na wykonywanie takich obliczeń, gdyż jednostki np. 1px są traktowane w specjalny sposób.

Zwróćmy uwagę na operację 1px + 1, która zwróci 2px jednak nie tak powinniśmy przeprowadzać operacje na jednostkach. Jeśli chcemy wykonać operację na jakiejś jednostce i do tego celu ma posłużyć liczba to tę liczbę powinniśmy pomnożyć przez 1 jednostkę.

.test
{
    width: 1px + (1 * 1px)
}

Możemy także pokusić się o odwrócenie sytuacji. Czyli chcemy pozbyć się jednostki. W tym celu dzielimy wartość przez 1 jednostkę.

.test
{
    transision: rotate(1px/1px);
}

Zagnieżdżenia elementów

Standardowy plik CSS jest strukturą płaską i często możemy się spotkać z komentarzami w pliku CSS mającymi na celu oddzielenie poszczególnych grup styli. SASS dostarcza nam rozwiązanie zaczerpnięte z języków programowania, a mianowicie zagnieżdżenia.

Załóżmy, że mamy taką prostą strukturę html-a.

<header>
    <nav>
        <ul>
            <li>
                <a href="/">Home</a>
            </li>
            <li>
                <a href="/contact.html">Contact</a>
            </li>
        </ul>
    </nav>
</header>

W pliku CSS mogli byśmy zdefiniować sobie style w następujący sposób.

header
{
    background: #F5F5F5;
}
 
header nav
{
    float: right;
}
 
header nav ul
{
    list-style: none;
    padding-left: 0;
}
 
header nav ul li
{
    display: inline-block;
}

Bardzo prosta definicja, i bardzo niewygodna do przeglądania i dalszego rozwijania. Oczywiście, gdybym mieli tylko tak proste struktury to bym nie narzekał. Jednak w obecnych czasach pliki CSS rozrastają się do niebotycznych rozmiarów, a to bywa bardzo kosztowne.

Rozwiązaniem tego problemu są zagnieżdżenia, które dają nam możliwość umieszczenia jednego elementu w innym. Dając niejako złudzenie poruszania się po drzewie DOM. Zobaczmy jak to wygląda w praktyce.

header
{
    background: #F5F5F5;
     
    nav
    {
        float: right;
 
        ul
        {
            list-style: none;
            padding-left: 0;
 
            li
            {
                display: inline-block;
            }
        }
    }
}

Tak zdefiniowana struktura dość jednoznacznie wskazuje nam, gdzie się znajdujemy i jak poszczególne elementy są ostylowane. Dodatkowo wygoda jaką niesie ze sobą zagnieżdżanie elementów pozwalając je zwijać na poszczególnych poziomach kupiła mnie całkowicie.

Zagnieżdżenia mają jeszcze jedną bardzo przydatną właściwość, a mianowicie umożliwiają odwoływanie się do elementów nadrzędnych poprzez znak & ampersanda. Już tłumaczę o co chodzi ;)

Załóżmy że chcieli byśmy, aby po najechaniu na element header zmieniało się jego tło na jakiś kolor. Do tego celu użylibyśmy pseudo klasy :hover i nadali mu odpowiednie style.

Jednak powyższy zapis nie zadziałał tak jak bym sobie to wymyślili. Bowiem dla SASS-a :hover to element jak każdy inny, nie jest specjalnie traktowany, w związku z czym został wygenerowany kod header :hover. My jednak możemy poprzez znak & odwołać się do rodzica wpływając na generowany kod wynikowy.

Najprościej możemy to sobie wytłumaczyć mówiąc że znak & zostanie zastąpiony przez definicję rodzica.

Zmienne

Zmienne to kolejne rozwiązanie o którym maży programista pracujący z plikami CSS. Ile razy powtarzamy ten sam kolor, który jest kolorem przewodnim strony. A później ile razy trzeba przeglądać cały plik, aby ten kolor zmienić (bo ma być odrobinkę ciemniejszy). Krew zalewa człowieka.

Rozwiązaniem tych bolączek są właśnie zmienne. Definiowanie zmiennych jest dziecinnie proste, piszemy najpierw znak dolara $ a następnie nazwę zmiennej.

$main-color

Mamy już zmienną, czas przypisać wartość do niej. Robimy to poprzez postawienie dwukropka za nazwą zmiennej, a następnie podajemy wartość po której stawiamy znak średnika ;.

$main-color: #456;

I od tego momentu mamy zmienną, której wartość to #456. Przypiszmy więc zawartość zmiennej do jakiegoś elementu.

Od tego momentu zmiana wartości koloru na ciut ciemniejszy wymaga od nas zmiany tylko w jednym miejscu ;)

Interpolacje

Pisząc w SASS-ie prędzej czy później spotkasz się z problemem wykonywania operacji dzielenia. Dokładniej chodzi o poniższy zapis.

$font-size-main: 14px;
$line-height-main: 14px;
 
body
{
    font: 500 $font-size-main/$line-height-main 'Arial';
}

W wyniku kompilacji otrzymamy poniższy kod, który jest niepoprawny.

body
{
    font: 500 1 "Arial";
}

Wynika to z faktu, że SASS dostarcza nam operacje matematyczne. Jednak w tym przypadku operacja ta jest niepożądana, jak możemy się przed tym zabezpieczyć? Otóż odpowiedzią są interpolacje, czyli specjalny zapis zmiennej pozwalający na użycie zmiennej w dowolnym miejscu arkuszu.

Zapis ten wygląda następująco:

#{$font-size-main}

Zaczynamy od hasza #, a następnie w nawiasach klamrowych umieszczamy zmienną. Taka modyfikacja pozwoli na poprawne skompilowanie kodu.

$font-size-main: 14px;
$line-height-main: 14px;
 
body
{
    font: 500 #{$font-size-main}/#{$line-height-main} 'Arial';
}

I wynikiem będzie poniższy kod.

body
{
    font: 500 14px/14px "Arial";
}

Poza tym że interpolacje są w stanie uchronić nas przed niepożądanymi działaniami arytmetycznymi. Umożliwiają także wzbogacanie selektorów.

$center: 'text-center';
 
body
{
    .#{$center}
    {
        text-align: center;
    }
}

Powyższy zapis wygeneruje kod.

body .text-center
{
    text-align: center;
}

Najistotniejsze w wygenerowanym kodzie jest to, że wartość zmiennej $center została użyta do utworzenia selektora body .text-center. Ta właściwość SASS-a bardzo nam się przyda, gdy będziemy pisać własne domieszki lub posługiwać się pętlami.

Listy

Zmienne poza przechowywaniem w sobie tylko pojedynczych wartości mogą także przechowywać listy wartości. Sama zmienna niczym się nie różni od tej przechowującej pojedynczą wartość. Różnica jest w przekazywaniu wartości bowiem w przypadku listy podajemy kolejne wartości po przecinku.

$list: 'red', 'blue', 'yellow';

Do elementów możemy odwoływać się wykorzystując funkcję nth podając jako pierwszy parametr zmienną, a drugi parametr numer elementu na liście ( numery są indeksowane od 1 ).

Do list rzadko odwołujemy się bezpośrednio jak w powyższym przykładzie. Częściej listy są wykorzystywane w połączeniu z pętlami.

Mapy

Mapy są bardzo podobne do list, jednak w przypadku map mamy możliwość definiowania indeksu oprócz wartości.

$align: (
    'l': left,
    'c': center,
    'r': right
);

Jeśli zaś chcemy pobrać wartość elementu mapy, konieczne jest użycie funkcji map-get. Gdzie pierwszym argumentem jest zmienna przechowująca mapę, zaś drugi argument to indeks z którego ma zostać zwrócona wartość.

I tutaj podobnie jak w przypadku list, częstsze wykorzystanie map jest w parze z pętlami.

Rozbijamy plik na mniejsze elementy ( Import )

Śmiało można powiedzieć, że zagnieżdżenia i zmienne to siła SASS-a. Dopełnieniem tej siły, jest możliwość podzielenia plików na mniejsze elementu i ich dołączenie poprzez @import.

Zaraz ktoś mi powie, ale przecież w CSS mamy @import, który też dołącza pliki. I zgadzam się z wami w 100%, ale trzeba wiedzieć że te mechanizmy działają całkowicie inaczej. Zacznijmy od importu dostarczanego wraz z CSS-em. Otóż import ten rzeczywiście daje nam możliwość wskazania, które pliki mają zostać dołączone. Niestety płacimy za to bardzo dużą cenę, bowiem dołączenie każdego pliku to dodatkowe zapytanie do serwera, a tym samy opóźnienie w ładowaniu strony :( Więc już wiesz dlaczego bardzo rzadko korzysta się z tego rozwiązania.

Całkowicie inaczej sprawa wygląda jeśli chodzi o import dostarczany wraz z SASS-em. Import ten jest wykorzystywany w momencie budowania wynikowego pliku CSS. Czyli zawartość każdego importowanego pliku zostanie wpisana w miejscu użytej dyrektywy @import. Takie rozwiązanie powoduje, że nie mamy dodatkowych zapytań, a tylko jedno o plik CSS który został zbudowany.

Zobaczmy jak taki import działa w praktyce. Mamy plik main.css, który ma poniższą treść.

$main-color: #222;
$background-page: #f5f5f5;
 
body
{
    background-color: $background-page;
    font-size: 14px;
}
 
p
{
    margin-bottom: 15px;
    color: $main-color;
}

Idealnie było by zmienne przenieść do osobnego pliku i je importować. W tym celu stworzymy sobie plik _vars.scss i przeniesiemy do niego zmienne.

$main-color: #222;
$background-page: #f5f5f5;

Teraz możemy zmienne usunąć z pliku main.scss i dodać dyrektywę @import.

@import "_vars.scss";
 
body
{
    background-color: $background-page;
    font-size: 14px;
}
 
p
{
    margin-bottom: 15px;
    color: $main-color;
}

Import jest bardzo prosty, podajemy nazwę pliku który ma zostać zaimportowany i resztę pracy wykona kompilator. Jako ciekawostkę mogę wam powiedzieć, że powyższy zapis importu jest najbardziej dosłownym. Można go uprościć nie pisząc rozszerzenia czy przedniej podłogi. Dopuszczalne są zapisy:

  • @import “_vars.scss”;
  • @import “_vars”;
  • @import “vars”;

Jednak uważajcie przy importach, gdyż w niektórych przypadkach import może zachować się jak import CSS-owy. Tak się wydarzy, gdy:

  • importujemy plik z rozszerzeniem .css,
  • ścieżka do pliku zaczyna się od http://
  • jako ścieżkę do pliku podamy url()
  • do importu dodamy media queries

Domieszki ( Mixins )

Ograniczenie powtórzeń w kodzie jest czymś do czego dążymy i zasada DRY nie powinna być obca nikomu. Dzięki domieszką możemy w bardzo prosty sposób wyeliminować ciągle powtarzające się fragmenty kodu. Ale zobaczmy jak to będzie wyglądało w praktyce.

Domieszki definiujemy za pomocą zapisu @mixin, a następnie nazwa pod którą ma dana domieszka występować. Aby zachować porządek w kodzie utworzymy sobie nowy plik w którym będziemy umieszczali domieszki. W pliku o nazwie _mixins.scss umieszczamy pierwszą naszą domieszkę.

@mixin button
{
    display: inline-block;
    padding: 6px 12px;
    color: #ffffff;
    background-color: #5cb85c;
    border: 1px solid #4cae4c;
}

Powyższa domieszka definiuje nam wygląd przycisku i teraz jeśli chcieli byśmy, aby jakiś element dostał zestaw takich ustawień wystarczy się do naszej domieszki odwołać. Jednak zanim to zrobimy musimy w naszym głównym pliku main.scss zaimportować domieszki.

@import "vars";
@import "mixins";
 
body
{
    background-color: $background-page;
    font-size: 14px;
}
 
p
{
    margin-bottom: 15px;
    color: $main-color;
}

Teraz możemy dopiero wykorzystać domieszkę w nowej klasie .btn.

.btn
{
    font-size: 14px;
    font-weight: 400;
 
    @include button;
}

Po kompilacji powinniśmy otrzymać klasę .btn zawierającą wszystkie definicje z naszego mixina.

Domieszki z argumentami

Zapewne zauważyliście, że domieszki są fajne. Jednak gdyby dało się dynamicznie zmieniać parametry wewnątrz to było by jeszcze lepiej. I powiem wam, że się da :) Mamy możliwość tworzenia domieszek z argumentami. Zobaczmy jak taka domieszka może wyglądać.

@mixin button($color, $bg, $border)
{
    display: inline-block;
    padding: 6px 12px;
    color: $color;
    background-color: $bg;
    border: 1px solid $border;
}

Zmodyfikowałem poprzednią domieszkę dodając jej parametry dla najczęściej modyfikowanych elementów. Teraz w prosty sposób możemy stworzyć przyciski o różnych kolorach.

.btn-green
{
    @include button(#ffffff, #5cb85c, #4cae4c);
}
 
.btn-red
{
    @include button(#ffffff, #d9534f, #d43f3a);
}

W wyniku działania pre procesora SASS powinniśmy otrzymać dwie klasy z różnymi kolorami, obramowaniami i tłami.

Tak wiem, że nie jest idealnie i mamy powtórzony kod i dla spokoju ducha i czystego sumienia wyrzucimy display i padding z mixina do osobnej klasy ;)

@mixin button($color, $bg, $border)
{
    color: $color;
    background-color: $bg;
    border: 1px solid $border;
}
@import "vars";
@import "mixins";
 
body
{
    background-color: $background-page;
    font-size: 14px;
}
 
p
{
    margin-bottom: 15px;
    color: $main-color;
}
 
.btn
{
    display: inline-block;
    padding: 6px 12px;
}
 
.btn-green
{
    @include button(#ffffff, #5cb85c, #4cae4c);
}
 
.btn-red
{
    @include button(#ffffff, #d9534f, #d43f3a);
}

Domyślne parametry

Ostatnią rzeczą jaką chcę wam powiedzieć o domieszkach jest możliwość zdefiniowania domyślnych parametrów. Zostańmy przy naszym przykładzie z przyciskami, mamy przycisk czerwony dla operacji usuwania. Mamy przycisk zielony dla operacji dodawania, ale potrzebowali byśmy, jakiegoś przycisku neutralnego. Przycisk ten zapewne będzie najczęściej wykorzystywany w systemie więc zapis powinien być jak najkrótszy. W tym właśnie pomogą nam domyślne parametry, zobaczmy jak je definiujemy.

@mixin button($color: #333, $bg: #ffffff, $border: #cccccc)
{
    color: $color;
    background-color: $bg;
    border: 1px solid $border;
}

Trochę wygląda to tak jakbyśmy do zmiennych przypisali wartości, brakuje tylko średnika. Rozbudujmy teraz listę naszych przycisków w pliku main.css.

.btn-default
{
    @include button;
}
 
.btn-green
{
    @include button(#ffffff, #5cb85c, #4cae4c);
}
 
.btn-red
{
    @include button(#ffffff, #d9534f, #d43f3a);
}

Dodaliśmy klasę .btn-default, która wywołuje domieszkę bez żadnych parametrów. Jednak po kompilacji dostaniemy klasę uzupełnioną wartościami domyślnymi.

Dziedziczenie stylów ( Extends )

Pozornie jest to bardzo łatwy temat bowiem sprowadza się do faktu, że możemy odziedziczyć style z innego selektora.

Załóżmy, że mamy klasę .text-center, która jak się domyślamy wyśrodkuje tekst.

.text-center
{
    text-align: center;
}

Kolejną klasę którą chcemy napisać to klasa przycisku, który także ma cieć wyśrodkowany tekst. Więc czemu by nie odziedziczyć tego z klasy .text-center

.btn
{
    @extend .text-center;
}

W wyniku tej operacji zostanie wygenerowany poniższy kod.

Co tu się stało? Otóż klasa .btn została dopisana do klasy z której dziedziczy, czyli .text-center. Rodzi to pewne niemiłe konsekwencje, jeśli będziemy kontynuowali ten tok myślenia to takie centrowanie tekstu rozrośnie nam się do ogromnych rozmiarów.

To jednak, że klasa .btn jest dołączana do klasy rodzica nie jest jeszcze najgorsze. Otóż oprócz takiego dopisania klasy .btn dodawane są do niej ograniczenia jakie miał rodzic. W tym przypadku nie było żadnych ograniczeń, ale zobaczmy co się stanie, gdy będziemy chcieli ograniczyć zachowanie klasy .text-center tylko do wnętrza kontenera.

.container
{
    .text-center
    {
        text-align: center;
    }
}

Ten kod wygeneruje nam style CSS.

.container .text-center
{
 text-align: center;
}

Wszystko mam nadzieję jest jasne ;) Otwórz element który będzie miał przypisaną klasę text-align i będzie znajdował się w elemencie z klasą container jego tekst zostanie wyśrodkowany.

<div class="container">
  <div class="text-center">Wyśrodkowany</div>
</div>
<div class="text-center">Nie wyśrodkowany :(</div>

I teraz jeśli dodamy naszą klasę .btn, która dziedziczy po .text-center możemy zostać zaskoczeni.

.container
{
    .text-center
    {
        text-align: center;
    }
}
 
.btn
{
    @extend .text-center;
}

Zaskakujący wynik ;)

Czemu ten wynik może być zaskoczeniem, otóż oprócz odziedziczenia styli dostaliśmy także ograniczenia klasy. Nie chcieliśmy przecież odziedziczyć styli tylko dla klasy .btn znajdującej się w .container.

Takie zachowanie dziedziczenia powinno nas wyczulić na dziedziczenia z innych klas. Według mnie jeśli nie robicie tego z pełną świadomością konsekwencji to nie dziedziczcie z klas.

W takim razie czy z tego rozwiązania nie korzystać ? Oczywiście, że korzystać lecz zamiast dziedziczenia z klas powinniśmy zastosować dziedziczenie z placeholder-ów. Placeholder jest zapisem, który pod jakąś nazwą trzyma zestaw styli, które sami napisaliśmy.

%text-center 
{
  text-align: center;
}

Powyżej mamy placeholder, który nazywa się text-center i po tej nazwie możemy się do niego odwoływać. Kompilacja pliku z samym placeholder-em nie spowoduje wygenerowanie jakiegokolwiek kodu. Zastąpmy więc naszą klasę .text-center i zobaczmy czy tym razem spełni to nasze oczekiwania.

%text-center 
{
  text-align: center;
}

.container
{
  .text-center 
  {
    @extend %text-center;
  }   
}

.btn 
{
  @extend %text-center;
}

Tym razem już bez zaskoczeń :) Mam nadzieję, że udało mi się wyjaśnić dziedziczenie, gdyż wbrew pozorom nie jest ono takie proste jak cześć osób opisuje.

Instrukcje Warunkowe (Conditionals)

SASS udostępnia nam instrukcje warunkowe działające identycznie jak te, które znamy z języków programowania. Nieco inny jest zapis jednak zasada działania identyczna.

$support-legacy: true;

@if $support-legacy == true 
{
  body 
  {
    background: red;
  }
} 
@else 
{
  body 
  {
    background: blue;
  }
}

I tak na samym początku zdefiniowaliśmy zmienną o nazwie $support-legacy i wartości true. Od wartości tej zmiennej będzie zależało jaki kolor tła zostanie ustawiony.

Dalej znajduje się dyrektywa @id z warunkiem, który ma zostać sprawdzony.

@if $support-legacy == true

Jeśli wartość zmiennej $support-legacy jest równa == wartości true to zostanie wykorzystany poniższy blok kodu.

body
{
  background: red;
}

Jeśli jednak wartość zmiennej będzie inna niż true to zostanie wykorzystany blok kodu zawarty za dyrektywą @else.

body
{
  background: blue;
}

Przy czym dyrektywa @else jest elementem opcjonalnym i nie musimy jej definiować jeśli nie mamy takiej potrzeby.

Funkcje

Funkcje w swojej budowie są bardzo podobne do mixinów, z tą różnicą, że nie wstrzykują kodu, a zwracają wartość. Dlatego są one wykorzystywane głównie do wykonywania obliczeń.

Budowa funkcji zaczyna się od dyrektywy @function, a następnie nazwa i lista argumentów o ile oczywiście potrzebujemy przekazać jakieś argumenty do funkcji.

@function pi()
{
	@return 3.14;
}

Wartość z funkcji zwracamy dyrektywą @return po której podajemy zwracaną wartość. Jak zostało to pokazane powyżej. Odwołanie do funkcji jest dziecinnie proste i sprowadza się do podania nazwy funkcji z której chcemy skorzystać.

.pi
{
    content: pi();
}

W rezultacie dostaniemy poniższy wynik.

Funkcje obowiązują te same zasady co domieszki (mixiny). Czyli możemy przekazywać argumenty, możemy ustawiać argumenty domyślne. Dodatkowo dostajemy całą gamę funkcji wbudowanych, które są w stanie załatwić za nas część pracy.

Pętle

W tym momencie dochodzimy do elementów, których staram się unikać jak ognia. O ile jestem w stanie zrozumieć istnienie warunków, funkcji o tyle same pętle wydają mi się nieco zbędne. W większości wypadków powodują, że kod który powstaje z ich wykorzystaniem staje się bardzo skomplikowany.

Rozwiązanie to jednak stosuję tylko w jednym przypadku, a mianowicie gdy potrzebuję przygotować kilka wersji kolorystycznych tego samego layoutu. W takich przypadkach rzeczywiście rozwiązanie to się sprawdza.

Each

Według mnie jest to najbardziej użyteczny rodzaj pętli, gdyż pozwala nam na iterowanie po listach i mapach. Właśnie na nich możemy oprzeć listę kolorystyk i odwoływania się do nich.

$themes: 'light', 'dark';
$colors: (
  'light': (
	  'bg-main': #F5F5F5,
	  'color-main': #222222
  ),
  'dark': (
	  'bg-main': #757575,
	  'color-main': #FFFFFF
  ),
);

Skoro mamy już na czym pracować to możemy przejść do zdefiniowania pętli.

@each $theme in $themes 
{
  body
  {
    &.#{$theme}
    {
      background-color: map-deep-get($colors, $theme, bg-main);  
      color: map-deep-get($colors, $theme, bg-main);  
    }
  }
}

W pętli tej użyłem niestandardowej funkcji którą musimy dodać, aby móc odczytać wartość z mapy zagłębionej w inną mapę. Nie ja wymyśliłem tę funkcję i jeśli ktoś jest chętny to miłej analizy, jest ona bardzo prosta i świetnie spełnia swoje zadanie.

/// Map deep get
/// @author Hugo Giraudel
/// @access public
/// @param {Map} $map - Map
/// @param {Arglist} $keys - Key chain
/// @return {*} - Desired value
@function map-deep-get($map, $keys...) {
    @each $key in $keys {
        $map: map-get($map, $key);
    }
    @return $map;
}

I po złożeniu całości otrzymamy poniższy wynik.

Powyżej widzimy że został wygenerowany kod dla obu wersji kolorystycznych. To rozwiązanie niestety spowoduje, że w wynikowym pliku będziemy mieli dużo nadmiarowego kodu. Na szczęście, gdy klient się zdecyduje na którąś wersję można resztę szablonów usunąć z listy i nadmiarowość zniknie ;)

For

Kolejny rodzaj pętli, który sprawdzi się jeśli chcecie iterować po elementach drzewa DOM wykorzystując pseudo klasę nth-of-type.

Składnia jest dość prosta, zaczynamy o dyrektywy @for następnie deklarujemy zmienną, którą będziemy mogli wykorzystać w pętli. Po zmiennej słowo kluczowe from po którym podajemy wartość początkową od jakiej pętla ma zacząć iterować. Za wartością początkową kolejne słowo kluczowe through, a po nim wartość końcowa.

@for $i from 1 through 5 
{
  p:nth-of-type(#{$i}) 
  {
    border: 1px solid rgba(255, 0, 0, $i * 0.10);
  }
}

Co w wyniku wygeneruje nam bardzo ładne obramowanie elementów p.

While

Ostatni rodzaj pętli, który podobnie jak pętla for wykorzystamy do wygenerowani kodu z pseudo klasą nth-of-type.

Składnia tej pętli sprowadza się do dyrektywy @while po której określamy warunek jaki musi zostać spełniony, aby pętla działała.

$i: 1;

@while $i < 5
{
	p:nth-of-type(#{$i})
	{
		border: 1px solid rgba(255, 0, 0, $i * 0.10);
	}
	
	$i: $i + 1;
}

Wynik działania praktycznie identyczny jak w przypadku pętli for.

Podsumowanie

Pre procesor SASS dostarcza nam wiele rozwiązań, które w mniejszym lub większym stopniu mogą przydać się w naszych projektach. Niezależnie od tego czy w Twoim projekcie użyjesz tylko zagłębień, zmiennych, importów czy innych rozwiązań SASS-a. To gwarantuję, że poziom komfortu pracy będzie dużo wyższy niż z plikiem liczącym setki lub tysiące linii kodu.

Daj znać czy korzystasz z pre procesorów, a jeśli nie to dlaczego ??