Na Frontendzie, w przypadku rozwojowych projektów, w pewnym momencie dochodzimy do wniosku, że style mogłyby być lepiej napisane? Jeśli spotykacie to na co dzień to oznaka, że warto podjąć pewne kroki, aby w przyszłości się to nie pojawiło. Naprawdę warto, chociaż wiem, że zmiana sposobu myślenia podczas pisania stylów to nie lada wyzwanie. Tak, też byłem sceptycznie do tego nastawiony, jednak przegryzłem się przez temat i wyrobiłem sobie pewną opinię – warto zmierzyć się z metodologią BEM.

Okoliczności powstania BEM ściśle wiążą się kilkoma kwestiami. Pierwszą z nich jest specyficzność CSS. Określa ona sposób w jaki przeglądarki interpretują style dla konkretnych węzłów. Specyficzność opiera się na regułach dopasowania, które składają się z różnych rodzajów selektorów CSS i wiąże się bezpośrednio z zagnieżdżeniami. BEM w tym przypadku ma za zadanie maksymalnie spłaszczyć strukturę wynikową stylów i uprościć dopasowanie. Prowadzić to ma do szybszego renderowania strony internetowej. O tej zalecie i przede wszystkim zaleceniu rozpiszę się jeszcze później, bowiem przez wielu z Was może być pomijany z uwagi na komfort w postaci zasobów sprzętowych w dzisiejszym świecie IT.

Druga okoliczność powstania BEM to dążenie do reużywalności komponentów. Oczywiście stroniąc od wykorzystania BEM (i innych metodologii) można tworzyć reużywalne komponenty, ale nakład pracy przy rozwoju i konserwacji tych elementów jest dużo wyższy. Za tym idą koszty i frustracja programistów, którzy w bliższej lub dalszej przyszłości muszą mierzyć się ze zmianami. Przy użyciu BEM stworzony komponent (blok) w sposób łatwy możemy przenosić do innych projektów, gdzie w optymalnym przypadku nie będzie trzeba ponosić żadnych kosztów, aby wyglądał i działał dobrze. Owszem może się zdarzyć, że inna czcionka w projekcie będzie propagować zmiany w szablonie komponentu, ale takie zabiegi są wpisane w ideę, a sam koszt będzie niski i nie popsuje samopoczucia programisty. Tak, to jest bezcenne.

Trzecia kwestia – uproszczony CSS wynikowy. Mogą pojawić się osoby, w których przekonaniu, wynikowy CSS będzie cięższy niż CSS tworzony bez jakiejkolwiek metodologii. Jestem innego zdania i postaram się tego dowieść w dalszej części wpisu. Zostańcie ze mną.

CSS specificity

O specyficzności, czyli zasadach dopasowania reguł do węzłów na podstawie selektorów dowiecie się więcej w już istniejących źródłach – np. CSS-Tricks. Znajdujemy tam bardzo ważną infografikę:

Specyficzność CSS

O tym jak silnik przeglądarki interpretuje selektory rozwodzi się szczegółowo Chris Coyier w przytoczonym artykule. Zasada jednak dla Was już teraz powinna być zrozumiała (oczywiście pominięte zostały style !important, których każdy szanujący się frontendowiec stara się nie używać). Unikamy również stylów inline (najwyższa specyficzność), bowiem ogranicza to możliwości zmian i manipulacji komponentem. Kolejnym grzechem jest permanentne stosowanie identyfikatorów w stylach. W tym przypadku priorytet jest również po stronie ID i reużywalność przestaje istnieć (wewnątrz projektu i przy założeniu czystej struktury CSS).

W tym momencie czas wspomnieć o BEM, gdyż opiera się on na klasach i pseudoklasach.

Korzystanie z atrybutów w CSS jest niepożądane z uwagi na relatywnie wolne renderowanie elementów.

BEM pozwala i zachęca do bardzo płaskiej struktury CSS – jednopoziomowej (maksymalnie dwupoziomowej za sprawą modyfikatora bloku – o tym później). To mocno usprawnia renderowanie. O wielopoziomowości i innych aspektach dowiecie się więcej z działu teoretycznego o BEM. Zatrzymamy się teraz na chwilę w temacie zagnieżdżeń selektorów.

