现代 CSS

关于BEM中常见的十个问题以及如何避免

特别声明:如果您喜欢小站的内容,可以点击申请会员进行全站阅读。如果您对付费阅读有任何建议或想法,欢迎发送邮件至: airenliao@gmail.com!或添加QQ:874472854(^_^)

不管你是刚刚才接触BEM还是已经是一名老手,对于它的思想你是不是都十分的赞美?如果你还没有接触过BEM,在阅读这篇文章之前我建议你先到BEM官方网站进行了解,因为我将对其进行分类表述我对这种CSS理念的观点。

本文旨在对那些BEM的爱好者以及想要更好地使用它或者是想要加深对它了解的人有所帮助。

现在我是不抱有任何幻想,关于说BEM是一个很好的CSS命名规范。因为它绝对不是!我曾经很长一段时间因为它丑陋的语法规范而放弃了它。作为设计者的我不希望我的标记中出现丑陋的双下划线以及连字符。

但是作为构建一个逻辑性的、模块化的用户界面,它是比较务实的,这胜过了我的右大脑的抱怨 - “但是它不够漂亮!!!”。我当然不推荐在起居室这种小范围内使用这种方式,但是当你需要一件救生衣(当你遨游在CSS大海之中时),我将会选择这种函数形式。不管怎么说,足够使用。这里有10个关于BEM的问题以及我处理这些问题的小技巧。

关于后代(不包含子选择器)选择器如何使用?

这里需要澄清一下,当你的元素嵌套两级以及大于两级的时候就需要使用子孙选择器。这简直就是我生命中的克星,我敢肯定他们的滥用就是有人对BEM产生反感的原因之一,如下所示:

<div class="c-card">

    <div class="c-card__header">
        <!-- Here comes the grandchild… -->
        <h2 class="c-card__header__title">Title text here</h2>
    </div>

    <div class="c-card__body">

        <img class="c-card__body__img" src="some-img.png" alt="description">
        <p class="c-card__body__text">Lorem ipsum dolor sit amet, consectetur</p>
        <p class="c-card__body__text">Adipiscing elit.
            <a href="/somelink.html" class="c-card__body__text__link">Pellentesque amet</a>
        </p>

    </div>
</div>

正如你可以想象的,这样的命名很快就会失控,越嵌套一个组件,类名就会变得更加狰狞以及不可读。 我使用了一个短块名称c-card并且使用了短元素名,如body , textlink,但你能想象当你的初始块名称被命名为类似c-drop-down-menu时,就会出现失控现象。

我相信双下划线模式在选择器名称下应该只能出现一次。BEM代表Block__Element--Modifier,并不是 Block__Element__Element--Modifier 。 因此,需要避免多个元素级别命名。这时如果你存在多层嵌套,你可能就需要重新审视你的组件结构了。

BEM命名并不严格绑定到DOM上,所以后代元素有多少层嵌套并没有关系。命名规范是帮助你识别顶层块组件之间的关系,如这里的c-card

这里我就会这样子处理相同的卡片组件,如下:

<div class="c-card">
    <div class="c-card__header">
        <h2 class="c-card__title">Title text here</h2>
    </div>

    <div class="c-card__body">

        <img class="c-card__img" src="some-img.png" alt="description">
        <p class="c-card__text">Lorem ipsum dolor sit amet, consectetur</p>
        <p class="c-card__text">Adipiscing elit.
            <a href="/somelink.html" class="c-card__link">Pellentesque amet</a>
        </p>

    </div>
</div>

这意味着所有的后代元素仅仅受到card块的影响。因此我们可以将文本和图像移动到c-card__header,甚至引入新的c-card__footer元素而不会破坏语义结构。

如何使用命名空间?

现在你可能已经注意到我在上述代码中使用 c- ,这表示“组件”,并且形成了我对BEM类命名规范。这个想法来自于Harry Robert的命名技巧,提高了代码的可读性。

我发现使用这些命名空间使我的代码更具有可读性,即使我不使用BEM,这也是一个重要的分辨点。

你也可以采用其余的命名空间,如qa-表示质量保证,ss-表示服务器端挂钩等。但是上述列表是一个很好的参照,如果你感觉还不错可以将其介绍给别人。

你会发现这种风格在未来命名空间问题上会是一个很好的例子。

我该如何命名包裹容器?

