企业开发CSS命名BEM代码规范实践


Posted in HTML / CSS onFebruary 12, 2022

背景

最近老大在维护别人的代码时,发现我们团队写的样式各有种的想法及风格,这在后续维护会增加一定的难度,所以老大决定统一样式的会名规范,所以就安排我去调研及实践,下面是我调研的结果。

什么是 BEM 命名规范

BEM 由 Yandex 团队提出的一种前端 CSS 命名方法论。BEM 是 BlockElementModifier 的缩写 ,其中B 表示块(block)、E 表示元素(element)、M 表示修饰符(modifier)。

这三个部分通常使用__ 与--连接。即: .块__元素--修饰符{}

Block:代表了一个独立的块级元素,可以理解为功能组件块。一个 Block 就是一个独立的区块,比如头部是个 block,表单功能输入框是一个block,block可大可小。Element:是 Block 的一部分不能独立来使用的,所有的 Element 都是与 Block 紧密关联的。例如一个带有 icon 的输入框,那么 这个 icon 就是这个输入框 Block 的一个 Element,脱离了输入框的 Block 那么这个 icon 就没有意义。Modifier:是用来修饰 Block 或 Element,表示 block 或者 element 在外观或行为上的改变。例如,上面提到的输入框 Block,当鼠标悬停时边框高亮,那么这个高亮的效果就应该用 Modifier 来实现。

企业开发CSS命名BEM代码规范实践

上图绿色表 block, 蓝色表示 element , 红色表示 modifier

为什么要用 BEM?

性能

CSS 引擎查找样式表,对每条规则都按从右到左的顺序去匹配,以下这段代码看起来很快,实际上很慢。通常我们会认为浏览器是这样工作的:找到唯一ID元素ul-id —> 把样式应用到li元素上。

事实上: 从右到左进行匹配,遍历页面上每个 li元素并确定其父元素

#ul-id li {}

所以不要让你的css超过三层。

语义化

BEM 的关键是光凭名字就可以告诉其他开发者某个标记是用来干什么的。 通过浏览HTML代码中的class属性,你就能够明白模块之间是如何关联的:有一些仅仅是组件,有一些则是这些组件的子孙或者是元素,还有一些是组件的其他形态或者是修饰符。

常规的命名法示例:

<div class="article">
    <div class="body">
        <button class="button-primary"></button>
        <button class="button-success"></button>
    </div>
</div>

这种写法从 DOM 结构和类命名上可以了解每个元素的意义,但无法明确其真实的层级关系。在 css 定义时,也必须依靠层级选择器来限定约束作用域,以避免跨组件的样式污染。

使用了 BEM 命名方法的示例:

<div class="article">
    <div class="article__body">
        <div class="tag"></div>
        <button class="article__button--primary"></button>
        <button class="article__button--success"></button>
    </div>
</div>

通过 BEM 命名方式,模块层级关系简单清晰,而且 css 书写上也不必作过多的层级选择。

怎么用 ?

假设我们要实现这样的一个卡片功能:

 企业开发CSS命名BEM代码规范实践

 根据上面的设计图,我们用 bem 方式来给对应 class 命名,如下所示:

<div class="card">
    <img class="card__img" src="./img.jpg" alt="">
    <div class="card__content">
        <ul class="card__list">
            <li class="card__item card__item--active">手机</li>
            <li class="card__item">移动市场</li>
            <li class="card__item">科技</li>
        </ul>
        <p class="card__desc">商化前端是一个很有活力的团队,能学到很多知识,你心动了吗?</p>
        <a class="card__link" href="#">详细内容</a>
    </div>
</div>

对应的 CSS 结构:

.card{
  // 省略...
}
.card__img{
  // 省略...
}
.card__content {
  // 省略...
}
.card__list{
  // 省略...
}
.card__item {
  // 省略...
}
.card__item--active {
  // 省略...
}
.card__link{
  // 省略...
}
.card__link:hover{
  // 省略...
}

从上面的代码可以看出,我们样式类没有一点嵌套关系,嵌套关系都以从命名的方式来代替。

这里刚开始使用 bem 的时候容易犯一个问题,就是把 ul 和 li 的样式写成 card__content__list 和 card__content__list__item 因为这样更能体现层级的关系。

这有悖BEM命名规范,BEM的命名中只包含三个部分,元素名只占其中一部分,所以不能出现多个元素名的情况。这样的约定可以防止当层级很深命名过长的问题。