Pewnie większość nie zdaje sobie sprawy z obciążenia przeglądarki przy renderowaniu strony. Takie mamy czasy, że wąskim gardłem zostaje w tym momencie prędkość internetu (choć coraz mniej) i zasilanie urządzeń, a nie sama moc obliczeniowa. Jednak warto do tematu podejść poważnie, bowiem cały czas korzystamy z urządzeń mobilnych, a tam pomimo, że mocy jest sporo, to jednak znacznie mniej niż w komputerach wszelkiej maści. Guru Chris Coyier w innym artykule sprzed kilku ładnych lat w bardzo prosty sposób opisał zasadę ładowania (dopasowania) selektorów do węzłów w drzewie DOM – Efficiently Rendering CSS.

Pozwolę sobie przytoczyć kilka ważnych informacji.

  1. Przeglądarki czytają nasz CSS od prawej do lewej (np. w selektorze ul > li a[title=”home”] to wytłuszczony element jest interpretowany najpierw – tzw. key selector.
  2. Istnieją 4 rodzaje kluczy: ID, klasa, tag i ogólne. Wymienione zostały od najefektywniejszych do najwolniejszych.
  3. Każde połączenie (zagnieżdżenie powoduje spowolnienie w myśl faktu czytania selektora od prawej do lewej).
    Np. #main-nav > li { } /* Slower than it might seem */
    Błędne jest myślenie, że szybko znaleziony ID pozwala szybko znaleźć dzieci. Selektor-tag jest dużo wolniejszy i to oddziałuje na czas renderowania.
  4. Nigdy nie używamy notacji ul#main-navigation{ }, gdyż ID z zasady jest unikalny w drzewie DOM. Gdy jednak na jednej stronie dany ID używany jest do innego tagu niż na drugiej i myślicie, że jest to konieczne to znaczy, że dużo nauki przed Wami 🙂
  5. Selektory potomków są „najdroższe” z punktu widzenia wydajności (szczególnie, gdy należy do kategorii tagów lub ogólnej).
  6. Wydajność renderowania nie jest już tak ważna, jak kiedyś.
  7. Bazowanie na ID i strukturze jednopoziomowej pozwala uzyskać optymalne czasy renderowania, ale … ocieramy się tutaj o śmieszność i utrudniamy utrzymanie w przyszłości.
  8. Nie można poświęcać semantyki i łatwości konserwacji dla optymalnej wydajności CSS.

Te 8 prawd Chrisa w pełni uzyskuje odzwierciedlenie w idei BEM. Semantyka pozostaje na nienaruszonym poziomie (korzystamy wyłącznie z klas), a konserwacja to czysta przyjemność (płaska struktura). Pozostałe punkty pozwalają na wniosek iż mamy do czynienia ze złotym środkiem. Sceptyków uprzedzam, że nie jest to jedyna słuszna metodologia. Jest jedną z wielu, ale w tym momencie bardzo trendy.

Jeśli ktoś byłby zainteresowany doświadczeniem, aby na własne oczy zobaczyć jak prezentują się wyniki (czasowe) renderowania strony w kontekście konkretnych selektorów odsyłam do artykułu Bena Fraina. Gdzie znajdziecie wyniki dla ówczesnych przeglądarek i link do aplikacji testowej pozwalającej przeprowadzić testy na aktualnych wersjach. Jedno jest pewne – twórcy przeglądarek robią magiczne zabiegi, aby opóźnienia renderowania były jak najmniejsze – w końcu priorytetem dla nich jest zadowolony użytkownik.

 

Reużywalne komponenty

Płaska struktura CSS to, krótko mówiąc, celowanie bezpośrednio w konkretny węzeł (lub węzły). W takiej konfiguracji nie martwimy się o konflikt przy przenoszeniu komponentu (w przypadku pecha trafiamy w taką samą klasę w systemie docelowym, ale jeśli korzystamy z SASS rozwiązanie problemu zajmuje chwilę, gdyż musimy zmienić jedną nazwę klasy w stylach – w jednym miejscu – i wystąpienia klasy w węzłach HTML). W przypadku klas tworzonych bez określonej metodologii, przenosząc komponent do innego projektu w najlepszym przypadku sporo się namęczymy – często nie mając pewności czy migracja przeszła bez uszczerbku. W najgorszym przypadku rezygnujemy z operacji przenoszenia komponentu. BEM ma to ułatwiać i rzeczywiście ułatwia.

Przykład komponentu zgodnego z metodologią BEM:

<!-- DO THIS -->
<figure class="photo photo--highlighted">
  <img class="photo__img" src="me.jpg">
  <figcaption class="photo__caption">Look at me!</figcaption>
</figure>

<style>
  .photo { } 
  .photo--highlighted .photo__img { }
  .photo--highlighted .photo__caption { }
  .photo__img { }
  .photo__caption { }
</style>

 

Uproszczony CSS

Przydałby się jakiś dowód, prawda? Już się robi. Poniżej znajdziecie przykład kodu SASS zarzucającego jakąkolwiek metodologię. Kilkukrotne zagnieżdżenia i media queries na głębokim poziomie oraz dodatkowe niepotrzebne zależności zaśmiecają kod. Zaniecham rozwodzenia się na tym, czy było to koniecznie, gdyż można się domyślać, że jest to „dekoracja” okienka modalnego z Bootstrapa.

Sami spójrzcie:

@mixin desktop {
  @media (max-width: 1366px) {
    @content;
  }
}
@mixin laptop {
  @media (max-width: 1199.98px) {
    @content;
  }
}
@mixin tablet {
  @media (max-width: 991.98px) {
    @content;
  }
}

#UserInvitationModal {
    .modal-dialog {
        .modal-body {
            .invitation-time-and-rate {
                display: flex;
                justify-content: space-evenly;
                align-items: flex-end;
                text-align: center;
                flex-shrink: 0;
                padding-bottom: 1.75rem;
                border-bottom: 1px solid rgba(155, 155, 155, 0.35);
                .time, .rate {
                    p {
                        &:first-of-type {
                            font-size: .75rem;
                            font-weight: 400;
                            text-transform: uppercase;
                            opacity: .85;
                            color: #f2b263;
                            margin-bottom: .4rem;
                        }
                        &:last-of-type {
                            font-size: 2rem;
                            font-weight: 700;
                            color: #fff;
                            margin-bottom: 0;
                        }
                    }
                }
                .rate {
                    p {
                        span {
                            font-weight: 300;
                            font-size: 1.5rem;
                        }
                    }
                }
            }
            .win-lose-info {
                display: flex;
                flex-flow: column;
                justify-content: center;
                i {
                    color: #d2b263;
                }
                .win-info, .lose-info {
                    display: flex;
                    justify-content: center;
                    max-width: 390px;
                    margin: auto;
                    padding: 0 1rem;
                    &>i {
                        font-size: 1.25rem;
                        margin-right: 1.6rem;
                    }
                    &>p {
                        font-weight: 300;
                        font-size: .875rem;
                        min-width: 7rem;
                        color: rgba(255, 255, 255, .75);
                        @include tablet {
                            min-width: auto;
                            padding: 0 .25rem;
                        }
			@include laptop {
                            min-width: 6rem;
                            padding: 0 .15rem;
                        }
                        &:last-of-type {
                            min-width: auto;
                        }
                    }
                    &>i, &>p {
                        margin-top: .8rem;
                        margin-bottom: .8rem;
                    }
                    strong {
                        color: #fff;
                        font-weight: 600;
                    }
                }
                .win-info {
                    border-bottom: 1px solid rgba(155, 155, 155, 0.35);
                }
            }
        }
    }
}

 

