آموزش روش BEM برای طراحی سایت: قسمت اول: مرور کلی

آموزش روش BEM برای طراحی سایت

یکی از دغدغه‌هایی که در پروژه‌های بزرگ وجود دارد، بحث توسعه و نگهداری کدهای آن‌ها است. در طراحی سایت و مخصوصا در کدهای مربوط به قسمت Frontend نیز این چالش وجود دارد. یکی از چالش‌های مربوط به توسعه کدهای Frontend بحث نامگذاری کلاس‌ها است. اساسا کلاس‌ها در طراحی وب بسیار مهم هستند. کلاس‌ها نمود بسیار زیادی در کدهای HTML، CSS، JavaScript یا Sass دارند بنابراین بسیار مهم است که چگونه نام کلاس‌ها را انتخاب کنیم. یکی از روش‌های جالب و بسیار کارآمدی که به قول معروف انقلابی در Frontend کارها به وجود آورد، روش BEM است. خب سوال: روش BEM چیست؟ چرا باید از روش نامگذاری BEM استفاده کرد؟ روش BEM چه مزیت‌هایی دارد؟ و کلی سوال دیگه برای شما ممکن است پیش بیاید که ما در این دوره آموزشی سعی داریم به طور مفصل درباره این روش توضیح دهیم.

روش BEM چیست؟

روش BEM مخفف کلمه Block Element Modifier روشی کامپوننت محور برای توسعه صفحات وب و کاهش حجم کدها است. در این روش هر رابط کاربری به کامپوننت‌های مستقلی تقسیم می‌شود که اساس کدهای شما را تشکیل می‌دهند. در واقع این روش به شما می‌گوید که کدهای خود را به قسمت‌های مستقلی تقسیم‌بندی کنید تا بتوانید بدون هیچ وابستگی کد هر قسمت را در تمامی پروژه استفاده کرد. با این متدولوژی توسعه کدهای پیچیده شما آسان‌تر و سریع‌تر خواهد بود. با این روش امکان استفاده مجدد کدهای هر قمست و اشتراک آن‌ها با دیگران به راحتی برای شما فراهم می‌شود.

خب شاید برای شما هنوز مفهوم کامپوننت در روش BEM واضح و روشن نباشد. در واقع کامپوننت به بلاکی از کد گفته می‌شود که بتواند به صورت مستقل از سایر کدها عمل کند و وابستگی به کدهای دیگر نداشته باشد و بتوان به ‌راحتی آن را در جاهای دیگر پروژه استفاده کرد. اگر بخواهیم مثالی برای یک کامپوننت بزنیم می‌توانیم به کارت اشاره کنیم. در تصویر زیر یک کارت و اجزای تشکیل‌دهنده آن مشخص شده است. شما می‌توانید این کد را بارها در پروژه استفاده کنید. ولی عناصری مانند یک آیتم لیست یا دکمه کارت یا یکی از قسمت‌های تب را نمی‌توان به عنوان یک بلوک مستقل در نظر گرفت زیرا به عناصر parent خود وابستگی دارند و بدون آن‌ها معنایی ندارند. مثلا اگر یک تگ li را بدون ul در نظر بگیریم، به تنهایی معنایی ندارد.

کامپوننت در روش نامگذاری BEM
Component in BEM

هر کامپوننت در روش نامگذاری BEM از عناصر Block، Element و Modifier تشکیل می‌شوند. به صورت کلی نام یک کلاس در روش نامگذاری BEM از دستور زیر پیروی می‌کند:

.block__element--modifier

در ادامه هر یک از این سه بخش را به صورت مختصر توضیح می‌دهیم.

بلاک (Block) در روش BEM

به اجزا مستقلی که قابلیت استفاده مجدد را در پروژه دارند، Block گفته می‌شود. در واقع بلاک مشخص می‌کند که ماهیت و هدف کامپوننت درباره چیست؟ درباره منو است یا کارت یا تب یا لیست یا فوتر و …. در مثال زیر می‌بینید که برای کامپوننتی که برای نمایش خطا هست استفاده از کلمه error برای نام کامپوننت درست است زیرا ماهیت و هدف آن را شرح می‌دهد اما نام red-text نام اشتباهی است زیرا هدف کامپوننت را به درستی شرح نمی‌دهد و با دیدن آن به یاد متن قرمز رنگ می‌افتید. پس حتما بایستی در انتخاب نام عنصر Block دقت کنید.