一些部件需要一个决定子元素布局的父容器。在这些情况下,我总是尝试将布局抽象为一个布局模块,如l-grid,并将其组件插入其中,如l-grid__item

在我们的卡片示例中,如果我们想要生成一个具有四个c-card的列表,我将会使用下面所示的标记:

<ul class="l-grid">
    <li class="l-grid__item">
        <div class="c-card">
            <div class="c-card__header">
                […]
            </div>
            <div class="c-card__body">
                […]
            </div>
        </div>
    </li>
    <li class="l-grid__item">
        <div class="c-card">
            <div class="c-card__header">
                […]
            </div>
            <div class="c-card__body">
                […]
            </div>
        </div>
    </li>
    <li class="l-grid__item">
        <div class="c-card">
            <div class="c-card__header">
                […]
            </div>
            <div class="c-card__body">
                […]
            </div>
        </div>
    </li>
    <li class="l-grid__item">
        <div class="c-card">
            <div class="c-card__header">
                […]
            </div>
            <div class="c-card__body">
                […]
            </div>
        </div>
    </li>
</ul>

现在你应该对布局模块和组件的命名空间的组合使用有了一个坚实的想法。

不要害怕使用一些额外的标记需要记忆而头疼。没有人会拍拍你的肩膀让你移除<div>标签的!

在一些情况下,这是不可能的。如,你的网格不能实现你想要的结果,或者说你想要一些语义化对父元素进行命名,这时你要怎么办?对于我,根据不同场景,我倾向于选择使用container或者list。返回我们的卡片示例,我可能会使用<div class="l-cards-container">[…]</div>或者<ul class="l-cards-list">[…]</ul>。关键点在于和你的命名约定相一致。

跨组件...组件?

所面临的另外一个问题是,组件的风格或者定位受其父容器的影响。关于这个问题Simurai对各种解决方案做了详细的说明。这里我就告诉大家我认为最具有扩展性的解决方法。

假设我们想要我们的示例中card__body中插入一个c-button。这个按钮已经有自己的组件,并且标记如下:

<button class="c-button c-button--primary">Click me!</button>

如果与其它常规按钮组件没有样式区别,这里就不存在问题,我们可以如下所示直接使用:

<div class="c-card">
    <div class="c-card__header">
        <h2 class="c-card__title">Title text here</h3>
    </div>

    <div class="c-card__body">

        <img class="c-card__img" src="some-img.png">
        <p class="c-card__text">Lorem ipsum dolor sit amet, consectetur</p>
        <p class="c-card__text">Adipiscing elit. Pellentesque.</p>

        <!-- Our nested button component -->
        <button class="c-button c-button--primary">Click me!</button>

    </div>
</div>

但是,当其样式不同时如,我们想要其更小一点,或者完全是圆角,但是这些情况仅仅出现在c-card组件的一部分。这时应该怎么办?

之前我说过,我找到一个跨组件最强大的解决方案:

<div class="c-card">
    <div class="c-card__header">
        <h2 class="c-card__title">Title text here</h3>
    </div>

    <div class="c-card__body">

        <img class="c-card__img" src="some-img.png">
        <p class="c-card__text">Lorem ipsum dolor sit amet, consectetur</p>
        <p class="c-card__text">Adipiscing elit. Pellentesque.</p>

        <!-- My *old* cross-component approach -->
        <button class="c-button c-card__c-button">Click me!</button>

    </div>
</div>

这就是BEM官方网站上著名的“mix”。但是当我参考了Esteban Lussich的一些意见之后,我却对其改变了看法。

在上面的示例中,c-card__c-button类试图改变c-button类已经存在的一个或者多个属性,但是是否成功取决于它们的源顺序(或者特殊指明)。源代码中,c-card__c-button类只有在c-button类之后才会起作用,但是当你建立更多类似组件时就会失控。(当然你也可以使用!important,但是我并不推荐使用)

一个真正的模块化UI元素应该是完全不知其父容器的 - 无论在哪里使用,效果应该是一致的。在一个组件中添加一个具有特定样式的类,称之为"mix"方法,违反了组件导向设计的开/关原则 - 即各个模块之间应该没有依赖关系。

最好的解决办法就是在这些不同之处使用一个修饰器,因为你可能会发现,你会在其它地方对其复用。

<button class="c-button c-button--rounded c-button--small">Click me!</button>

即使你不会再次使用这些类,在修饰器指定特异性或者源顺序,它们不会被捆绑到父容器上。