Dla powyższego kodu, wynikowy CSS ma rozmiar 3.29KB. Dla uproszczenia z porównywanego CSS usunę pierwsze 3 węzły, gdyż są zupełnie zbędne – opóźniają renderowanie, zwiększają rozmiar pliku CSS i utrudniają konserwację. Wyobraźcie sobie teraz wprowadzenie modyfikatora do całego #UserInvitationModal. Jeśli zmiany mają dotyczyć węzłów zawartych wewnątrz, trzeba odwzorować całą ścieżkę (selektor) i nadpisać style, które nas interesują. Jeśli dochodzą media queries sytuacja się powtarza. Przy takich zagnieżdżeniach stosunek samych selektorów do stylów w pliku CSS jest bardzo duży.

Usunięcie trzech przodków od lewej strony automatycznie powoduje, że plik wynikowy CSS ma rozmiar 2.05KB – 37,69% mniej.

Teraz pozwoliłem sobie na przerobienie stylów na podejście z BEM. Arkusz znajduje się poniżej:

@mixin desktop {
  @media (max-width: 1366px) {
    @content;
  }
}
@mixin laptop {
  @media (max-width: 1199.98px) {
    @content;
  }
}
@mixin tablet {
  @media (max-width: 991.98px) {
    @content;
  }
}