<!-- Correct. The `error` block is semantically meaningful -->
<div class="error"></div>

<!-- Incorrect. It describes the appearance -->
<div class="red-text"></div>

💡 نکته اساسی: بهترین تمرین برای یادگیری روش BEM با نام کلاس‌ها هست زیرا فقط نام کلاس‌ها را می‌توان در جاهای دیگر برای ایجاد کدهایی با قابلیت استفاده مجدد به‌کار برد. در این روش برای استایل‌دهی به کلاس‌ها فقط باید با نام کلاس کار کرد و از نام IDها نباید به عنوان انتخابگر استفاده کرد.

بلاک‌ها می‌توانند به صورت تودرتو قرار بگیرند و به تعدادهای متفاوتی می‌توان بلاک تودرتو داشت. برای مثال یک بلاک هدر می‌تواند شامل لوگو، فرم سرچ و یا بلاک‌های دیگری باشد.

<!-- `header` block -->
<header class="header">
    <!-- Nested `logo` block -->
    <div class="logo"></div>

    <!-- Nested `search-form` block -->
    <form class="search-form"></form>
</header>
روش BEM
Block in BEM

همچنین برای درک بهتر فرض کنید بخواهید با روش نامگذاری BEM یک بلاک فرم را نامگذاری کنید. برای اینکار باید از class attribute در تگ‌های html استفاده کنید. توجه کنید که نام بلاک را همیشه نامی یکتا و غیرتکراری انتخاب کنید زیرااز همین نام بلاک در بخش‌های داخلی کامپوننت می‌توان استفاده کرد.

.form { /* styles */ }
<form class="form" action="/">

Element در روش BEM

Element قسمتی از یک بلاک است که به صورت مستقل نمی‌تواند استفاده شود و فقط با بلاک است که مفهوم می‌یابد. هر Element فقط می‌تواند یک بلاک پدر داشته باشد. در واقع یک element یک کامپوننت در یک بلاک است که وظایف خاصی را انجام می‌دهد. نام Element به صورت زیر است. در ابتدا نام بلاک پدر و سپس __ و در نهایت نام Element می‌آید. block-name__element-name . در واقع نام Element جدا از اینکه خودش چندبخشی باشد، ابتدا با نام بلاک پدر شروع شده و سپس __ و درنهایت اگر نام Element چند بخش باشد هم با یک – جدا می‌شود.

.block__element { /* styles */ }

نام یک element در واقع باید مشخص‌کننده هدف آن باشد به‌طور مثال متن، آیتم، لیست، تصویر و … باشد نه اینکه مشخص‌کننده حالت مانند قرمز، عریض، بزرگ یا … باشد. برای مثال یک element مانند تصویر در یک بلاک card. به‌صورت .card__image نامگذاری می‌شود. در مثال زیر تصویر، متن، توضیحات و دکمه از elementهای کارت می‌باشند و به بلاک card متصل می‌شوند.

<body>
    <div class="card">
      <img class="card__image" src="…" />
      <h2 class="card__title">…</h2>
      <p class="card__description">…</p>
      <a class="card__button">…</a>
    </div>
</body>
.card { /* styles */ }
.card__image { /* styles */ }
.card__title { /* styles */ }
.card__description { /* styles */ }

ویژگی تودرتویی برای Elementها در روش BEM

Elementها می‌توانند تا هر عمقی به صورت تودرتو استفاده شوند. فقط توجه کنید که یک Element همیشه بخشی از یک بلاک است نه یک Element دیگر. یعنی نباید نام یک Element را به صورت سلسله‌مراتبی به فرم block__elem1__elem2 نوشت بلکه نام Element فقط از نام بلاک پدر گرفته می‌شود. در مثال زیر هر دو حالت درست و اشتباه آورده شده است.

<!--
    Correct. The structure of the full element name follows the pattern:درست
    `block-name__element-name`
-->
<form class="search-form">
    <div class="search-form__content">
        <input class="search-form__input">

        <button class="search-form__button">Search</button>
    </div>
</form>

<!--
    Incorrect. The structure of the full element name doesn't follow the pattern:اشتباه
    `block-name__element-name`
