SMACSS 和 BEM:如何在模块中定位模块?
Posted
技术标签:
【中文标题】SMACSS 和 BEM:如何在模块中定位模块?【英文标题】:SMACSS and BEM: How to position Module inside of a Module? 【发布时间】:2014-09-03 16:25:46 【问题描述】:注意:我使用 Module 这个词,在 BEM 中称为 Block。还使用修改后的 BEM 命名约定
BLOCK__ELEMENT--MODIFIER
,请在您的回答中也使用它。
假设我有一个看起来像这样的.btn
模块:
.btn
background: red;
text-align: center;
font-family: Arial;
i
width:15px;
height:15px;
我需要创建一个 .popup-dialog
模块,其中包含一个 .btn
:
.popup-dialog
...
.btn
position: absolute;
top: 10px;
right: 10px;
在 SMACSS 和 BEM 中,您应该如何处理在模块内部定位模块?
在您的回答中,请找出正确的解决方案,并分析以下方法:(请注意,以下所有示例均基于或修改上述 CSS)
方法 1
[ 覆盖.popup-dialog
内部的原始.btn
类]
CSS:
.popup-dialog
...
.btn // override the original .btn class
position: absolute;
top: 10px;
right: 10px;
标记:
<div class="popup-dialog">
...
<button class="btn"><i class="close-ico"></i> close</btn>
</div>
方法2
[在.popup-dialog
中添加一个子类]
CSS:
.popup-dialog
...
.popup-dialog__btn
position: absolute;
top: 10px;
right: 10px;
标记:
<div class="popup-dialog">
...
<button class="btn popup-dialog__btn"><i class="close-ico"></i> close</btn>
</div>
方法 3
[带有修饰符的子类.btn
]
CSS:
.btn--dialog-close
position: absolute;
top: 10px;
right: 10px;
标记:
<div class="popup-dialog">
...
<button class="btn btn--dialog-close"><i class="close-ico"></i> close</btn>
</div>
方法 4
[带有布局类的子类.btn
]
CSS:
.l-dialog-btn // layout
position: absolute;
top: 10px;
right: 10px;
标记:
<div class="popup-dialog">
...
<button class="btn l-dialog-btn"><i class="close-ico"></i> close</btn>
</div>
【问题讨论】:
【参考方案1】:BEM
如果您不修改 .btn
内的 .popup-dialog
第一种方法是最好的。
如果您需要对.btn
进行一些修改,根据 BEM 方法,您必须使用修改器类,例如 .btn_size_s
如果你有修改与.btn
没有直接联系,并且你怀疑将来是否可以重复使用,例如你必须在弹出窗口中将.btn
浮动到右边,你可以使用像.popup-dialog__btn
这样的mixin
SMACSS
再次,如果您只需要将一个块放在其他块内 - 请遵循第一种方法。
如果您需要任何修改,有两种方法:子类和后代选择器。
如果您的修改可能在未来被重用 - 使用子类,例如 .btn-size-s
。
如果修改与某些特定模块紧密相关 - 最好使用后代选择器。
更新:
补充几点来明确我的答案:
首先,方法 4 是不可接受的 - 将模块与布局混合使用是不好的做法,因为 Layout 类负责网格和页面部分的几何形状,模块独立于 Layout 并且应该对它放置的部分一无所知。
现在让我评论一下其他方法以及它的最佳用法:
方法 1 - 考虑以下情况:您有 Popup
模块和“关闭”Button
模块。 Popup
对 Button
什么都不做,没有修改,没有浮动或边距,它只是它的孩子。在这种情况下,这种方法是最好的。
方法 2 - 另一种情况:Popup
有子 Button
,但我们必须添加额外的上边距并将 Button
浮动到右侧。如您所见,此修改与Popup
紧密结合,对其他模块无用。这种“本地”修改是这种方法的最佳使用。在 BEM 中,这种方法也称为 mix
方法 3 - 最后一个案例:Popup
带有子按钮,但我们需要更大的Button
,这样修改后的按钮可以重复使用,并且可能对其他模块和页面有用。
在 BEM 中,它被称为 modifier
为了标记 A2 和 A3 之间的主要区别,让我们从 Popup
中删除 Button
并将其放在其他位置。 A3 仍会影响Button
,A2 不会。
所以要将模块作为child 使用,您可以使用A1 或A2,如果模块修改独立于上下文,则应使用A3。
【讨论】:
谢谢,但我不太明白你的回答。你说要使用修饰符,但你没有说把它放在哪里(在 .popup-dialog 内或作为 .btn 模块的一部分)。使用我在问题中使用的遵循 BLOCK__ELEMENT--MODIFIER 约定的命名样式来命名修饰符.btn_size_s
也没有任何意义。
修饰符总是块的一部分,所以btn的修饰符应该在btn中。您不能使用弹出修饰符来更改 btn。其次,我以原始 bem 命名约定为例来定义修饰符。
size_s
是修饰符吗?如果是这样,不应该是.btn--size_s
吗?
是的,它的修饰符,根据bem命名约定,应该用lodash定义为分隔符name_value
好的,谢谢,令人困惑的答案,但 +1 有点帮助。我实际上使用的是修改后的 BEM 命名约定。【参考方案2】:
我自己在最近的一个大型项目中一直在努力解决这个问题,我很高兴你能在 SO 上引起人们的注意。
恐怕这个问题没有一个“正确”的解决方案,而且它会有点基于意见。但是,我会尽量保持客观,并就您的四种方法对我的团队有效和无效的方法提供一些见解。
我还要假设以下内容:
您熟悉 SMACCS 方法(您阅读了这本书并在至少一个项目中实施了它)。 您只对 CSS 类名使用(修改后的)BEM 命名约定,而不是 BEM 方法开发堆栈的其余部分。方法 1
这显然是最糟糕的方法,并且有几个缺陷:
它通过使用基于上下文的选择器在.popup-dialog
和.btn
之间创建紧密耦合。
您将来可能会遇到特异性问题,假设您将来会在.popup-dialog
中添加额外的.btn
元素。
如果您需要使用未更改的类名,我建议至少通过使用直接子选择器来降低适用性的深度。
CSS:
.popup-dialog ...
.popup-dialog > .btn
position: absolute;
top: 10px;
right: 10px;
方法 2
这实际上与我们的解决方案非常接近。我们在项目中设置了以下规则,它被证明是稳健的:“一个模块不能有外部布局,但可以布局它的子模块”。这在很大程度上受到了 SUITCSS 框架中的@necolas 约定的启发。注意:我们使用的是概念,而不是语法。
https://github.com/suitcss/suit/blob/master/doc/components.md#styling-dependencies
我们在这里选择了第二个选项,并将子模块包装在额外的容器元素中。是的,它创建了更多标记,但好处是我们仍然可以在使用我们无法控制 html(来自其他网站的嵌入等)的 3rd 方内容时应用布局。
CSS:
.popup-dialog ...
.popup-dialog__wrap-btn
position: absolute;
top: 10px;
right: 10px;
HTML:
<div class="popup-dialog">
...
<div class="popup-dialog__wrap-btn">
<button class="btn"><i class="close-ico"></i> close</button>
</div>
</div>
方法 3
这可能看起来很干净(扩展而不是覆盖),但事实并非如此。它将布局与模块样式混合在一起。如果您有另一个模块需要为关闭按钮设置不同的布局,那么在 .btn--dialog-close
上设置布局样式将不再有用。
方法 4
这与方法 3 基本相同,但语法不同。布局类必须不知道其布局的内容。我也不热衷于书中建议的l-prefix
语法。根据我的经验,它造成的混乱多于帮助。我们的团队完全放弃了它,我们只是将所有内容都视为模块。但是,如果我需要坚持下去,我会尝试完全从模块中抽象出布局,这样你就有了一些有用且可重用的东西。
CSS:
.l-pane
position: relative;
...
.l-pane__item
position: absolute;
.l-pane__item--top-right
top: 10px;
right: 10px;
.popup-dialog // dialog skin
...
.btn // button skin
...
HTML:
<div class="popup-dialog l-pane">
<div class="l-pane__item l-pane__item--top-right">
<button class="btn"><i class="close-ico"></i> close</button>
</div>
</div>
我不会因为这种方法而责怪任何人,但根据我的经验,并非所有布局都可以以合理的方式抽象,并且必须单独设置。这也使其他开发人员更难理解。我会从这个假设中排除网格布局,它们很容易掌握并且非常有用。
你有它。出于上述原因,我建议尝试修改后的方法 2。
希望能提供帮助。
【讨论】:
感谢您的精彩而清晰的回答。我很好奇,你不喜欢 SUITECSS 语法的什么地方? 实际上我引用了 SMACCS 提出的 layout-_ 或 l- 前缀。正如本书所做的那样,我认为布局部分太模糊了,因此造成了混乱。这主要基于两个用例展示。重复页面元素(l-header、l-footer、l-sidebar)和 true 布局类(l-grid),它们是不同的东西,应该这样对待。 SUITCSS 及其语法对我来说似乎很成熟。我们只是无法改变我们当前的构建过程。我们正在使用 SASS,但 SUIT 需要不同的预处理器(据我所知,基于 node.js)。也就是说,单独采用语法是可能的,但目前不在我们的列表中。 我不得不感谢你这个惊人的答案。我一直在思考这个问题一天,你已经完美地表达了它。继续加油!【参考方案3】:还有另一个可能适合您需要的约定:https://ncss.io
目标:
一种可预测的 CSS 语法,提供有关 HTML 模板的语义信息。
哪些标签、组件和部分受到影响 一类与另一类的关系是什么示例:
<div class="modal-dialog">
...
<div class="wrapper-button-dialog">
<button class="button-dialog">close</button>
</div>
</div>
【讨论】:
【参考方案4】:首先,我想澄清一下,根据 BEM 中的定义,按钮是 ELEMENT 而不是 BLOCK。因此,如果您要使用 BEM 方法来解决这个问题,那么这个问题就会变得简单一些。
其次,我同意 mlnmln 的解决方案(方法 2),因为它定义了块内的元素变化,这是块本身所独有的。但是,如果像此按钮这样的元素变体存在于弹出对话框块之外,那么您需要采用方法 3 并应用允许全局使用的命名约定。
【讨论】:
以上是关于SMACSS 和 BEM:如何在模块中定位模块?的主要内容,如果未能解决你的问题,请参考以下文章