.invitation-data {
    display: flex;
    justify-content: space-evenly;
    align-items: flex-end;
    text-align: center;
    flex-shrink: 0;
    padding-bottom: 1.75rem;
    border-bottom: 1px solid rgba(155, 155, 155, 0.35);
    
    &__detail {
        &:first-of-type {
            font-size: .75rem;
            font-weight: 400;
            text-transform: uppercase;
            opacity: .85;
            color: #f2b263;
            margin-bottom: .4rem;
        }
        &:last-of-type {
            font-size: 2rem;
            font-weight: 700;
            color: #fff;
            margin-bottom: 0;
        }
    }
    &__distinction {
        font-weight: 300;
        font-size: 1.5rem;
    }
}
.win-lose {
    display: flex;
    flex-flow: column;
    justify-content: center;
    &__distinction {
        color: #d2b263;
    }
    &__info {
        display: flex;
        justify-content: center;
        max-width: 390px;
        margin: auto;
        padding: 0 1rem;
        &--win {
            border-bottom: 1px solid rgba(155, 155, 155, 0.35);
        }
    }
    &__value {
        font-size: 1.25rem;
        margin: .8rem 1.6rem .8rem 0;
    }
    &__desc {
        font-weight: 300;
        font-size: .875rem;
        min-width: 7rem;
        color: rgba(255, 255, 255, .75);
        margin: .8rem 0;
        &:last-of-type {
            min-width: auto;
        }
        @include tablet {
            min-width: auto;
            padding: 0 .25rem;
        }
	@include laptop {
            min-width: 6rem;
            padding: 0 .15rem;
        }
    }
    &__lead {
        color: #fff;
        font-weight: 600;
    }
}

 

Jak widać powyżej pojawiły się dwa reużywalne komponenty – invitation-data  oraz win-lose – chociaż równie dobrze można było to zawrzeć w jednym. Nie wiem, jak wygląda to okienko modalne, nie wiem również, czy te dwie części w jego treści są wykorzystywane w kilku miejscach. Programista mając taką wiedzę może, a wręcz musi już na etapie tworzenia semantycznego kodu HTML takie decyzje podejmować lub przynajmniej mieć z tyłu głowy.

Pamiętajcie, że to jest tylko przykład i nie gwarantuję, że u Was zmiana na BEM pozwoli na tak wymierne korzyści jak tutaj, ani też nie zapewniam, że to jest maksymalny zysk. Wszystko zależy od tego w jakiej postaci mamy dotychczasowy kod. To dotyczy migracji CSS do BEM. Jeśli postanowicie z BEM korzystać od początku budowania projektu to korzyści są dużo większe i w pełni skorelowane z porządkiem i przyjemnością pracy.

Powyższy przykład skompilował się do pliku CSS i uzyskał rozmiar 1.47KB – zysk 28.29%. Od pierwotnego brzydkiego kodu uzyskujemy zysk 55.31%.

Postawię nieśmiało tezę, że przechodząc na BEM zyskujemy, nigdy tracimy.

 

BEM – teoria

Nazwa wywodzi się ze struktur, które uwzględnia metodologia. Blok, element i modyfikator to składowe komponentów. Blok może istnieć bez elementów, jednak element bez bloku już nie. Modyfikator natomiast może być dla bloku lub elementu.

Zapożyczę ładny przykład od Robina Rendle:

Mamy komponent z przyciskiem (klasa .btn) składający się z dwóch elementów: .btn__price.btn__text. Modyfikatory odpowiadają za kolor i wielkość. Ten przykład możecie przeskalować na dowolnie duży komponent. Może to być formularz, formularz logowania (jako modyfikacja lub osobny byt), nagłówek, stopka, menu, newsletter, artykuł. Pamiętajcie, że każdy komponent może zawierać inny, ale nie może być od niego uzależniony. Inaczej mówiąc w stylach jednego komponentu nie może być selektora innego komponentu.

 