-->
<form class="search-form">
    <div class="search-form__content">
        <!-- Recommended: `search-form__input` or `search-form__content-input` -->
        <input class="search-form__content__input">

        <!-- Recommended: `search-form__button` or `search-form__content-button` -->
        <button class="search-form__content__button">Search</button>
    </div>
</form>

با نام بلاک یک حالتی مانند namespace تعریف می‌شود به‌گونه‌ای که از نامگذاری Element مشخص است که چه بلاکی پدر این Element است و به چه بلاکی وابسته هست. یعنی شما با دیدن نام کلاس search-form. سریعا متوجه می‌شوید که این کلاس متعلق به یک بلاک است و با نام search-form__icon متوجه می‌شوید که این کلاس متعلق به یک Element است که به بلاک search-form وابسته است. در مثال زیر با اینکه عناصر Element به صورت تودرتو تعریف شدند اما در css با روش BEM همه Elementهای یک بلاک به صورت ساختار مسطح و با اولویت یکسان دیده می‌شود.

<div class="block">
    <div class="block__elem1">
        <div class="block__elem2">
            <div class="block__elem3"></div>
        </div>
    </div>
</div>
.block {}
.block__elem1 {}
.block__elem2 {}
.block__elem3 {}

یکی از امکانات روش BEM این است که شما به راحتی بدون اینکه فایل CSS و قواعد نامگذاری شما به هم بریزد بتوانید Elementهای خود را جابجا کنید.

<div class="block">
    <div class="block__elem1">
        <div class="block__elem2"></div>
    </div>

    <div class="block__elem3"></div>
</div>

ویژگی عضویت برای Elementها در روش نامگذاری BEM

یک Element همیشه به‌عنوان عضوی وابسته از یک بلاک حساب می‌شود و نمی‌تواند مستقل از بلاک باشد. مثال زیر یک حالت درست استفاده از Element را نشان می‌دهد.

<!-- Correct. Elements are located inside the `search-form` block -->
<form class="search-form">
    <!-- `input` element in the `search-form` block -->
    <input class="search-form__input">

    <!-- `button` element in the `search-form` block -->
    <button class="search-form__button">Search</button>
</form>

اما مثال زیر نحوه استفاده اشتباه Element را نشان می‌دهد.

<!--
    Incorrect. Elements are located outside of the context of
    the `search-form` block
-->
<!-- `search-form` block -->
<form class="search-form">
</form>

<!-- `input` element in the `search-form` block -->
<input class="search-form__input">

<!-- `button` element in the `search-form` block-->
<button class="search-form__button">Search</button>

ویژگی اختیاری بودن برای Elementها در روش نامگذاری BEM

قواعد BEM به شما می‌گوید که حتما نباید هر بلاک لزوما دارای Element باشد و ممکن است بلاکی که شما بر اساس نیاز خود ایجاد می‌کنید، اصلا دارای Element نباشد. در مثال زیر هر کدام از تگ‌ها به خودی خود بلاکی هستند که نیاز به Element ندارند.

<!-- `search-form` block -->
<div class="search-form">
    <!-- `input` block -->
    <input class="input">

    <!-- `button` block -->
    <button class="button">Search</button>
</div>

سوال؟ چطوری تشخیص بدهم کجا باید از بلاک استفاده کنم کجا Element

اول: اگر قطعه‌ای کدی دارید که هدف خاصی را نشان می‌دهد مانند سبد خرید، کارت محصول، منو، لوگو، اشتراک گذاری در خبرنامه، تبلیغ، فوتر، جستجو یا غیره. دوم: این قطعه کد شما در جاهای دیگر قابل استفاده مجدد باشد سوم: بتواند مستقل از دیگر کامپوننت‌ها عمل کند و وابستگی نداشته باشد از block استفاده کنید. اما اگر نتوانید از آن قطعه کد به صورت مستقل و مجزا استفاده کنید و همچنین قابلیت استفاده مجدد نداشته باشد مانند لیستی در سبد خرید، تصویر یک محصول در کارت محصول، تصویر و عنوان یک تبلیغ، آیکون جستجو و… باید از Element استفاده کنید.

Modifier در روش BEM