当然,另外一个选择就是回到你的设计者岗位,告诉他们按钮的样式就应该和网站上其余按钮保持一致,并完全避免这个问题......但是,这又是另一天。

修饰器或新建组件?

最大的问题之一在于决定组件的结束与另一个组件的开始。在c-card示例中,你可能随后就会创建一个c-panel组件,它们之间有很大的相似之处,但是又有细微的差别。

但是,你需要决定是否应该存在有两个组件 - c-panelc-card,或者在c-card上应用一个简单的修饰器对样式进行修改。

这很容易造成过渡模块化,使其一切变为组件。我建议使用修饰器,但是如果你发现你的特定组件的CSS文件越来越难以管理,这时你就可以终止对修饰器的使用。一个很好的指标就是,当你发现你不得不重置你所有的块CSS以对你的修饰器进行样式修饰时 - 这对我来说就可以生成一个新组件。

最好的办法就是,如果你和其余的开发者或者设计师一起工作,征求他们的意见并进行讨论。我知道这有点虎头蛇尾,但是对于一个大型应用程序,明白模块化的复用性以及什么时候需要组件是至关重要的。

如何处理状态?

这是一个普遍的问题,特别是当你对组件的活动或开启状态进行样式修饰时。比方说我们的卡片需要有一个活跃状态;所以当你点击时,就会显现一个漂亮的边框样式。这里你将如何对类进行命名呢?

这里我感觉你会有两个选择: 一个是独立的状态钩,或者是在组件中添加一个BEM中的修饰符:

<!-- standalone state hook -->
<div class="c-card is-active">
    […]
</div>

<!-- or BEM modifier -->
<div class="c-card c-card--is-active">
    […]
</div>

这里我喜欢保持一致性,使用BEM类似命名。一个独立类的好处就是,它可以很容易的使用JavaScript在所有组件上应用通用状态的挂钩。当你不得不用脚本应用特定的基于修饰器的状态类时,这就会变得有些困难。当然,这是完全有可能的,但意味着使用了大量的JavaScript脚本。

坚持使用一套标准挂钩是有道理的。Chris Pearce 有一个已经编译好的目录,我建议阅读一下。

什么时候可以不用在一个元素上添加类?

我可以理解人们对于构建一个复杂的UI界面所面临的大量的类的苦楚,特别是他们不向每个类上添加一个标签。

通常情况下,我会在有特殊样式的组件上下文中添加类。我经常丢弃p标签级的,除非是组件的特殊需要。

当然这可能意味着你的标记中包含大量的类,但是你的组件可以独立化并且可以在任何地方无副作用使用。

由于CSS的全局特性,应用样式所控制的远远大于组件所渲染的样式。这种最初的心理不适性导致是值得的,相对于模块化系统所带来的好处。

如何嵌套组件?

假设我们想在我们的c-card组件中展示一个选择清单,这里有一个错误的示范:

<div class="c-card">
    <div class="c-card__header">
        <h2 class="c-card__title">Title text here</h3>
    </div>

    <div class="c-card__body">

        <p>I would like to buy:</p>

        <!-- Uh oh! A nested component -->
        <ul class="c-card__checklist">
            <li class="c-card__checklist__item">
                <input id="option_1" type="checkbox" name="checkbox" class="c-card__checklist__input">
                <label for="option_1" class="c-card__checklist__label">Apples</label>
            </li>
            <li class="c-card__checklist__item">
                <input id="option_2" type="checkbox" name="checkbox" class="c-card__checklist__input">
                <label for="option_2" class="c-card__checklist__label">Pears</label>
            </li>
        </ul>

    </div>
    <!-- .c-card__body -->
</div>
<!-- .c-card -->

这里存在几个问题。其中一个就是我们在section 1部分使用的祖父母选择器。另外一个问题就是所有应用c-card__checklist__item类的均被限定使用,不可复用。

这里我更倾向于将清单本身变为一个布局模块并且清单子项目将会成为它的组件,使他们能够在其他地方独立使用。这里使用l-命名空间:

