欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  web前端

[英] 使用 BEM 来模块化你的 CSS 代码_html/css_WEB-ITnose

程序员文章站 2022-04-12 13:16:54
...

How we use BEM to modularise our CSS

If you’re not familiar with BEM, it’s a naming methodology that provides a rather strict way to arrange CSS classes into independent components. It stands for Block Element Modifier and a common one looks like this:

.block {}
.block__element {}
.block--modifier {}
.block__element--modifier {}

The principles are simple — a Block represents an object (a person, a login form, a menu) , an Element is a component within the block that performs a particular function (a hand, a login button, a menu item) and a Modifier is how we represent the variations of a block or an element (a female person, a condensed login form with hidden labels, a menu modified to look differently in the context of a footer) .

There are plenty of resources online that explain the methodology in more detail ( https://css-tricks.com/bem-101/ , http://getbem.com/naming/ ). In this article we’ll be focusing on describing the challenges we had implementing it in our projects.

Before we decided to port our styles and use the Block-Element-Modifier methodology, it all started with a bit of research. Looking around, we found tens of articles and resources, mixins and documentation that seemed to answer all possible questions. It was pretty clear we found our new BFF.

But once you go deeply into building something to scale, it gets pretty confusing. And the more you try to battle it and make it work, the worse it gets — unless you don’t see it and treat it as your friend. Our story starts a couple of months ago when we met BEM. We went out, introduced ourselves, then lured it into working with us on a little corner side project. We connected well, so it was decided — we like it and we want to take this friendship to another level.

The process we followed was relatively simple and somehow natural. We’ve experimented with naming conventions and manually created stylesheet classes . After deciding on a s et of guidelines , we’ve created basic mixins to generate the class names , without having to pass the block name every time we add a new modifier or an element.

So our journey started with something like this:

Then using a set of custom mixins we’ve converted the above to:

And then slowly, when more and more edge cases started to emerge, we’ve improved the mixins without having to change any of the existing code. Pretty neat.

So if for example we want to define the list element in the context of the full-size modifier, we would do this:

How we do BEM in our backyard

We didn’t jump in and convert everything to follow this methodology. We took it gradually and started with scattered pieces until we saw a pattern.

Like any relationship, there must be understanding from both sides in order to make things work. Some of the guidelines we follow are part of the methodology and we don’t question, and some we’ve added on our own down the road.

The ground rule for us is that we never nest blocks inside blocks and elements inside elements . This is the one rule that we set to never to break.

The very deepest level of nesting for a single block is:

If there’s need for more nesting, it means there’s too much complexity and the elements should be stripped down into smaller blocks.

Another rule here is in the way we convert elements into blocks. Following rule#1, we split everything into smaller concerns.

So let’s say we have this structure for a correspondence component:

First we create the structure for the higher-level block:

Then we repeat the process for the smaller, inner structures:

If the title becomes more complex, we just extract that into yet another smaller concern:

And then add more complexity — let’s say we want the actions to be displayed on hover:

After everything is done, if we follow the code back to our stylesheet, it’s going to be laid out nicely, like this:

And there’s nothing stopping us to clean some of the semantics that are not necessary. Since our item is clearly part of a list and there is no other item in the context of the correspondence, we can rename it to correspondence-item :

This is another guideline we use — simplify the naming of BEM blocks for nested components if it doesn’t conflict with other blocks.

For example, we don’t do it for the item-title, since we could actually have a correspondence-title on the main block or a title inside the preview. It’s just too generic.

The Mixins

The mixins that we’re using above are part of Paint, our internal styles library.

They can be found here: https://github.com/alphasights/paint/blob/develop/globals/functions/_bem.scss

Paint is available as a bower/NPM package and it’s undergoing a core redesign. The BEM mixins are still usable and maintained regularly.

Why do we need mixins in the first place?

Our aim was to make the CSS class generation system extremely simple, as we knew front-end and back-end developers don’t need to spend much time building stylesheets. So we’ve automated the process as much as possible.

At the moment we’re developing a set of helper components that would do the same thing for the templates — provide a way to define blocks, elements and modifiers, then generate the markup classes automatically, as we do in CSS .

How things work

We have a function _bem-selector-to-string that turns a selector into a string for easy processing. Sass (rails) and LibSass (node) seem to handle selector strings differently. Sometimes the class name dot is added to the string, so we make sure we strip that before any further processing, as a matter of precaution.

The function that we use to check if a selector has a modifier is _bem-selector-has-modifier . It returns true if the modifier separator is found or if the selector contains a pseudo-selector (:hover, :first-child etc.).

The last function fetches the block name from a string that contains a modifier or pseudo-selector. The _bem-get-block-name would return correspondence if correspondence — full-size is passed. We need the block name when we work with elements inside modifiers, otherwise we would have a hard time generating the proper class names.

The bem-block mixin generates a basic class with the block name and the passed properties.

The bem-modifier mixin creates a .block — modifier class name.

The bem-element mixin does a bit more. It checks if the parent selector is a modifier (or contains a pseudo-selector). If it does, then it generates a nested structure containing the block — modifier with the block__element inside. Otherwise it creates a block__element directly.

For elements and modifiers we currently use an @each $element in $elements but we would optimise this in the next versions to allow sharing the same properties instead of duplicating them on each element.

What we enjoy about BEM

Modularity

It’s very hard to restrain from adding too much logic to a component. With BEM, there’s not much choice, which most of the time is a good thing.

Clarity

When looking at the DOM, you can easily spot where’s the block, what’s the element and if any modifiers are applied. Similarly, when looking at a component stylesheet, you can easily get to where you need to make a change or add more complexity.

Sample blocks structure of an interaction participants component

A block with elements and modifiers

Team Work

Working on the same stylesheet makes it quite hard to avoid conflicts. But when using BEM, everyone can work on their own set of blocks-elements, so there’s really no getting in the way of another.

Principles

When writing CSS, we have a set of principles / rules that we like to follow. BEM enforces some of those by default, which makes writing code even easier.

1. The Separation of Concerns

BEM forces us to separate styles into smaller, maintainable block components that contain elements and modifiers. If the logic gets too complicated, it should be split into smaller concerns. Rule #2.

2. The Single Responsibility Principle

Each of the blocks has a single responsibility and that responsibility is encapsulated in the content of the component.

For the initial example, the correspondence is in charge of setting the grid for the list and preview elements. We don’t share that responsibility with outer concerns or inner concerns.

Following this approach, if the grid changes, we only change it in the context of the correspondence. Every other module would still work.

3. DRY

Every time we stumble upon code duplication, we extract things into placeholders and mixins. If it’s something we DRY within the current scope (things reused in the context of a component) the pattern is to define mixins and classes prefixed with underscore.

Remember not to over engineer your code and separate coincidental occurrence of properties from actual code duplication.

4. The Open/Close Principle

This principle is quite hard to break when working with BEM. It states that everything should be open for extension and closed for modification. We avoid changing properties of a block directly, in the context of other blocks. We instead create modifiers to serve that purpose.

BEM is a powerful methodology, but I think the secret is to make it your own. If something doesn’t work out of the box, find out what does work and try to bend the rules. As long as it brings structure and improves productivity, there’s definitely value in implementing it.

We would love to hear from anyone using BEM to see what challenges you’re faced with.