به ماهیتی که حالت، رفتار،استایل یا ظاهر یک Block یا Element را مشخص می‌کند Modifier گفته می‌شود. استفاده از این کلاس اختیاری است. نامگذاری این ماهیت به صورت زیر است.

.block--modifier { /* styles */ }
.block__element--modifier { /* styles */ }

Modifierها می‌توانند در زمان Runtime نیز تغییر کنند. فرض کنید فرمی دارید که می‌خواهید برای ورودی‌هایش پس از کلیک بر روی دکمه ثبت درصورتی که کاربر مقدار در ورودی وارد نکرده باشد، پیغام خطایی نمایش یا عدم‌نمایش داده شود.

به طور مثال در تصویر زیر برای بلاک link اگر ماهیتی به نام رنگ آبی تعریف شده است که نشانگر شکل ظاهری است. در واقع نام Modifier توصیف کننده ظاهر (مثلا چه سایزی دارد، چه تم رنگی دارد؟ با )، حالت (این input با بقیه چه تفاوتی از نظر حالت focused یا disabled) و رفتار آن قطعه کد است.

روش BEM
.card {}

.card__background {}
.card__avatar {}
.card__title {}
.card__description {}
.card__footer {}

.link {}

.link--blue {}
.link--light {}

.card__avatar--circle {}
.card__avatar--rounded {}

انواع Modifier

💡 Boolean: در این حالت بودن یا نبودن Modifier مهم است نه مقدار و حالت آن. برای مثال حالت disabled. در مثال زیر search-form__button_disabled نشان‌دهنده disabled بودن ورودی است و search-form_focused نشان‌دهنده focused بودن ورودی است این دو modifier از نوع boolean هستند و برای یک Element یا Block یا وجود دارند یا ندارند.

<!-- The `search-form` block has the `focused` Boolean modifier -->
<form class="search-form search-form_focused">
    <input class="search-form__input">

    <!-- The `button` element has the `disabled` Boolean modifier -->
    <button class="search-form__button search-form__button_disabled">Search</button>
</form>

💡 Key-Value: این حالت زمانی که مقدار Modifier مهم است، استفاده می‌شود. برای مثال یک منو با تم رنگی تاریک یا روشن. برای این حالت برای modifier مقادیر متفاوتی وجود دارد مانند search-form–theme-light برای تم روشن و search-form–theme-dark برای تم تیره.

<!-- The `search-form` block has the `theme` modifier with the value `islands` -->
<form class="search-form search-form--theme-light">
    <input class="search-form__input">

    <!-- The `button` element has the `size` modifier with the value `m` -->
    <button class="search-form__button search-form__button--size-m">Search</button>
</form>

توجه کنید که برای یک کد نمی‌توان دو modifier با مقادیر مختلف را به‌صورت همزمان استفاده کرد. کد زیر نادرست است.

<!-- You can't use two identical modifiers with different values simultaneously -->
<form class="search-form
             search-form--theme-light
             search-form--theme-dark">
    <input class="search-form__input">
    <button class="search-form__button
                   search-form__button--size-s
                   search-form__button--size-m">
        Search
    </button>
</form>

نکات مربوط به Modifier

یک Modifier نمی‌تواند به صورت تنها بکاربرده شود: از دیدگاه روش نامگذاری BEM یک Modifier به صورت تنها نمی‌تواند برای کلاس یک نگ استفاده شود زیرا کار Modifier این است که حالت یا مقدار یا ظاهر یک Element یا Block را تغییر دهد. بنابراین حتما برای هر تگی که در کلاس خود از Modifier استفاده می‌کند، کلاسی مربوط به Element یا Block نیز وجود دارد. کد زیر طبق این دیدگاه درست است:

<!--
    Correct. The `search-form` block has the `theme` modifier with
    the value `islands`
-->
<form class="search-form search-form--theme-dark">
    <input class="search-form__input">

    <button class="search-form__button">Search</button>
</form>

اما کد زیر اشتباه است:

<!-- Incorrect. The modified class `search-form` is missing -->
<form class="search-form--theme-dark">
    <input class="search-form__input">

    <button class="search-form__button">Search</button>
</form>