上面我们每个样式都要写一遍 card,如果 card 换成一个比较长的单词,这样也太冗长了,这也是大家不太喜欢 bem 的一个原因,但这个 sass 或 less 是很好的解决的,我们可以用 & 表示根元素,上面在 less 或 sass 中可以改成如下结构:

.card{
  // 省略...
  &__img{
  // 省略...
  }
  &__content {
  // 省略...
  }
  &__list{
  // 省略...
  }
  &__item {
  // 省略...
  }
  &__item--active {
  // 省略...
  }
  &__link{
  // 省略...
  }
  &__link:hover{
  // 省略...
  }
}

插件的使用

和 eslint 校验类似,stylelint 也有一个配置文件.stylelintrc.js(还有其他格式的,这里以js文件为例)。

module.exports = {};

为了让小伙伴编写符合 Bem 的规范,这里我们使用 stylelint-selector-bem-pattern 插件,它结合了插件 postcss-bem-linter 的规则,可用于校验 BEM 命名规范。

module.exports = {
  plugins: [
    'stylelint-selector-bem-pattern'
  ],
  "rules": {
       'plugin/selector-bem-pattern': {
          // 选择Preset Patterns,支持suit和bem两种,默认suit规范;
          // 不管哪种都需要手动指定,因为该插件未给源插件默认指定
          'preset': 'bem',
          /**
           * 自定义模式规则
           * 指定组合的选择器检查规则,其实就是指定class名称规则
           * 支持正则字符串、返回正则的函数、包含2个选项参数的对象等写法
           */
          componentSelectors: {
            // 只初始的选择器规则(可以理解为外层的class规则)
            initial: '^\\.{componentName}(?:__[-a-z]+)?(?:--[a-z]+)?$',
            // 可以理解为外层以内的选择器的规则,
            // 如果不指定,则跟initial同样的规则,
            // 注意这里配置的时候比上面少一个问号,
            // 是希望内层就不应该只有componentName作为选择器了
            combined: '^\\.{componentName}(?:__[-a-z]+)(?:--[a-z]+)?$'
          },
          "utilitySelectors": "^\\.u-[a-z]+$",
          ignoreSelectors: ['^\\.el-', '/deep/', '>>>', '^\\.icon-'],
          ignoreCustomProperties: [],
        }
   }
}

配置完成后,为了能让 VsCode 给出错误提示,我们需要在 VsCode 中添加 stylelint插件。

最后, 就是 git commit 校验

// package.json
{
    "husky": {
        "hooks": {
          "pre-commit": "lint-staged"
        }
    },
   "lint-staged": {
    "*.{vue,ts,tsx,js,jsx}": "eslint --fix",
    "*.{vue,css,less,sass,scss}": "stylelint --fix"
  },
}

这里涉及到 husky 的使用,如果不懂的,可以自行谷歌了解理详细的信息。

实战

配置完成后,我们就需要动手验证一下了

首先,我们需要定义一个上下文,这样插件才知道对 CSS 进行校验。

比如我们有如下的 html 结构:

<div class="form form--theme-xmas">
  <input class="form__input" />
  <input class="form__submit form__submit--disabled" type="submit" />
</form>

对应的 css 要这样写:

/** @define formWrapper */
.formWrapper{
  padding: 0 20px;
  box-sizing: border-box;
}
.formWrapper--line{
  display: none;
}
.formWrapper__form-item{
  display: flex;
  align-items: center;
  margin-bottom: 20px;
}

这里 @define formWrapper 声明了一个 block formWrapper, 表示样式必须是 formWrapper 开头,否则报错。

如果有多个 block,我们只要多个 @define 声明即可。

/** @define Foo */
.Foo {}
/** @define Bar */
.Bar {}

如果一个类不属于任何的 block,我们又要怎么做,才不会导致 styleint 报错呢?这里我们可以加 /** @end */ 表示 block 的结束。

/** @define form */
.form{
  display: flex;
}
.form--theme-blue{
  text-align: center;
}
/** @end */
 
.isActive{
  display: flex;
}

如果我们想忽略对某块样式进行校验,可以使用下面的语法来忽略:

/** @define MyComponent */
.MyComponent {
  display: flex;
}
/* postcss-bem-linter: ignore */
.no-flexbox .Component {
  display: block;
}

总结

BEM 最难的部分之一是明确作用域是从哪开始和到哪结束的,以及什么时候使用或不使用它。随着不断使用的经验积累,你慢慢就会知道怎么用,这些问题也不再是问题。技术无好坏,合适方最好。