<div class="c-card">
    <div class="c-card__header">
        <h2 class="c-card__title">Title text here</h3>
    </div>

    <div class="c-card__body"><div class="c-card__body">

        <p>I would like to buy:</p>

        <!-- Much nicer - a layout module -->
        <ul class="l-list">
            <li class="l-list__item">

                <!-- A reusable nested component -->
                <div class="c-checkbox">
                    <input id="option_1" type="checkbox" name="checkbox" class="c-checkbox__input">
                    <label for="option_1" class="c-checkbox__label">Apples</label>
                </div>

            </li>
            <li class="l-list__item">

                <div class="c-checkbox">
                    <input id="option_2" type="checkbox" name="checkbox" class="c-checkbox__input">
                    <label for="option_2" class="c-checkbox__label">Pears</label>
                </div>

            </li>
        </ul>
        <!-- .l-list -->

    </div>
    <!-- .c-card__body -->
</div>
<!-- .c-card -->

这样你就不用重复样式,也意味着可以在应用的其它地方应用l-list类和c-checkbox类。这意味着多一点标记,但是方便了阅读,封装以及可重用性。你可能已经注意到了这都是共同的话题!

组件是不是结束于大量类之中?

一些人认为,在一个元素上应用大量的类是很不好的,而且--modifiers会积少成多。就我个人而言,我不感觉这有问题,因为这使代码更具有可读性,可以很快明白其语义。

这里有一个应用了四个类的按钮:

<button class="c-button c-button--primary c-button--huge  is-active">Click me!</button>

我知道这个语法不是最可视化的,但是却是最明确的。

但是,如果这对于你来说是一个难题,你可以看看Sergey Zarouski所提出的扩展技术。从本质上说,我们将会在样式表中使用.className [class^="className"],[class*=" className"] 效仿vanilla CSS的扩展功能。如果你对这种语法比较眼熟,这是因为它和Icomoon处理图标选择是十分相似的。

使用这种方式,你的标记可能会如下所示:

<button class="c-button--primary-huge  is-active">Click me!</button>

我不知道使用class=^class*=选择器所带来的性能损失是否比单独使用大规模的类大得多,但是这在理论上是一个很好的替代。我对于多类的选择还是可以接受的,但是我感觉应该为那些喜欢选择的人提及一下。

是否可使组件具有响应化?

这个问题是Arie Thulank向我提出的,也是一个我挣扎了好久想出一个100%具体解决方案的问题。

一个例子就是在给定断点一组选项卡的转换,或屏幕导航在给定断点处切换为下拉菜单样式。

从本质上讲,一个组件在媒体查询下具有两个不同的表现形式。

关于这两个具体例子,我的倾向是建立一个c-navigation组件,因为在两个断点处它的行为是最基础的。这让我产生了一个思考,在大屏幕上如何将一个图像列表转换为幻灯片形式?这对于我来说可能是一个边缘情况,并且只要是具有可行性文档和评论,我认为这是完全合理的,为这种类型的用户界面创建这种具有明确的命名(如c-image-list-to-carousel)一次性独立组件。

Harry Roberts曾写过响应后缀 ,这也是一种处理方式。他的做法是为了更多的布局和样式的变化,而不是整个组件的变化,但我不明白为什么这种技术不能应用在这里。所以,基本上你会发现作者的类是这样的:

<ul class="c-image-list@small-screen c-carousel@large-screen">

这样,对于不同的屏幕尺寸就会保留各自的媒体查询。 提示:你需要在你的CSS中使用反斜杠使用@,如下所示:

.c-image-list\@small-screen {
    /* styles here */
}

我没有太多的示例创建这些类型的组件,但是你必须这样子做,这感觉将会是一种十分有好的开发商方式。 下面接管你代码的人应该也能够很容易地理解你的意图。 我不主张像small-screenlarge-screen的命名 - 这里纯粹是为了可读性。

总结

BEM绝对是我在创建一个具有模块化,组件驱动方式的应用中的一个救星。现在我已经使用了近三年,上述问题是也是我遇到的为数不多的绊脚石。 我希望这篇文章对你的学习有所帮助,另外如果BEM不过时,我强烈建议你这样做。

本文根据@David Berner的《Battling BEM (Extended Edition): 10 Common Problems And How To Avoid Them》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:https://www.smashingmagazine.com/2016/06/battling-bem-extended-edition-common-problems-and-how-to-avoid-them/

静子

在校学生,本科计算机专业。一个积极进取、爱笑的女生,热爱前端,喜欢与人交流分享。想要通过自己的努力做到心中的那个自己。微博:@静-如秋叶

如需转载,烦请注明出处:http://www.w3cplus.com/css/battling-bem-extended-edition-common-problems-and-how-to-avoid-them.htmlnike air max 90 fashion

返回顶部