تکنیک MIX برای ترکیب ماهیت‌های مختلف در یک کد: فرض کنید بلاکی به نام header دارید که این بلاک شامل یک بلاک جستجو است. از یک دید که نگاه کنیم جستجو می‌تواند به عنوان یک Elementای برای هدر باشد و وابسته به هدر باشد. از دیدی دیگر جستجو خود به تنهایی می‌تواند مانند یک بلاک مستقل در نظر گرفته شود که استایل‌های مربوط به خود را داشته باشد. بنابراین در این حالت می‌توان از تکنیک Mixing استفاده کرد.

<!-- `header` block -->
<div class="header">
    <!--
        The `search-form` block is mixed with the `search-form` element
        from the `header` block
    -->
    <div class="search-form header__search-form"></div>
</div>

در مثال بالا رفتار و استایل بلاک search-form و Element (search-from) برای header ترکیب شده است. این تکنیک به شما این اجازه را می‌دهد که موقعیت المنت header__search-form تنظیم شود در حالی که بلاک search-form خودش می‌تواند به صورت مستقل عمل کند.

نکات کلی درباره روش نامگذاری BEM

توجه کنید که نام بلاک به صورت منحصربه‌فرد باید انتخاب شود.

کلاس‌های Element، Block و Modifier باید نامشان به صورت حروف کوچک نوشته شود.

نام Element توسط دو underline به‌صورت ( __ ) از نام بلاک جدا می‌شود.

نام Modifier توسط دو خط تیره ( -- ) از نام Element یا بلاک مربوط جدا می‌شود.

مقدار یک Modifier توسط یک خط تیره (-) از نام آن جدا می‌شود.

روش BEM به‌صورت عملی

از آنجایی که تا الان ما به‌طور کلی با روش نامگذاری BEM آشنا شدیم باید تمرین بیش‌تری انجام دهید تا برای شما تثبیت بشود در زیر چند نمونه تمرین عملی برای شما قرار دادیم.

اولین تمرین: ایجاد accordion

دومین تمرین: ایجاد منو

سومین تمرین: کار با فرم‌ها

👍 مزایای روش BEM

از نظر کارایی: یکی از روش‌های استایل‌دهی به کلاس‌ها در CSS روش انتخابگر یا selector تودرتویی بود. این موضوع باعث کاهش سرعت بارگذاری صفحه می‌شد اما با روش BEM تمام انتخابگرهای CSS به صورت flat خواهند بود و باعث افزایش سرعت بارگذاری صفحه می‌شود.

از نظر قابلیت استفاده مجدد: با این روش نامگذاری شما نام هر کامپوننت را بر اساس هدف آن نامگذاری می‌کنید و بدون اینکه در پروژه جدید بخواهید استایلی را تغییر بدهید، می‌توانید جابجا کنید.

افزایش خوانایی کدهای CSS: با این روش دیگران به راحتی با دیدن نام کلاس‌ها متوجه می‌شوند که برای چه هدفی بوده است. این روش به خصوص در پروژه‌های بزرگ‌تر نمود بیش‌تری پیدا می‌کند و واقعا از کارایی این تکنیک ساده شگفت‌زده می‌شوید.

قابلیت نگهداری پروژه

افزایش مقیاس‌پذیری

ساختار فایل‌ها از دیدگاه روش BEM

متدولوژی BEM علاوه بر این‌که برای نامگذاری کلاس‌ها استفاده می‌شود، برای ساختار فایل‌های یک پروژه نیز می‌توان استفاده کرد. برای پیاده‌سازی این روش باید به صورت جداگانه برای هر کدام از بلاک‌ها یک دایرکتوری یا فولدر همنام با بلاک ایجاد کنید و فایل‌های مربوط به آن بلاک را در آن قرار دهید. برای مثال برای بلاک menu یک پوشه به نام menu بایستی ایجاد کنید. برای پیاده‌سازی بلاک‌های مستقل شما از کدهای CSS و JavaScript استفاده می‌کنید. بنابراین در پوشه menu به صورت مثال دو فایل menu.css و menu.js ایجاد کنید. سپس برای Element یا Modifierهای این بلاک نیز فولدرهای جداگانه در همان پوشه بلاک اصلی ایجاد کنید. و cssهای مربوط به آن‌ها را در آن پوشه‌ها قرار دهید.