以上就是企业开发CSS命名BEM代码规范实践的详细内容,更多关于CSS命名BEM代码规范的资料请关注三水点靠木其它相关文章!

 
HTML / CSS 相关文章推荐
CSS3教程(10):CSS3 HSL声明设置颜色
Apr 02 HTML / CSS
一款纯css3实现的响应式导航
Oct 31 HTML / CSS
css3实现文字扫光渐变动画效果的示例
Nov 07 HTML / CSS
css3实现背景模糊的三种方式(小结)
May 15 HTML / CSS
详解HTML5通讯录获取指定多个人的信息
Dec 20 HTML / CSS
HTML5 Canvas实现文本对齐的方法总结
Mar 24 HTML / CSS
html5构建触屏网站之touch事件介绍
Jan 07 HTML / CSS
HTML5 Canvas如何实现纹理填充与描边(Fill And Stroke)
Jul 15 HTML / CSS
Application Cache未缓存文件无法访问无法加载问题
May 31 HTML / CSS
详解HTML5 Canvas绘制不规则图形时的非零环绕原则
Mar 21 HTML / CSS
浅谈移动端网页图片预加载方案
Nov 05 HTML / CSS
HTML5 HTMLCollection和NodeList的区别详解
Apr 29 HTML / CSS
CSS3实现360度循环旋转功能
CSS实现九宫格布局(自适应)的示例代码
Feb 12 #HTML / CSS
postman中form-data、x-www-form-urlencoded、raw、binary的区别介绍
Jan 18 #HTML / CSS
MIME类型中application/xml与text/xml的区别介绍
Jan 18 #HTML / CSS
HTTP中的Content-type详解
Jan 18 #HTML / CSS
POST提交数据常见的四种方式
Jan 18 #HTML / CSS
Html5获取用户当前位置的几种方式
You might like
combox改进版 页面原型参考dojo的,比网上jQuery的那些combox功能强,代码更小
2010/04/15 Javascript
GridView中获取被点击行中的DropDownList和TextBox中的值
2013/07/18 Javascript
js函数定时器实现定时读取系统实时连接数
2014/04/30 Javascript
js中this的用法实例分析
2015/01/10 Javascript
jQuery简单入门示例之用户校验demo示例
2016/07/09 Javascript
Angularjs中$http以post请求通过消息体传递参数的实现方法
2016/08/05 Javascript
详解AngularJS中$filter过滤器使用(自定义过滤器)
2017/02/04 Javascript
Node.js简单入门前传
2017/08/21 Javascript
AngularJS中filter的使用实例详解
2017/08/25 Javascript
浅谈在vue项目中如何定义全局变量和全局函数
2017/10/24 Javascript
js代码编写无缝轮播图
2020/09/13 Javascript
NodeJS开发人员常见五个错误理解
2020/10/14 NodeJs
[04:44]显微镜下的DOTA2第二期——你所没有注意到的细节
2014/06/20 DOTA
[33:33]完美世界DOTA2联赛PWL S2 FTD.C vs SZ 第二场 11.27
2020/11/30 DOTA
解决python写的windows服务不能启动的问题
2014/04/15 Python
详解Python中的多线程编程
2015/04/09 Python
详解通过API管理或定制开发ECS实例
2018/09/30 Python
Python开发之Nginx+uWSGI+virtualenv多项目部署教程
2019/05/13 Python
Python使用Pandas库常见操作详解
2020/01/16 Python
keras 两种训练模型方式详解fit和fit_generator(节省内存)
2020/07/03 Python
10个示例带你掌握python中的元组
2020/11/23 Python
Python 中 sorted 如何自定义比较逻辑
2021/02/02 Python
CSS3旋转——彩色扇子兼容firefox浏览器
2013/06/04 HTML / CSS
基于 HTML5 Canvas实现 的交互式地铁线路图
2018/03/05 HTML / CSS
英国在线花园中心:You Garden
2018/06/03 全球购物
Molly Bracken法国电子商店:法国女性时尚品牌
2019/07/24 全球购物
Crocs波兰官方商店:女鞋、男鞋、童鞋、洞洞鞋
2019/10/08 全球购物
加拿大鞋网:Globo Shoes
2019/12/26 全球购物
2014年消防工作实施方案
2014/02/20 职场文书
终止合同协议书
2014/04/17 职场文书
邮政竞聘演讲稿
2014/09/03 职场文书
升职感谢信
2015/01/22 职场文书
世界文化遗产导游词
2015/02/13 职场文书
创业项目大全(适合在家创业的项目)
2019/08/15 职场文书
南阳市白酒市场的调查报告
2019/11/08 职场文书
Python中request的基本使用解决乱码问题
2022/04/12 Python