Praca z CSS

Ze stylowaniem elementów możemy działać na kilka sposobów.

Właściwość style

Najczęściej spotykanym sposobem jest skorzystanie z właściwości style, którą możemy użyć do ustawiania i odczytu stylowania:


const btn = document.querySelector(".button");

btn.addEventListener("click", function() {
    this.style.backgroundColor = "#4BA2EA";
    this.style.fontSize = "1.6rem";
    this.style.borderRadius = "3rem";
    this.style.color = "#F7F781";
});

Jeżeli właściwość składa się z jednego słowa, to zapisujemy ją tak jak w css. Jeżeli właściwość składa się z kilku słów oddzielonych myślnikiem, wtedy dla takiej właściwości musimy zastosować zapis CamelCase. Możemy tutaj też zastosować zapis znany z obiektów:


//css
font-size : 1rem;
//js
el.style.fontSize = "1rem";
el.style['font-size'] = "1rem";


//css
background : linear-gradient(#fff, #ddd);
//js
el.style.background = "linear-gradient(#fff, #ddd)";
el.style['background'] = "linear-gradient(#fff, #ddd)";


//css
background-color : rgba(255,255,255,0.5);
//js
el.style.backgroundColor = "rgba(255,255,255,0.5)";
el.style['background-color'] = "rgba(255,255,255,0.5)";


//css
border-width : 2px
//js
el.style.borderWidth = "2px"
el.style['border-width'] = "2px";

Podobne zasady tyczą się odczytu styli, do którego używamy tej samej właściwości:


<div class="module">
    <h3 class="module-title">Przykładowa nazwa artykułu</h3>
    <div class="module-content" style="display:none">
        Lorem ipsum dolor sit amet...
    </div>
    <button type="button" class="button module-button" id="testShowContent">Pokaż treść</button>
</div>

const btn = document.querySelector(".module-button");
const textCnt = btn.previousElementSibling;

btn.addEventListener("click", function() {
    if (textCnt.style.display === "none") {
        textCnt.style.display = "block";
    } else {
        textCnt.style.display = "none";
    }
});

Przykładowa nazwa artykułu

Zauważ, że początkowe ukrycie treści uzyskałem przez display:none zapisane w atrybucie style danego elementu. Wrócimy do tej sprawy poniżej.

setProperty() i getPropertyValue()

Właściwość style udostępnia nam też dodatkowe metody, wśród których 2 kluczowe to:
style.setProperty(propertyName, value, priority*) - służy do ustawiania stylowania. Ostatni opcjonalny parametr priority służy do ewentualnego dodania do danych styli deklaracji !important. Najczęściej jest pomijany.
style.getPropertyValue(property) - służy do pobierania stylowania.


const btn = document.querySelector(".button");

btn.addEventListener("click", function() {
    if (this.style.getPropertyValue("font-size") !== "1.5rem") {
        this.style.setProperty("background-color", "#4BA2EA");
        this.style.setProperty("font-size", "1.5rem");
        this.style.setProperty("border-radius", "3rem");
    } else {
        this.style.setProperty("background-color", "");
        this.style.setProperty("font-size", "");
        this.style.setProperty("border-radius", "");
    }
});

getComputedStyle()

Jeżeli ustawiamy style za pomocą powyższych sposobów, są one wstawiane inline w atrybucie style danego elementu.
Żeby to sprawdzić, odśwież stronę, przejdź do debugera i zbadaj przycisk z pierwszego przykładu. Kliknij na niego i zobacz jak doklejane są style.

style przycisku

Podobna zasada tyczy się odczytu. Jeżeli dany element nie ma ustawionych styli inline (wpisanych z palca, lub za pomocą JavaScript), to nie jesteśmy w stanie ich pobrać:


const btn = document.querySelector("#testInline");
btn.addEventListener("click", function() {
    console.log(this.style['font-size']); //wypisze ""
    console.log(this.style.backgroundColor); //wypisze ""

    this.style['font-size'] = "1.5rem"; //ustawiamy style inline
    this.style.backgroundColor = "#4BA2EA"; //to samo z kolorem tła

    console.log(this.style['font-size']); //wypisze "1.5rem"
    console.log(this.style.backgroundColor); //wypisze "#4BA2EA"
});

Bardzo często będziemy chcieli jednak sprawdzić lub pobrać style, które są zadeklarowane w arkuszach stylów a nie inline w kodzie HTML. Przykładowo powyższy przycisk ma swój wygląd (np. background), który nadała mu klasa .button z pliku ze stylami.

Żeby pobrać takie stylowanie musimy skorzystać z metody getComputedStyle(elem, pseudoEl*) obiektu window, która zwraca przeliczone przez przeglądarkę stylowanie. Pierwszy parametr tej metody określa badany element, drugi - opcjonalny pozwala pobierać style dla pseudo elementów. Jeżeli nie chcemy badać pseudo elementów, wtedy używamy null, lub go pomijamy:


const btn = document.querySelector(".button");
const styleComputed = getComputedStyle(textCnt);

console.log( styleComputed.getPropertyValue("height") );
console.log( styleComputed.width );
console.log( styleComputed['background-color'] );

Gdy sprawdzisz w konsoli wynik powyższego skryptu, zorientujesz się, że dane zwracane przez omawianą metodę różnią się od tego, co zostało podane w CSS.
I tak w stylach kolor przycisku ma #f15c5c, a zwracany jest rgb(166, 14, 14). Podobnie z rozmiarem czcionki. W CSS jest on zapisany w jednostce rem, a zwracany jest w px. W tym ostatnim przypadku żeby uzyskać rezultat w rem trzeba wynik przeliczyć przez zwrócony rozmiar dla elementu html.

Wartość style pobrana za pomocą getComputedStyle jest właściwością tylko do odczytu. Próba ustawienia za jej pomocą nowych stylów zakończy się błędem: getComputedStyle error

Mając teorię za sobą, poprawmy wcześniejszy przykład i usuńmy z niego style inline:


<div class="module">
    <h3 class="module-title">Przykładowa nazwa artykułu</h3>
    <div class="module-content"> <!-- usuneliśmy atrybut style -->
        Lorem ipsum dolor sit amet...
    </div>
    <button type="button" class="button module-button" id="testShowContent">Pokaż treść</button>
</div>

const btn = document.querySelector(".button");
const textCnt = btn.previousElementSibling;

btn.addEventListener("click", function() {
    btn.addEventListener("click", function() {
        const styleComputed = getComputedStyle(textCnt);

        if (styleComputed.display === "none") {
            textCnt.style.display = "inline-block";
        } else {
            textCnt.style.display = "none";
        }
    });
});

Przykładowa nazwa artykułu

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Soluta itaque doloremque reiciendis illo placeat officia voluptate, et sapiente, laboriosam tempore exercitationem velit quis quibusdam iste molestiae alias obcaecati ut natus!

Aby pobrać style dla pseudo elementu musimy skorzystać z drugiego parametru tej metody:


const module = document.querySelector('.module');
const styleComputed = getComputedStyle(textCnt, ':before');

console.log(styleComputed.backgroundColor);

classList

W powyższych przykładach ustawialiśmy właściwości css za pomocą JavaScript. Niema co ukrywać - w dzisiejszych czasach starsze zasady tworzenia stron się nieco rozmywają. Jedna z takich zasad mówiła o oddzieleniu warstw od siebie. HTML to treść dokumentu, JavaScript odpowiada za manipulowanie taką treścią i dodawanie interakcji. Za wygląd odpowiedzialny jest CSS.
Właśnie dlatego w wielu przypadkach ustawianie wyglądu powinno się odbywać za pomocą CSS - a dokładnie klas w css, a JavaScript powinien tylko odpowiednio manipulować tymi klasami - dodawać je, odejmować itp. Dzięki temu jeżeli w przyszłości najdzie konieczność np. zmiany sposobu zmiany danego przycisku, zmienisz tylko odpowiednie klasy w CSS, a JavaScript zostawisz w spokoju.

Aby zarządzać klasami css danego elementu użyjemy właściwości classList, która udostępnia nam kilka metod:

add("nazwa-klasy") dodawanie klasy
remove("nazwa-klasy") usuwanie klasy
toggle("nazwa-klasy") przełączanie (jak nie ma to dodaje, jak jest to usuwa) klasy
contains("nazwa-klasy") sprawdza czy element ma taką klasę

const btn = document.querySelector('.btn');

btn.classList; //zwraca pseudo tablicę z nazwami klas
btn.classList.add('btn'); //dodaję klasę .btn
btn.classList.remove('btn'); //usuwam klasę .btn
btn.classList.toggle('btn'); //przełączam (dodaję lub usuwam) klasę .btn
btn.classList.contains('btn'); //sprawdzam czy dany element ma klasę .btn

Przykładowo mamy przycisk, który działa jak przełącznik. Po kliknięciu włączamy lub wyłączamy tryb edytowania jakiś pól:


const btn = document.querySelector('.btn-test-edit');
btn.addEventListener('click', function() {
    this.classList.toggle('editable'); //przełączam klasę buttonowi

    const inputs = this.form.querySelectorAll('input, textarea');

    if (this.classList.contains('editable')) { //sprawdzam czy button ma klasę
        //włącz tryb edytowania
        this.form.classList.add("edited");
        this.innerText = "Zakończ";
        for (const inp of inputs) {
            inp.disabled = false;
        }
    } else {
        //wyłącz tryb edytowania
        this.form.classList.remove("edited");
        this.innerText = "Edytuj";
        for (const inp of inputs) {
            inp.disabled = true;
        }
    }
});

className

Właściwość className zwraca nam jako tekst wszystkie klasy jakie posiada dany element:


const btn = document.querySelector('.btn');
console.log(btn.className); //btn btn-primary

W czasach gdy wsparcie dla classList było marginalne, była to główna właściwość, która pozwalała zarządzać klasami. Każdy taki manewr wymagał dodatkowego kodu:


const btn = document.querySelector('.btn');
const classText = "nazwaKlasy";

//add
btn.className += " "+classText;

//remove
const classText = classText;
const regPattern = new RegExp('(\\s|^)'+classText+'(\\s|$)');
btn.className = btn.className.replace(regPattern, '');

//contains
if (btn.className.indexOf('btn')!==-1) {
    console.log('Przycisk ma klasę btn');
}

//toggle
if (btn.className.indexOf('btn')!==-1) {
    btn.className += " "+classText;
} else {
    const regPattern = new RegExp('(\\s|^)'+classText+'(\\s|$)');
    btn.className = btn.className.replace(regPattern, '');
}

Ogólnie nie polecam stosować tej właściwości do manipulacji klasami, a tylko do ewentualnego odczytu nazwy klasy.

Zmienne w CSS

CSS także doczekał się swoich zmiennych. Ich podstawowe użycie ma postać:


:root {
    --color : red; /* zmienne deklarujemy poprzedzając ich nazwę -- */
}

.module-error {
    border:1px solid var(--color); /* a używamy ze słowem var */
    box-shadow: 0 1px 3px var(--color);
}
.module-error-title {
    color: var(--color);
}

Zmienne w CSS zachowują się mniej więcej tak jak zmienne w JS - czyli ich zasięg jest dostępny dla coraz bardziej zagnieżdżonych elementów (tak jak zmienne w JS są dostępne dla coraz bardziej zagnieżdżonych funkcji).

W powyższym kodzie zadeklarowałem pojedynczą zmienną w elemencie :root. Element taki dla stron jest równoznaczny z html (można pisać tak i tak). Dzięki temu zmienna taka jest dostępna dla całego dokumentu.

Takie zmienne możemy też deklarować w każdy elemencie z osobna. Dzięki temu dostępne są one tylko w tym elemencie (i dla jego dzieci):


.header {
    --font-size: 14px; /* zmienne dostępne tylko dla .header i jego dzieci */
    --color1: #222;
    --color2: #000;
    --color-link : #fff;

    background: linear-gradient(var(--color1), var(--color2));
}
.header a {
    color: var(--color-link); /* ok */
}

.element {
    /* jeżeli ten element nie jest w header to nie ma dostępu do powyższych zmiennych */
}

Zmienne CSS dają olbrzymie możliwości ich manipulacji. Możemy zmieniać ich wartość za pomocą zdarzeń :hover, :focus (i podobnych), możemy je zmieniać w kolejnych deklaracjach @media, a także za pomocą Javascriptu.


    .element {
    --color: red;

    transition: 0.5s;
    background: var(--color);
}

.element:hover {
    --color : blue;
}
.element:focus {
    --color : pink;
}

@media (max-width:500px) {
    .element {
        --color : yellow;
    }
}

const elem = document.querySelector('.element');
elem.style.setProperty("--color", "black");

Poniżej zamieszczam kilka przykładów jak to można wykorzystać w praktyce.

Przykład 1 - zmiana koloru

W pierwszym przykładzie za pomocą inputa typu color zmieniamy właściwości elementu:

Zmień kolor i najedź kursorem

<div class="element">
    Zmień kolor i najedź kursorem
</div>

<input type="color" class="color">

.element {
    --color : red;

    border: 2px solid var(--color);
    color: var(--color);
    padding: 30px;
    font-weight: bold;
    text-transform: uppercase;
    font-family: sans-serif;
    position: relative;
    background: #eee;
    transition: 0.5s;
}
.element:hover {
    background-color: var(--color);
    color: #fff;
}

const element = document.querySelector('.element');

const color = document.querySelector('.color');
color.addEventListener('change', function() {
    element.style.setProperty('--color', this.value)
})

Przykład 2 - losowa pozycja


<button type="button" class="button-move">
    Zamknij reklamę
</button>

.button-move {
    --ml : 0px;

    padding:0.5rem 1rem;
    transform:  translate(var(--ml));
    transition: 0.3s;
}

const element = document.querySelector('.button-move');

setInterval(function() {
    element.style.setProperty('--ml', Math.random() * 300 + "px");
}, 300)

Przykład 3 - zmiana po najechaniu

W kolejnym przykładzie pobieramy pozycję kursora na elemencie i ustawiamy mu pozycję pseudoelementu z gradientem. Dodatkowo na hover zmieniamy jego wielkość:


<button type="button" class="button-test">
    <span>KLIK</span>
</button>

.button-test {
    --size: 0;
    --left: 0;
    --top: 0;

    border: 0;
    padding: 20px 70px;
    border-radius: 50px;
    text-transform: uppercase;
    font-family: sans-serif;
    color: #fff;
    overflow: hidden;
    position: relative;
    background-color: tomato;
}

.button-test span {
    position: relative;
    z-index: 1;
}

.button-test:hover { /* wielkość gradientu zwiększam na hover */
    --size: 400px;
}

.button-test:after {
    content: '';
    position: absolute;
    left: var(--left);
    top: var(--top);
    width: var(--size);
    height: var(--size);
    background: radial-gradient(circle closest-side, #4405f7, tomato 60%);
    background-repeat: no-repeat;
    transform: translate(-50%, -50%);
    transition: 0.3s width, 0.3s height;
    z-index: 0;
}

const element = document.querySelector('.button-test');

element.addEventListener('mousemove', function(e) {
    this.style.setProperty('--left', e.pageX - this.offsetLeft + "px");
    this.style.setProperty('--top', e.pageY - this.offsetTop + "px");
})

Przykład 4 - zmiana pozycji elementów

Ostatni przykład możesz zobaczyć na oddzielnej stronie. To jeden z moich eksperymentów odnośnie animacji w CSS. Tutaj także wykorzystałem powyższe techniki do animowania elementów.