CSS命名规范—BEM思想
CSS命名规范—BEM思想
BEM是什么:
BEM是一种非常有用、功能强大、简单的CSS命名约定,它使您的前端代码更易于阅读和理解,易于使用,易于扩展,更健壮,更容易协作,更容易控制。
团队开发痛点:
在团队开发中,由于缺乏规范,样式管理一直是开发中的痛点,样式污染,难以定制化,依赖性高,各种问题层出不穷。
css的样式应用是全局性的,没有作用域可言。考虑以下场景
场景一:开发一个弹窗组件,在现有页面中测试都没问题,一段时间后,新需求新页面,该页面一打开这个弹窗组件,页面中样式都变样了,一查问题,原来是弹窗组件和该页面的样式相互覆盖了,接下来就是修改覆盖样式的选择器...又一段时间,又开发新页面,每次为元素命名都心惊胆战,求神拜佛,没写一条样式,F5都按多几次,每个组件都测试一遍...
场景二:承接上文,由于页面和弹窗样式冲突了,所以把页面的冲突样式的选择器加上一些结构逻辑,比如子选择器、标签选择器,借此让选择器独一无二。一段时间后,新同事接手跟进需求,对样式进行修改,由于选择器是一连串的结构逻辑,看不过来,嫌麻烦,就干脆在样式文件最后用另一套选择器,加上了覆盖样式...接下来又有新的需求...最后的结果,一个元素对应多套样式,遍布整个样式文件...
以往开发组件,我们都用“重名概率小”或者干脆起个“当时认为是独一无二的名字”来保证样式不冲突,这是不可靠的。
理想的状态下,我们开发一套组件的过程中,我们应该可以随意的为其中元素进行命名,而不必担心它是否与组件以外的样式发生冲突。
BEM解决这一问题的思路在于,由于项目开发中,每个组件都是唯一无二的,其名字也是独一无二的,组件内部元素的名字都加上组件名,并用元素的名字作为选择器,自然组件内的样式就不会与组件外的样式冲突了。
这是通过组件名的唯一性来保证选择器的唯一性,从而保证样式不会污染到组件外。
这也可以看作是一种“硬性约束”,因为一般来说,我们的组件会放置在同一目录下,那么操作系统中,同一目录下文件名必须唯一,这一点也就确保了组件之间不会冲突。
BEM的命名规矩很容易记:block-name__element-name--modifier-name,也就是模块名 + 元素名 + 修饰器名。
BEM设计思想
BEM 基于组件方式的web开发方法,采用块+元素+修饰符组合css命名,核心思想是强制约束规范,避免css样式冲突,可帮助您在前端开发中创建可重用的组件和代码共享。
BEM特点:
- 简单:要使用BEM,只需采用BEM的命名约定。
- 模块化的:独立的块和CSS选择器使您的代码可重用和模块化。
- 灵活:使用BEM,可以按照自己喜欢的方式重组和配置方法和工具。
BEM方法可确保参与网站开发的每个人都使用单一代码库并使用相同的语言。使用BEM正确的命名约定将更好地为您准备对网站进行的设计更改。
BEM使用注意
模块名称命名:描述了它的目的(“它是什么?” —— 菜单或者按钮),而不是它的状态(“它看起来是什么样子?” —— 红色或者大的)。
非结构化命名:模块不应该影响它所在的环境,这意味着你不应该为模块设置会影响到外部的形状(影响大小的 padding 或边框)和定位。
选择器选用:你也不应该在使用 BEM 的时候使用 CSS 标签选择器和 ID 选择器。
修饰符命名:名称可以包含拉丁字母、数字、短划线。
BEM命名约定
BEM:块(block)、元素(element)、修饰符(modifier)
Block:一个独立的,可以复用而不依赖其他组件的部分,可作为一个块。
Element:属于块的某部分,可作为一个元素。
Modifier:用于修饰块或元素,体现出外形行为状态等特征的,可作为一个修饰器。
.block{} .block__element{} .block--modifier{}
其中块可以用单个连字符来界定:如
.site-search{} //块 .site-search__field{} //元素 .site-search--full{} //修饰符
修饰符注意:
- 仅使用类别名称选择器
- 没有标签名称或ID
- 不依赖页面上的其他块/元素
BEM 规范虽然结构比较清晰,但有时候会产生代码冗余。
为避免写太多重复性的代码,我们要学会善于利用预编译语言
//css为例 <p class="article"> <p class="article__body"> <p class="tag"></p> <button class="article__button--primary"></button> <button class="article__button--success"></button> </p> </p> //less为例 .article { max-width: 1200px; &__body { padding: 20px; } &__button { padding: 5px 8px; &--primary {background: blue;} &--success {background: green;} } }
常问问题
这些常见问题是由BEM开始的开发人员的真实问题,由GetBEM社区回答。
1. 为什么我应该选择BEM,而不选择其他CSS模块化解决方案?
3. 为什么我需要CSS类进行阻止,而不是使用语义自定义标签?
4. 为什么需要将块和带前缀的修饰符类组合为一个已修改的块?
7. 我可以在选择器(如button.button)中组合标签和类吗?
8. 命名修饰符与CSS中的修饰符相对应吗?喜欢.block__element--border-bottom-5px。
9. 另一个元素中的一个元素的类名称是什么?.block__el1__el2?
还有一些其他的CSS模块化解决方案(例如OOCSS,AMCSS,SMACSS,SUITCSS)。选择BEM的原因是什么?
BEM为所有前端技术提供解决方案:CSS,JavaScript,模板;以及用于构建Web应用程序的过程。该方法适用于任何地方。但是,要将其应用到JavaScript中并进行模板化,则需要特殊的框架,而在CSS中,您可能只是遵循方法上的建议。BEM的CSS部分最容易纳入您的开发过程。这就是为什么许多人只使用它的原因。另一方面,如果最近您发现您的项目完全采用BEMed(用CSS编写),并且对它不断增加的维护感到满意,那么您可能会采取下一步措施来模块化Web应用程序。BEM CSS将更易于与模块化JavaScript和基于块的项目文件结构进行协调。
如果仅谈论CSS模块化解决方案,则BEM的关键功能是模块的独立性。遵循CSS建议可以将块放置在页面上的任何位置,并确保不会受到周围环境的影响。另外,如果您最近需要在当前块中嵌套另一个块,则可以保证它们的完全兼容性。换句话说,在维护Web应用程序时,您将能够在页面上移动块,添加其他块并将它们组合在一起。
BEM CSS明确定义了哪个CSS属于某个接口,因此使用它可以回答以下问题的答案:“我可以删除这段代码吗?” 和“如果更改这段代码会发生什么情况,哪些接口部分会受到影响?”。
BEM建议像这样修改块<div class="block block--mod">。为什么不使用像like这样的简单版本<div class="block mod">?由于我们现在结合了选择器.block.mod,因此很容易为其定义所有CSS属性。
建议使用修饰符CSS类的块名作为前缀。
首先,由于可以在同一DOM节点上混合多个块和元素,因此我们需要确保修饰符仅影响它所属的块。假设我们有一个菜单项元素和一个按钮混合在一起。在HTML中,此结构由以下标记表示:
<div class="menu__item button"></div>
在这种情况下,向.active它们添加修饰符会同时影响两者。
<div class="menu__item button active"></div>
所有3个坐在同一DOM节点,所以无法区分,如果我们的意思menu__item.active或button.active。在带前缀的情况下,命名button--active明确地表示,因为这只是必须受影响的按钮。
另一点是CSS特异性。组合的选择器比单个类的选择器更具体(意味着更重要)。这意味着用父块代码重新定义它们时可能会遇到麻烦。
<div class="header">
<button class="button active">
</div>
如果您.button.active的代码中已经包含选择器,则重新定义.header .button的特异性将与修饰符组合选择器的特异性完全相同,从而使您依赖于声明的CSS规则的顺序。而如果使用前缀修饰符,则始终可以确保级联选择器.header .button将覆盖.button--active修饰符。
这使工作更加轻松,尤其是对于可维护的项目。
第三点是,通过查看<div class="block block--mod">标记,您可以清楚地看到块结构。容易认识到我们有一个块及其修饰符,这里没有不同的解释。不幸的是,掌握<div class="block mod">代码并不能提供此类信息。根据确切的CSS类有时,在这里我们是否有一个块和一个修饰符或两个块的混合是无法识别的。如果实体的名称复杂或缩写/缩写(有时在大型项目中会发生),这可能会更加令人困惑。
在文件系统上寻找相应的代码时,对块结构的清楚了解特别有帮助。
.block--mod在重构和对所有项目文件使用全局搜索时,您还将赞赏练习。想象一下,寻找没有前缀.mod的HTML及其可能包含的所有HTML片段。
最后,从开发过程的角度来看,.block.mod和之间的区别.block--mod只是一个符号。使用-代替.花费不多,但它带来了上面列出的所有好处。此外,由于预处理程序开始支持BEM表示法,因此很自然地在&--mod此处编写代码,并最终按照建议的方式声明了修饰符。
块可以表示为自定义标签,我们可以为其定义CSS规则。看起来我们根本不需要CSS类用于块。它们只能用于修饰符,例如<button class="mod"/>。
使用自定义标签作为块选择器确实是BEMish解决方案之一,并且可以使用。但是,此变体不如建议的“类”方法灵活。
这很有可能需要在修饰符类的块名前添加前缀,以为其提供名称空间。细节在破获“为什么修改CSS类与其父块名称前缀?” 题。因此,最后一个块的自定义标签版本是<block class="block--mod"/>。看起来与<div class="block block--mod">假设标签无关,您可以使用任何自定义节点并使用,这看起来并没有太大不同<block class="block block--mod">。
第二个缺点是“标记”版本无法使用块的混合,而“类”版本自然用来表示<div class="block1 block2">。
最后一种反对这种方法的方法是,在许多情况下,您根本无法用自定义标签来表示块。对于一个link区块,您肯定需要<a>标记,对于则同样如此<input>。
为什么在修改后的代码块中,像这样的代码块和修饰符的类都放在一起<div class=”block block--mod”>?
有关已修改块的所有内容都可以在中描述.block--mod。如果2个修饰符之间有共同点,则可以使用预处理器的mixins来避免复制粘贴。
由于有了预处理器,这种方法才有可能。但是,它带来了一些应注意的缺点。
如果在同一块上组合2个或更多修改器,则将<div class="block--theme--christmas block--size--big">两次获得核心块的样式。但是,这取决于预处理器算法。
使用JavaScript动态添加/删除修饰符时,附加修饰符更方便。关闭它意味着仅从DOM节点中删除一个CSS类,而无需再将核心块CSS类永久地放回原处。
例如xmas,如果我有一个块修饰符,并且我也希望该块中的元素也以圣诞节为主题,那怎么做才是最好的呢?
是否--xmas为每个元素都添加了后缀?还是这将是嵌套的一个用例(例如block--xmas block__elem { ... }?)
虽然BEM通常建议避免使用嵌套选择器,但在这种情况下,它们是合理的。
创建嵌套选择器时,您声明一个实体依赖于另一个。因为BEM引入了独立的组件,所以当我们谈论2个不同的块时,不建议采用这种方法。
但是,当涉及到一个块及其元素时,它们的含义并不相同。根据定义,元素在其父块之外没有任何意义。因此,元素是块相关的实体。假设这样,元素受块的当前状态影响是很正常且合乎逻辑的。
因此,这是BEM中常见的编码模式
.my-block--xmas .my-block__button {
/* Jingle bells, jingle bells, jingle all the way.*/
}
我听说全球改性喜欢visible,invisible,red,opacity50未在BEM欢迎。为什么?
我认为将这样的通用属性合并到这样的全局类中,然后将其应用于不同的块是很有用的。
实际上,您可以在同一DOM节点上有2个主要的CSS类。在BEM中,我们称其为mix:
<div class="block1 block2"></div>
但重要的是,两者block1和block2都应为独立块。这与人们通常所说的“全局修饰符”略有不同,因为修饰符本身没有任何意义,而只是一组要更改的属性。
<div class="block globalmod"></div>
如果您认为自己有一个全局修饰符,则可能会遇到以下问题:
首先出现特异性问题。在本地修饰符的情况下,CSS代码如下所示:
.block {
display: block;
}
.block--hidden {
display: none;
}
块选择器和修饰符选择器都具有相同的特异性。当修饰符声明在该块之后时,它将重新定义CSS属性。这些样式属于图块,并存储在图块文件中。因此,独立于如何从源代码生成结果CSS,您将始终按此顺序排列它们,并确保重新定义。
对于全局修饰符,如果它们遵循代码中的修饰符,则可以通过块重新定义其属性:
.hidden { display: none }
/* ... */
.block { display: block }
<div class="block hidden">you still see me</div>
解决此问题的方法之一是通过将全局修饰符添加!important到选择器中来提高选择器的特异性。但是在这种情况下,此类全局修饰符的任何副作用都只能由具有相同!important指令的声明覆盖。
另一种方法是在所有其他样式之后加载全局修饰符CSS。但是在这种情况下,您将无法再对组件使用延迟加载策略。附加的惰性CSS仍将在修饰符之后加载,并且您会遇到相同的问题。
下一个问题是在同一块上几个全局修饰符的组合。
<div class="block mod1 mod2"></div>
在这种情况下,您绝对无法控制该块。代码中修饰符的顺序可以不同。如果它与其他声明冲突,则更改顺序可以解决此冲突,但会导致另一个冲突。唯一的方法是重新定义块中的混乱。并且不要忘记!important您的hack。
同样,根据一个块,相同的修饰符可以不同地实现。即使是简单的.hidden,有时需要不display: none,但visibility: hidden甚至position: absolute; left: -9999px等等,如果你需要带来一些变化到你的块,它是更漂亮不浪费时间寻找所有在那里这个模块可以与全球改性剂相结合的地方。特别是假设通常不会在任何地方描述这种依赖关系。
通过将修饰符封装在类似的块中,可以避免所有这些麻烦.block--mod。
实际上,使用全局修饰符会使生成的代码更少。但是,如果您以字节为单位来衡量实际差异,通常看起来并没有那么大。特别是如果您使用的是CSS优化器,可以结合选择器。
我可以在选择器中将标记和类组合在一起button.button吗?
我想使用选择器,例如button.button将块功能封装在特定标签中。如果最近有人在他们的代码中使用<h2 class="button">,那么这种封装将防止冲突。
这样的选择器的CSS专用性在增加。.button--mod选择器不会覆盖该块的CSS属性,只会button.button--mod起作用。您还需要将其修饰符与标记结合使用,最近将重新定义您的代码块的开发人员也需要这样做。
最近,当项目进展较大,它很可能,你可能有input.button,span.button并a.button为好。并且所有带修饰符和嵌套元素的前缀选择器都需要4个声明。
因此,最好不要用这样的前缀来束缚自己的双手。但是,如果您仍可以为用户提供每个块的模板,那么您仍然可以轻柔地确保块使用正确的标记。这是最灵活和自动的解决方案。
如果模板看起来很繁琐,则可以使用“文档化”方法来通知您的用户将CSS块类应用于哪个标签,这可以通过记录模块代码来完成。最短的版本可能只是带有标记名称的注释,该标记名称以块声明为前缀/*button*/.button。或者这可能是一个更大的注释,需要完整的HTML片段才能使该块起作用。
多亏了混合,我们可以创建很多表示CSS属性的修饰符,并将它们分配给块。但是我听说“这很糟糕”。例如,此选择器.block__element--border-bottom-5px被标记为“糟糕”。我想知道为什么以及如何命名修饰符?
不建议命名与它们的CSS表示相对应的修饰符。确实,它看起来不是很好,但是也有实际的理由反对它。最近,组件的视图发生了变化,您不仅需要修复CSS,还需要修复选择器。因此,当边框为6px时,将需要更改所有模板,有时还需要更改JavaScript。
同样,修饰符永远不会只有一个CSS属性来定义并且永远存在。即使现在只有一个边界将一个州与另一个州区分开,这很可能您最近还需要其他CSS属性来实现同一块状态。如果您在称为“边框”的修饰符中定义背景或填充,这将很麻烦。因此,建议即使现在只有修饰符一个属性,也要为修饰符选择语义名称。
另一个元素中的一个元素的类名称是什么?.block__el1__el2?
如果我的块具有复杂的结构并且其元素嵌套,该怎么办?CSS类block__elem1__elem2__elem3看起来很吓人。
根据BEM方法,应将块结构弄平;您不需要反映该块的嵌套DOM结构。因此,这种情况下的类名称为:
.block {}
.block__elem1 {}
.block__elem2 {}
.block__elem3 {}
而该块的DOM表示可以嵌套:
<div class='block'>
<div class='block__elem1'>
<div class='block__elem2'>
<div class='block__elem3'></div>
</div>
</div>
</div>
除了使类看起来更好之外,它还使元素仅依赖于块。因此,在对界面进行更改时,可以轻松地将它们跨块移动。块DOM结构的更改不需要对CSS代码进行相应的更改。
<div class='block'>
<div class='block__elem1'>
<div class='block__elem2'></div>
</div>
<div class='block__elem3'></div>
</div>
CSS重置是一种很好的表现。许多框架首先对齐所有内容,然后应用其特殊样式。BEM不建议常规重置。为什么?而我们应该怎么做呢?
如果您使用普通复位,则不会对您的块造成任何不良影响(嗯,以下某些特殊情况除外)。因此,BEM不禁止使用它们。但是使用BEM方式会更有效。
通用CSS重置是一组应用于文档节点的CSS,可确保它们的默认视图在不同的浏览器中相同。在大多数情况下,CSS规则是为标签选择器编写的,在BEM中不建议这样做(您可以在上面找到很多解释)。
另一点是,在BEM中,块封装了显示和运行所需的所有内容。这就是为什么我们称BEM块独立。如果在未将第三方CSS添加到页面的情况下该块无法正常显示,则不能将其称为“独立”。
假设所有这一切,BEM建议每个块自行重置。如果您同时在HTML中进行menu阻止和list阻止<ul>,则它们每个都应提供通常提供给的重置CSS <ul>。您可能会担心具有相同重置规则的多个块会在结果代码中重复出现大小写。但这是CSS优化器应该为您做的。作为开发人员,您可以独立开发每个块,就像同一页上没有其他块一样。
如果您没有CSS优化器来将选择器与相同的规则集结合在一起,则可以使用预处理器来防止复制粘贴。对于每个新块,您都可以使其混合适当的代码来重置自身。例如,使用SASS,它看起来像:
.menu {
@include reset-list;
}
.menu__item {
@include reset-list-item;
}
/* ... */
.list {
@include reset-list;
}
.list__item {
@include reset-list-item;
}
但是,使用这种混合方式时,您应该意识到,唯一的原因就是没有合适的优化器。
对每个块进行重置(除了不错和BEMish之外)还可以防止注入依赖于浏览器默认值的第三方HTML / CSS标记问题,因此可能会受到全局重置的影响。
参考资料:
2. http://getbem.com/ (官方)
3. https://blog.csdn.net/eunice_sytin/article/details/83341381