BEM i SASS

Patrząc na mój przykład i przykład zapożyczony widzicie, że nadal korzystamy z SASS i wykorzystujemy jego potencjał w maksymalnym stopniu. Znak & służy nam do tego, aby nie duplikować klasy bloku/komponentu, a w przypadku modyfikatorów elementów, by nie duplikować klasy elementu.

Istnieje jeszcze jeden przypadek, który nie wystąpił w przykładach. Mianowicie, jeśli chcemy uzależnić style elementu od modyfikatora bloku, wówczas w uproszczonej wersji należałoby napisać pełną klasę elementu, ale tutaj SASS też przychodzi z pomocą. Wykorzystanie tego podejścia jest opcjonalne i zależy od preferencji Frontend Developera.

Podejście standardowe:

.invitation-data {
  font-size: 12px;
    
  &--lead {
    font-size: 16px;
    
    .invitation-data__detail {
      color: #f00;
    }
  }
    
  &__detail {
    color: #000;
  }
}

Podejście alternatywne:

.invitation-data {
  $self: &;
  font-size: 12px;
    
  &--lead {
    font-size: 16px;
    
    #{$self}__detail {
      color: #f00;
    }
  }
    
  &__detail {
    color: #000;
  }
}

 

Podejście alternatywne pozwala w zmiennej przechowywać klasę bloku i wykorzystać ją kiedy jest zależność elementu od modyfikatora bloku. Takich sytuacji może być więcej, gdy chcemy uzależnić styl elementu od innego elementu wykorzystując np. selektor ~ lub +. Zatem warto wiedzieć o możliwościach SASS.

Jako ciekawostkę uznajcie, że pracując w metodologii BEM można wykorzystać bez problemu SASS’owe domieszki (mixins) – odnośnik do code snippets.

 

Zalety

  • modułowość
  • niski specificity CSS (to wymóg modułowości)
  • łatwa kontrola zmian
  • wymuszanie dobrych praktyk i jednolite zasady dla każdego członka zespołu (w tym konwencja nazewnicza)
  • niski próg wejścia i koszty konserwacji

 

Wady

  • rozszerzony HTML o nieco dłuższe nazwy klas (tutaj gdy używamy Angulara podobno wtyczka angular-bem jakoś skraca zapis – nie mam o tym wiedzy, więc zapraszam do komentowania)
  • wymuszenie zerwania z nawykami tworzenia zagnieżdżonych selektorów

 

Prywatne zmiany metodologii

Sam nie podchodzę do BEM tak restrykcyjnie, jak zakłada to jej standard. Poluzowałem trochę obostrzenia i zdarza mi się nie tworzyć klas-elementów do węzłów takich jak np. strong, czy span. Dopuszczam ich zagnieżdżenie w selektorze. Dotyczy to jednak węzłów inline i stylizacji fontów oraz jeśli węzeł nie ukazuje się jako odrębny element. Taki zabieg niemal nie zwiększa specificity CSS, a pozwala uniknąć śmiesznych sytuacji, gdy treść w węźle jest dużo lżejsza od samego węzła (atrybutu class). Zdaję sobie sprawę, że ewangeliści BEM  w tym momencie na mnie krzywo patrzą, jednak myślę, że trzeba wypracować w zespole nawyki, które wspólnie będą wszystkich satysfakcjonować.

 

Podsumowanie

Sam długo przekonywałem się do BEM, a teraz nie wyobrażam sobie pisania stylów bez dostosowania się do metodologii. Powyżej zetknęliście się ze sporą dawką wiadomości, które zachęcam byście przeanalizowali, jeśli dopiero zamierzacie wykorzystać BEM w swojej pracy.

Na deser przedstawiam trzy filmy, w których twórca stara się w sposób maksymalnie zrozumiały przedstawić najważniejsze informacje o BEM. Zdaję sobie sprawę, że niektóre osoby niezwykle ciężko przekonać do nowych rzeczy, a ja sam nie jestem wybitnym blogerem, ani mówcą, by przekonać takie osoby. Zatem odsyłam do innych źródłem. Internet pełen jest treści o BEM. Pozdrawiam i miłego seansu.