[{"data":1,"prerenderedAt":528},["ShallowReactive",2],{"content:\u002F2023\u002Fcss\u002Fbemcss":3,"surround:\u002F2023\u002Fcss\u002Fbemcss":517},{"id":4,"title":5,"body":6,"categories":481,"date":484,"description":485,"draft":486,"extension":487,"image":488,"meta":489,"navigation":503,"path":504,"permalink":488,"published":488,"readingTime":505,"recommend":488,"references":488,"seo":510,"sitemap":511,"stem":512,"tags":513,"type":515,"updated":488,"__hash__":516},"content\u002Fposts\u002F2023\u002Fcss\u002Fbemcss.md","CSS命名规范-BEM（转载）",{"type":7,"value":8,"toc":470},"minimark",[9,19,24,27,30,33,36,39,42,45,48,61,64,70,77,87,96,99,105,116,125,132,135,139,142,145,153,156,162,165,171,174,177,180,183,186,192,198,201,210,224,230,233,239,245,256,259,270,273,277,286,292,295,302,308,335,341,345,348,351,358,364,371,374,378,381,396,399,405,408,412,415,421,424,430,433,436,439,445,456,464],[10,11,12,13],"p",{},"原文戳这里👉：",[14,15,16],"a",{"href":16,"rel":17},"https:\u002F\u002Fbemcss.com",[18],"nofollow",[20,21,23],"h2",{"id":22},"bem-解决的问题","BEM 解决的问题",[10,25,26],{},"css 的样式应用是全局性的，没有作用域可言。考虑以下场景：",[10,28,29],{},"**场景一：**开发一个弹窗组件，在现有页面中测试都没问题，一段时间后，新需求新页面，该页面一打开这个弹窗组件，页面中样式都变样了，一查问题，原来是弹窗组件和该页面的样式相互覆盖了，接下来就是修改覆盖样式的选择器...又一段时间，又开发新页面，每次为元素命名都心惊胆战，求神拜佛，每写一条样式，F5 都按多几次，每个组件都测试一遍...",[10,31,32],{},"**场景二：**承接上文，由于页面和弹窗样式冲突了，所以把页面的冲突样式的选择器加上一些结构逻辑，比如子选择器、标签选择器，借此让选择器独一无二。一段时间后，新同事接手跟进需求，对样式进行修改，由于选择器是一连串的结构逻辑，看不过来，嫌麻烦，就干脆在样式文件最后用另一套选择器，加上了覆盖样式...接下来又有新的需求...最后的结果，一个元素对应多套样式，遍布整个样式文件...",[10,34,35],{},"以往开发组件，我们都用“重名概率小”或者干脆起个“当时认为是独一无二的名字”来保证样式不冲突，这是不可靠的。",[10,37,38],{},"理想的状态下，我们开发一套组件的过程中，我们应该可以随意的为其中元素进行命名，而不必担心它是否与组件以外的样式发生冲突。",[10,40,41],{},"BEM 解决这一问题的思路在于，由于项目开发中，每个组件都是唯一无二的，其名字也是独一无二的，组件内部元素的名字都加上组件名，并用元素的名字作为选择器，自然组件内的样式就不会与组件外的样式冲突了。",[10,43,44],{},"这是通过组件名的唯一性来保证选择器的唯一性，从而保证样式不会污染到组件外。",[10,46,47],{},"这也可以看作是一种“硬性约束”，因为一般来说，我们的组件会放置在同一目录下，那么操作系统中，同一目录下文件名必须唯一，这一点也就确保了组件之间不会冲突。",[10,49,50,51,55,56,60],{},"BEM 的命名规矩很容易记：",[52,53,54],"code",{"code":54},"block-name__element-name--modifier-name","，也就是",[57,58,59],"strong",{},"模块名 + 元素名 + 修饰器名","。",[10,62,63],{},"一般来说，根据组件目录名来作为组件名字：",[10,65,66,67],{},"比如分页组件：",[52,68,69],{"code":69},"\u002Fapp\u002Fcomponents\u002Fpage-btn\u002F",[10,71,72,73,76],{},"那么该组件模块就名为 ",[52,74,75],{"code":75},"page-btn","，组件内部的元素命名都必须加上模块名，比如：",[78,79,85],"pre",{"className":80,"code":82,"language":83,"meta":84},[81],"language-html","\u003Cdiv class=\"page-btn\">\n  \u003Cbutton type=\"button\" class=\"page-btn__prev\">上一页\u003C\u002Fbutton>\n  \u003C!-- ... -->\n  \u003Cbutton type=\"button\" class=\"page-btn__next\">下一页\u003C\u002Fbutton>\n\u003C\u002Fdiv> \n","html","",[52,86,82],{"__ignoreMap":84},[10,88,89,90,60,93,60],{},"上面我们用双下划线来明确区分模块名和元素名，当然也可以用单下划线，比如 ",[52,91,92],{"code":92},"page-btn_prev和page-btn_next",[57,94,95],{},"我们只需保留 BEM 的思想，其命名规范可以任意变通",[10,97,98],{},"一开始了解 BEM 的时候，可能会产生误解，出现以下不正确的命名方式：",[78,100,103],{"className":101,"code":102,"language":83,"meta":84},[81],"\u003Cdiv class=\"page-btn\">\n  \u003C!-- ... -->\n  \u003Cul class=\"page-btn__list\">\n    \u003Cli class=\"page-btn__list__item\">\n      \u003Ca href=\"#\" class=\"page-btn__list__item__link\">第一页\u003C\u002Fa>\n    \u003C\u002Fli>\n  \u003C\u002Ful>\n  \u003C!-- ... -->\n\u003C\u002Fdiv> \n",[52,104,102],{"__ignoreMap":84},[10,106,107,108,111,112,115],{},"分页组件有个 ul 列表名为：",[52,109,110],{"code":110},"page-btn__list","，列表里面存放每一页的按钮，名为：",[52,113,114],{"code":114},"page-btn__list__item__link","，这是不对的。",[10,117,118,119,121,122,60],{},"首先，有悖 BEM 命名规范，BEM 的命名中只包含三个部分，元素名只占其中一部分，所以不能出现多个元素名的情况，所以上述每一页的按钮名 ",[52,120,114],{"code":114}," 可以改成：",[52,123,124],{"code":124},"page-btn__btn",[10,126,127,128,131],{},"其次，有悖 BEM 思想，",[57,129,130],{},"BEM 是不考虑结构的","，比如上面的分页按钮，即使它是在 ul 列表里面，它的命名也不应该考虑其父级元素。当我们遵循了这个规定，无论父元素名发生改变，或是模块构造发生的改变，还是元素之间层级关系互相变动，这些都不会影响元素的名字。",[10,133,134],{},"所以即使需求变动了，分页组件该有按钮还是要有按钮的，DOM 构造发生变动，至多也就不同元素的增删减，模块内名称也随之增删减，而不会出现修改名字的情况，也就不会因为名字变动，牵涉到 JS 文件的修改，或样式文件的修改。",[20,136,138],{"id":137},"bem-命名好长","BEM 命名好长",[10,140,141],{},"BEM 的命名中包含了模块名，长长的命名会让 HTML 标签会显得臃肿。",[10,143,144],{},"其实每个使用 BEM 的开发团队多多少少会改变其命名规范，比如 Instagram 团队使用的驼峰式:",[78,146,151],{"className":147,"code":149,"language":150,"meta":84},[148],"language-css",".blockName-elementName--modifierName { \u002F* ... *\u002F } \n","css",[52,152,149],{"__ignoreMap":84},[10,154,155],{},"还有单下划线：",[78,157,160],{"className":158,"code":159,"language":150,"meta":84},[148],".block-name_element-name--modifierName { \u002F* ... *\u002F } \n",[52,161,159],{"__ignoreMap":84},[10,163,164],{},"还有修饰器名用单横线连接：",[78,166,169],{"className":167,"code":168,"language":150,"meta":84},[148],".blockName__elementName-modifierName { \u002F* ... *\u002F } \n",[52,170,168],{"__ignoreMap":84},[10,172,173],{},"其实这些对缩短命名没有多大的帮助，但我们也无需担心文件体积的问题，由于服务端有 gzip 压缩，BEM 命名相同的部分多，压缩下来的体积不会太大。另外现在都用 IDE 来编写代码了，有自动提示功能，也无须担心重复的输入过长的名字。",[10,175,176],{},"因为命名长，我们是不是可以用子代选择器来代替 BEM 命名？这样至少在 HTML 编写时，让 HTML 标签看起来美观一点。",[10,178,179],{},"下面说说子代选择器带来的问题。",[20,181,182],{"id":182},"子选择器",[10,184,185],{},"子代选择器的方式是，通过组件的根节点的名称来选取子代元素。按照这个思路，分页按钮样式可以这么写：",[78,187,190],{"className":188,"code":189,"language":83,"meta":84},[81],"\u003Cdiv class=\"page-btn\">\n  \u003C!-- ... -->\n  \u003Cul class=\"list\">\u003C\u002Ful>\n  \u003C!-- ... -->\n\u003C\u002Fdiv> \n",[52,191,189],{"__ignoreMap":84},[78,193,196],{"className":194,"code":195,"language":150,"meta":84},[148],".page-btn { \u002F* ... *\u002F } \n.page-btn .list { \u002F* ... *\u002F } \n",[52,197,195],{"__ignoreMap":84},[10,199,200],{},"HTML 看起来美观多了，但这解决了样式冲突问题么？试想下，如果让你来接手这个项目，要增加一个需求，新增一个组件，你命名放心么？",[10,202,203,204,206,207,209],{},"你面临的问题是：你打开组件目录，里面有个分页组件，叫做 ",[52,205,75],{"code":75},"，可是你完全不知道要怎么给新组件命名，因为即使新组件模块名与 ",[52,208,75],{"code":75}," 不一样，也不能保证新组件与分页组件不冲突。",[10,211,212,213,216,217,219,220,223],{},"比如新的需求是“新增一个列表组件”，如果该组件的名字叫做 ",[52,214,215],{"code":215},"list","，其根节点的名字叫 ",[52,218,215],{"code":215},"，那么这个组件下面写的样式，就很可能和 ",[52,221,222],{"code":222},".page-btn .list"," 的样式冲突:",[78,225,228],{"className":226,"code":227,"language":150,"meta":84},[148],".list { \u002F* ... *\u002F } \n",[52,229,227],{"__ignoreMap":84},[10,231,232],{},"这还仅仅只有两个组件而已，实际项目中，十几个或几十个组件，难道我们要每个组件都检查一下来“新组件名是否和以往组件的子元素命名冲突了”么？这不现实。",[10,234,235,238],{},[57,236,237],{},"BEM 禁止使用子代选择器","，以上是原因之一。子代选择器不好的地方还在于，如果层次关系过长，逻辑不清晰，非常不利于维护。为了懒得命名或者追求所谓的“精简代码”，写出下面这种选择器：",[78,240,243],{"className":241,"code":242,"language":150,"meta":84},[148],".page-btn button:first-child {} \n.page-btn ul li a {}\n\u002F* ... *\u002F \n\u002F* 维护代码，新增需求 *\u002F\n.page-btn .prev {} \n",[52,244,242],{"__ignoreMap":84},[10,246,247,248,251,252,255],{},"用层次关系结构关系来定位元素，可能会因为需求改变而大面积的重写样式文件。试想一下维护这类代码有多么痛苦，我们要一边检查该元素的上下文 DOM 结构，一边对照着 CSS 文件，一一对比，找到该元素对应的样式，也就是说我为了改一个元素的代码，需要不断翻阅 HTML 文件和 CSS 文件，可维护性非常之差。更有甚者，来维护这块代码的同事，直接",[57,249,250],{},"在样式文件最后","添加覆盖样式，这会造成一个",[57,253,254],{},"非常严重","的问题了：同一个元素样式零散的分布在文件的不同地方，而且定位该元素的选择器也可能各不相同。",[10,257,258],{},"这样的样式文件只会越写越糟糕，可以说，当我们用子代选择器来定位元素时，这个样式文件就已经注定是要被翻来覆去的重构的了，甚至，每个来维护这个文件的人都会将其重构一遍。",[10,260,261,262,265,266,269],{},"子代选择器还会造成权重过大的问题，当我们要做响应式的时候，某个带样式的元素需要适配不同的屏幕，此时，我们还要",[57,263,264],{},"不断的确认该元素之前的选择器写法","！为了覆盖前面权重过大的样式，甚至通过添加",[57,267,268],{},"额外的类名或标签名","来增加权重。可想而知，此后这个样式文件的维护难度就像雪球一样，越滚越大。",[10,271,272],{},"如果我们用的是 BEM，要覆盖样式很简单：找到要覆盖样式的元素，得知它的类名，在媒体查询中，用它的类名作为选择器，写下覆盖样式，样式就覆盖成功了，不需要担心前面样式的权重过大。",[20,274,276],{"id":275},"bem-修饰器","BEM 修饰器",[10,278,279,280,282,283],{},"根据不同的场景，组件可能会表现出不同的样式。比如分页组件在 pc 端具有具体的页码以及上下页按钮，但在移动端，因空间有限，可能只保留上下页按钮。我们可以用修饰器来区分这两种情况。默认情况下，分页按钮的类名为 ",[52,281,75],{"code":75},"，但在移动端，我们需要加多个类名 ",[52,284,285],{"code":285},"page-btn--min",[78,287,290],{"className":288,"code":289,"language":150,"meta":84},[148],"\u002F* 缩小版分页组件中，具体页码按钮隐去 *\u002F \n.page-btn--min .page-btn__btn { display: none; }\n.page-btn--min .page-btn__prev { width: 50%; }\n.page-btn--min .page-btn__next { width: 50%; } \n",[52,291,289],{"__ignoreMap":84},[10,293,294],{},"上面这种情况用了子代选择器，BEM 是不允许这么写的，BEM 中修饰器的样式不依赖于任何结构关系，也就是说，元素的状态改变只会影响自身，不对其他元素进行影响，但实际上，这很难做到。以上的写法不会造成样式冲突，而且权重的影响也不大。",[10,296,297,298,301],{},"BEM 修饰器代表着元素的状态，但",[57,299,300],{},"有时候元素的状态需要 js 来控制，此时遵循规范没有任何好处，比如激活状态","，BEM 推荐的写法是：",[78,303,306],{"className":304,"code":305,"language":150,"meta":84},[148],".block__element { display: none; }\n.block__element--active { display: block; } \n",[52,307,305],{"__ignoreMap":84},[10,309,310,311,314,315,318,319,322,323,326,327,330,331,334],{},"当用 js 为该元素添加状态时，我们需要知道该元素的名字 ",[52,312,313],{"code":313},"block__element","，这样我们才能推导出它的激活状态为 ",[52,316,317],{"code":317},"block__element--active","，",[57,320,321],{},"这是不合理的，因为很多时候我们无法得知元素的名称","，所以这时候，我们应该统一 js 控制状态的类名格式，比如 ",[52,324,325],{"code":325},"is-active","、",[52,328,329],{"code":329},"js-active"," 等等，",[57,332,333],{},"这些类名只用作标识，不予许有默认的公共样式","：",[78,336,339],{"className":337,"code":338,"language":150,"meta":84},[148],".block__element { display: none; }\n.block__element.is-active { display: block; } \n",[52,340,338],{"__ignoreMap":84},[20,342,344],{"id":343},"原子类和-bem","原子类和 BEM",[10,346,347],{},"BEM 可以不需要用到原子类，但是如果已经引入了类似 Bootstrap 的框架，也没必要强制避免使用原子类，比如“pull-right”、\"ellipsis\"、“clearfix”等等类，这些类非常实用，和 BEM 是可以互补的。",[10,349,350],{},"在组件开发中其实不推荐使用原子类，因为这会降低组件的可复用性。可复用性的最理想状态就是组件不仅仅在不同的页面中表现一致，在跨项目的情况下，也能够运行良好。如果组件的样式因为依赖于某几个原子类就要依赖整个 Bootstrap 库，那么组件的迁移负担就重很多了。",[10,352,353,354,357],{},"原子类更适合应用在实际页面中，这是因为页面",[57,355,356],{},"变动大而且不可复用","，假设在 header 中，我们用到了两个组件 logo 和 user-panel（用户操作面板），两个组件分别置于 header 的左侧和右侧，我们可以这么写：",[78,359,362],{"className":360,"code":361,"language":83,"meta":84},[81],"\u003Cdiv class=\"header clearfix\">\n  \u003Cdiv class=\"logo pull-left\">\n  \u003C!-- ... -->\n  \u003C\u002Fdiv>\n  \u003Cdiv class=\"user-panel pull-left\">\n  \u003C!-- ... -->\n  \u003C\u002Fdiv>\n\u003C\u002Fdiv> \n",[52,363,361],{"__ignoreMap":84},[10,365,366,367,370],{},"header 可以封装成一个模块，但它复用程度不高，不能算是组件，所以即使使用原子类也没有关系。",[57,368,369],{},"在项目中，使用原子类之前应该考虑一下，这个场景是否变动大而且不可复用","，如果是的话，我们可以放心的使用原子类。",[10,372,373],{},"组件应该是“自洽的”，其本身就应该构成了一个“生态圈”，也就是说，他几乎不需要外部供给，自给自足就能够运转下去。",[20,375,377],{"id":376},"实际页面中也应该使用-bem","实际页面中也应该使用 BEM",[10,379,380],{},"在实际页面中也需要用到 BEM 命名方法，不然乱起的一个名字很可能就和某一组件冲突了，导致样式相互覆盖。",[10,382,383,384,387,388,391,392,395],{},"假如我们有联系页面，路径是 ",[52,385,386],{"code":386},"\u002Fpages\u002Fcontact\u002F","。那么该页面的模块名可以是 ",[52,389,390],{"code":390},"page-contact","，其名下元素均以 ",[52,393,394],{"code":394},"page-contact__element-name"," 命名。",[10,397,398],{},"一般来说，实际页面中只是对组件进行调用，对组件的位置进行调整，但不会对组件内部细节进行修改。但实际情况下，同一个组件在不同页面不同模样的情况也是有的，所以会出现在实际页面中对组件样式进行微调的代码：",[78,400,403],{"className":401,"code":402,"language":150,"meta":84},[148],"\u002F* 联系页面对分页按钮进行微调 *\u002F\n.page-contact .page-btn {} \n",[52,404,402],{"__ignoreMap":84},[10,406,407],{},"但更推荐的做法是给分页组件添加一个修饰器，将上面的样式放到修饰器名下，再根据实际情况运用到页面中。",[20,409,411],{"id":410},"webpack-css-loader-解决之道","webpack css-loader 解决之道",[10,413,414],{},"BEM 主要被诟病的一点在于其命名过长，结合 Angular 这种带有标签指令的框架时，整个 HTML 看起来会更混乱：",[78,416,419],{"className":417,"code":418,"language":83,"meta":84},[81],"\u003C!-- 发帖页面 -->\n\u003Cspan ng-repeat=\"post in postData track by post.id\" ng-if=\"$index === 0\" class=\"page-post__post-item\" ng-class=\"{'page-post__post-item--even': $even}\" popover-content=\"\">\u003C\u002Fspan> \n",[52,420,418],{"__ignoreMap":84},[10,422,423],{},"当然，我们可以通过换行来缓解这个问题:",[78,425,428],{"className":426,"code":427,"language":83,"meta":84},[81],"\u003C!-- 发帖页面 -->\n\u003Cspan\n  ng-repeat=\"post in postData track by post.id\"\n  ng-if=\"$index === 0\"\n  class=\"page-post__post-item\"\n  ng-class=\"{'page-post__post-item--even': $even}\"\n  popover-content=\"\"\n>\n\u003C\u002Fspan> \n",[52,429,427],{"__ignoreMap":84},[10,431,432],{},"但其实说穿了，BEM 保证样式不冲突的核心就是：在元素名中加入唯一的标识。这个标识在 BEM 中对应的是模块名，也可能是一个独一无二的乱序字符串。",[10,434,435],{},"为模块中每个元素名加入标识，这可是重复的工作啊，重复的工作就应该交给机器去做。",[10,437,438],{},"webpack 加载器 css-loader，可在 js 中读取 css 样式，自2015年4月份起，该插件加入了 placeholder 功能，使得该插件可以解决 CSS 作用域的问题，原理也就是给元素的名称加入唯一的标识。",[78,440,443],{"className":441,"code":442,"language":150,"meta":84},[148],"\u002F* 分页组件 *\u002F\n:local(.prev) {} \n",[52,444,442],{"__ignoreMap":84},[10,446,447,448,451,452,455],{},"css-loader 加载器自定义的语法：",[52,449,450],{"code":450},":local(.identifier){}"," 向外暴露出选择器 ",[52,453,454],{"code":454},".prev","。在 JS 代码中，我们可以拿到这个选择器：",[78,457,462],{"className":458,"code":460,"language":461,"meta":84},[459],"language-js","import styles from '.\u002Fpage-btn.css';\nvar $prevBtn = $('\u003Cbutton class=\"' + styles.prev + '\">上一页\u003C\u002Fbutton>');\n\u002F\u002F ... \n","js",[52,463,460],{"__ignoreMap":84},[10,465,466,469],{},[52,467,468],{"code":468},"styles.prev"," 返回的是一串独一无二且随机的字符串，该字符串对应着样式文件中的选择器。这名字有悖语义化，但 css-loader 支持配置字符串的生成格式。",{"title":84,"searchDepth":471,"depth":471,"links":472},4,[473,475,476,477,478,479,480],{"id":22,"depth":474,"text":23},2,{"id":137,"depth":474,"text":138},{"id":182,"depth":474,"text":182},{"id":275,"depth":474,"text":276},{"id":343,"depth":474,"text":344},{"id":376,"depth":474,"text":377},{"id":410,"depth":474,"text":411},[482,483],"学习笔记","CSS","2023-07-07","原文戳这里👉：https:\u002F\u002Fbemcss.com",false,"md",null,{"comments":486,"slots":490},{"copyright":491},{"props":492,"type":7,"value":494},{"title":493},"转载说明",[495],[10,496,497,498,502],{},"本文转载自 ",[14,499,501],{"href":16,"rel":500},[18],"原文","，版权及许可按原作者说明。",true,"\u002F2023\u002Fcss\u002Fbemcss",{"text":506,"minutes":507,"time":508,"words":509},"19 min read",18.79,1127400,3758,{"title":5,"description":485},{"loc":504},"posts\u002F2023\u002Fcss\u002Fbemcss",[514],"BEM","tech","bfcVPWVyOdFRaBkvtZs9CZvgdffNGs_RFdoP3YUufho",[518,523],{"title":519,"path":520,"stem":521,"date":522,"type":515,"children":-1},"-webkit-fill-available 关键字","\u002F2023\u002Fcss\u002Fwebkit-fill-available","posts\u002F2023\u002Fcss\u002Fwebkit-fill-available","2023-07-06",{"title":524,"path":525,"stem":526,"date":527,"type":515,"children":-1},"大屏适配方案汇总","\u002F2023\u002Fbig-screen","posts\u002F2023\u002Fbig-screen","2023-07-14",1793699837885]