Mimo dużej popularności BEMa, bardzo ciężko znaleźć w Internecie przykłady i opisy bardziej rozbudowane, niż podstawowy nagłówek + wyszukiwarka. W tym dokumencie przedstawię podstawy działania tego systemu, jego korzyści, wady, a także mniej i bardziej rozbudowane przykłady użycia.
Blok, Element, Modyfikator to metodologia stworzona głównie po to, by walczyć ze specyficznością reguł w CSS. Specyficzność (ang. specificity) określa ilość zagnieżdzeń, a przez to poziom skomplikowania wynikowego kodu.
Duży wzrost specyficzności przypisywany jest pojawieniu się preprocesorów takich jak LESS czy S(C|A)SS. Tworzenie tam kolejnych zagnieżdżeń jest bardzo proste, przez co bardzo szybko kod zaczyna być niepotrzebnie skomplikowany i trudny w użyciu.
BEM rozdziela wszystkie elementy wyglądu na trzy kategorie - bloki, ich elementy i modyfikatory.
- Blok to coś na kształt rodzica danego komponentu. Może się zdarzyć, że nie będzie miał dzieci - będzie to przycisk lub coś podobnego.
- Element to dziecko komponentu. Należy pamiętać, że element nie może już mieć dzieci - nawet jeżeli wizualnie znajduje się w środku.
- Modyfikator to dodatkowy wariant danego bloku czy elementu. Może różnić się od podstawy w dość nieznaczny sposób. Jeżeli zmian jest dużo, warto rozważyć stworzenie nowego elementu lub nawet bloku.
Tu przedstawiam prosty schemat wyglądu:
BEM, tak samo jak każda inna metodologia, ma swoje zalety, ale też wady. W Internecie często można natknąć się na dużą listę tych pierwszych, a mało kiedy na jakąkolwiek listę drugich.
Zacznijmy jednak od tych dobrych stron:
- modułowość - tworząc komponenty w BEMie mamy możliwość używania ich w kilku miejscach jednocześnie, nie martwiąc się przy tym, czy umieszczenie go wewnątrz innego elementu nie zmienimy niechcący wyglądu. Ponadto, wszystkie elementy danego komponentu są de-facto oddzielnym bytem możliwym do wykorzystania osobno. To bardzo ułatwia późniejszą pracę, kiedy graficy korzystają z gotowych elementów.
- niska specyficzność - rzecz idealna jeżeli chodzi o renderowanie wyglądu przez przeglądarkę. Im więcej zagnieżdżeń, tym przeglądarka musi wykonać więcej pracy. Jeżeli nie musi wchodzić głęboko, pracuje szybciej.
- zachowanie zmian - zmiejąc
.class-one
wiesz, że modyfikujesz tylko ten element. Nie będzie niespodzianek polegających na tym, że w innym pliku ktoś również stylował tę klasę. - wymuszenie dobrych praktyk - BEM jako metodologia jest dość restrykcyjna, przez co wymusza na programiście trzymanie się ustalonych wcześniej reguł (zachowanie katalogów, nazewnictwo itd).
To podstawowe zalety korzystania z tej metodologii. Jakie są wady?
- nieładny HTML - to największa wada - nagle elementy mają długie, złożone nazwy.
- narzut pracy - na początku faktycznie może się wydawać, że BEM wymaga większego nakladu pracy, ale rezultat zdecydowanie wynagradza tę niedogodność.
- zerwanie z nawykami - tak naprawdę, to jest największa wada BEMa w oczach programistów, którzy zaczynają
z tym standardem. Wymaga porzucenia myślenia na zasadzie
.parent > .children ( > .grandchildren )
, a wymusza.element {} .element__children {}, element__grandchildren {}
lub nawet.element {} .children {} .grandchildren {}
.
Przygotowałem niewielką grafikę przedstawiająca popularną kartę użytkownika, zawierającą awatar, nazwę, kilka zdań,
przycisk i ikonki socjalne. Uznałem, że każdy element jest zależny od klasy .user-card
. Wygląda tak:
Na pierwszy rzut oka możemy wyróżnić:
- awatar;
- nazwę użytkownika;
- opis;
- przycisk do wysyłania wiadomości;
- przycisk do facebooka;
- przycisk do twittera.
Jak wcześniej napisałem, założymy że wszystkie te elementy pierwszy raz pojawiają się na tej karcie. Nasz kod HTML będzie następujący:
<figure class="user-card">
<img src="./path/to/avatar.jpg" alt="Monty Burns" class="user-card__avatar">
<figcaption>
<h3 class="user-card__name">Monty Burns</h3>
<p class="user-card__description">Burns' state of mind is the subject of frequent jokes on the show.
At times, he appears to be completely removed from reality and modern conventions.</p>
<a href="#" class="user-card__button">Wyślij wiadomość</a>
<ul class="user-card__social-wrapper">
<li class="user-card__social-item">
<a href="http://facebook.com/montyburns" class="user-card__social-icon user-card__social-icon--facebook">Facebook</a>
</li>
<li class="user-card__social-item">
<a href="http://twitter.com/montyburns" class="user-card__social-icon user-card__social-icon--twitter">Twitter</a>
</li>
</ul>
</figcaption>
</figure>
Nie będę tu pisał stylowania całości, podam za to zapis dla LESS i SCSS:
.user-card {
...
&__name {
...
}
&__description {
...
}
&__button {
...
}
&__social-wrapper {
...
}
&__social-item {
...
}
&__social-icon {
...
&--facebook {
...
}
&--twitter {
...
}
}
}
Jak widzicie, kod ma dwa zagnieżdżenia - jedno dla elementów i drugie dla modyfikatorów. Najważniejsze to to,
żeby nie tworzyć elementu w elemencie, co wydaje się być dość oczywiste przy okazji elementu .user-card__social-wrapper
i jego dzieci.
Wszystkie powstałe tutaj elementy można teraz wykorzystywać w każdym miejscu w naszym projekcie. Jednak - przynajmniej
dla mnie, jest coś nieprzyjemnego w korzystaniu z klasy user-card__name
w opisie produktu czy czegoś podobnego.
Na szczęście możemy to łatwo obejść.
Weźmy teraz nasz przykład i dostosujmy go do bardziej realnych standardów. Przycisk służący do kontaktu, nazwa i tekst, przyciski socialowe - te wszystkie elementy przeniesiemy do komponentów globalnych - takich, które mogą być użyte tutaj i w setkach innych miejsc.
<figure class="user-card">
<img src="./path/to/avatar.jpg" alt="Monty Burns" class="user__avatar">
<figcaption>
<h3 class="box__name">Monty Burns</h3>
<p class="article__text article__text--small">Burns' state of mind is the subject of frequent jokes on the show.
At times, he appears to be completely removed from reality and modern conventions.</p>
<a href="#" class="button button--blue">Wyślij wiadomość</a>
<ul class="social-icons">
<li class="social-icons__item">
<a href="http://facebook.com/montyburns" class="social-icons__icon social-icons__icon--facebook">Facebook</a>
</li>
<li class="social-icons__item">
<a href="http://twitter.com/montyburns" class="social-icons__icon social-icons__icon--twitter">Twitter</a>
</li>
</ul>
</figcaption>
</figure>
W tej chwili CSS wygląda tak:
.social-icons {
...
&__item {
...
}
&__icon {
...
&--facebook {
...
}
&--twitter {
...
}
}
}
.box {
...
&__title {
...
}
}
.article {
...
&__text {
...
&--small {
...
}
}
}
.user {
...
&__card {
...
}
}
Dzięki tej zmianie współdzielimy style z innymi elementami, które wyglądają tak samo. Mamy przez to kilka małych klas-komponentów, które mogą być użyte gdziekolwiek indziej na stronie.
Korzystanie z modyfikatorów wydaje się mieć jedną podstawową wadę. Gdy chcemy zmienić jakiś blok, każdy element także musi otrzymać dodatkową klasę. To jest jedno z działających i całkowicie poprawnych rozwiązań, jednak jest inne, lepsze.
Używanie zmiennej $at-root
pozwala na zrobienie zagnieżdżeń w stylu akceptowalnym przez BEM. Używać jej należy
jednak tylko do obsługi modyfikatorów, nigdy do dziedziczenia elementów.
Metoda wygląda nastepująco:
.block {
$at-root: &; // zdefiniowanie zmiennej jako & czyli obecny element (w tym przypadku - .block)
...
&__title {
font-size: 1em;
}
/* ==== modificators */
&--large { // modyfikator
#{$at-root}__title { // używamy zmiennej $at-root, przez co uzyskujemy selektor .block__title
font-size: 15em;
}
}
}
Użycie takiej nazwy zmiennej na początku może powodować niejasności, dlatego warto zmienić nazwę tej zmiennej na
camelCase odpowiadający klasie. W powyższym przykładzie będzie to $block
, w przypadku klasy .user-block
-
$userBlock'.
Ten sam przykład będzie teraz wyglądał tak:
.block {
$block: &;
...
&__title {
font-size: 1em;
}
/* ==== modificators */
&--large { // modyfikator
#{$block}__title { // używamy zmiennej $block, przez co uzyskujemy selektor .block__title
font-size: 15em;
}
}
}
Korzystanie z media queries podczas pisania CSSa jest praktycznie nieuniknione. Jedynie w niewielkich komponentach, które zawsze wyglądają tak samo można je pominąć. W znacznej większości jednak będą one obecne. Przy pracy z nimi warto pamiętać o schemacie używania ich na zasadzie blokowej - wpisywania całego bloku odpowiedzialnego za dany przedział na samym dole komponentu. Oto, o czym mówię (numery w nawiasach pokazują, gdzie nadpisywanie są dane właściwości):
.block {
font-size: 1.5em; // (1)
...
&__title {
color: #ff0000; // (2)
}
&__text {
font-size: 2em; // (3)
}
/* === media queries */
@media (min-width: 768px) {
font-size: 1.25em; // (1)
&__title {
color: #ffff00; // (2)
}
}
@media (min-width: 1024px) {
&__text {
font-size: 1.5em; // (3)
}
}
}
Jak widać, notacja ta pomaga skupić się na konkretnej rozdzielczości, a pracując z metodologią mobile-first jest to niesamowicie przydatne.
Korzystanie z BEMa jest w zasadzie bardzo proste. Jedyne, czego trzeba pilnować to pamiętanie o tym, by elementy tworzyć tylko wewnątrz bloków.
Autor: Tomek Buszewski