نام هر فولدر مربوط به Element با __ شروع می‌شود و نام مربوط به فولدر Modifier با شروع می‌شود. برای مثال در داخل پوشه menu دو پوشه به نام‌های menu/__logo و menu/__item به عنوان Element ایجاد می‌شود و برای Modifier دو فولدر به صورت menu/__item /–fixed یا menu/__item/–theme-dark ایجاد می‌شود. کدهای CSS و JS هر کدام از Element یا Modifier ها نیز به صورت menu/__item/menu__item.css (برای Element) و menu/__item/–theme-dark/menu__item–theme-dark.css ایجاد کنید.

search-form/                           # Directory of the search-form

    __input/                           # Subdirectory of the search-form__input
        search-form__input.css         # CSS implementation of the
                                       # search-form__input element
        search-form__input.js          # JavaScript implementation of the
                                       # search-form__input element

    __button/                          # Subdirectory of the search-form__button
                                       # element
        search-form__button.css
        search-form__button.js

    --theme/                            # Subdirectory of the search-form_theme
                                       # modifier
        search-form--theme-islands.css  # CSS implementation of the search-form block
                                       # that has the theme modifier with the value
                                       # islands
        search-form--theme-lite.css     # CSS implementation of the search-form block
                                       # that has the theme modifier with the value
                                       # lite

    search-form.css                    # CSS implementation of the search-form block
    search-form.js                     # JavaScript implementation of the
                                       # search-form block

BEM Tree

با استفاده از درخت BEM می‌توان نمایشی از ساختار یک صفحه وب را با توجه به تودرتویی، ترتیب، نام و حالت Blockها، Elementها و Modifierها داشت. در پروژه‌های واقعی BEM Tree می‌تواند در هر فرمتی قرار بگیرد. حال قطعه کد html زیر را در نظر بگیرید.

<header class="header">
    <img class="logo">
    <form class="search-form">
        <input class="input">
        <button class="button"></button>
    </form>
    <ul class="lang-switcher">
        <li class="lang-switcher__item">
            <a class="lang-switcher__link" href="url">en</a>
        </li>
        <li class="lang-switcher__item">
            <a class="lang-switcher__link" href="url">ru</a>
        </li>
    </ul>
</header>

درخت BEM مرتبط با مثال بالا به صورت زیر است.

header
    logo
    search-form
        input
        button
    lang-switcher
        lang-switcher__item
            lang-switcher__link
        lang-switcher__item
            lang-switcher__link

درخت BEM در قالب فرمت XMl و Json نیز به صورت زیر می‌تواند قرار بگیرد.

<block:header>
    <block:logo/>
    <block:search-form>
        <block:input/>
        <block:button/>
    </block:search-form>
    <block:lang-switcher>
        <elem:item>
            <elem:link/>
        </elem:item>
        <elem:item>
            <elem:link/>
        </elem:item>
    </block:lang-switcher>
</block:header>
{
    block: 'header',
    content : [
        { block : 'logo' },
        {
            block : 'search-form',
            content : [
                { block : 'input' },
                { block : 'button' }
            ]
        },
        {
            block : 'lang-switcher',
            content : [
                {
                    elem : 'item',
                    content : [
                        { elem : 'link' }
                    ]
                },
                {
                    elem : 'item',
                    content : [
                        { elem : 'link' }
                    ]
                }
            ]
        }
    ]
}

سخن پایانی

در اولین دوره آموزشی روش BEM سعی کردیم به صورت کلی به سوال شما که BEM چیست و چرا باید از آن استفاده کنیم؟ جواب دهیم. این تکنیک با این‌که بسیار ساده است اما برای تبحر در آن باید از همین الان در هر کجای پروژه خود هستید از آن استفاده کنید. اگر به صورت تیمی برنامه‌نویسی می‌کنید به شدت توصیه می‌کنم از این روش استفاده کنید تا دچار گیجی و سردرگمی نشوید. این تکنیک امتیاز مثبتی برای رزومه شما نیز خواهد بود.

آموزش‌های مرتبط با این مطلب

منبع: بامادون

کپی و نشر مطلب با ذکر منبع و نام نویسنده بلامانع است.

زهرا فتوحی نیا وب‌سایت
کارشناسی ارشد هوش مصنوعی و عاشق برنامه‌نویسی و طراحی وب

نظر دادن یک نوع هنر است! نظر شما چیست؟