前端工程师最常见且最具挑战性的问题之一是 CSS 命名约定。随着 Block Element Modifier(BEM)方法的流行,许多人习惯于按照一种可维护的模式组织他们的样式。
即将在 Chrome 浏览器中实施的 @scope 允许在样式表中对样式进行块级作用域划分,从而进一步提高了 BEM 的性能。这将使样式表更易于维护,同时对 CSS 级联进行更严格的控制。
在这里将展示如何在 Chrome 中使用 @scope 特性,以及如何使用它来替换前端项目中的 BEM。通过几个例子进行讲解,可以在 GitHub 上的示例项目中查看并跟随操作。

CSS @scope 是什么?

在即将发布的 Chrome 118 版本中,@scope 特性创建了 CSS 样式的块级作用域。这给了开发者对 CSS 样式更多的控制权,因为现在可以在 CSS 文件中直接为视图的不同部分明确定义作用域。
请看下面的 HTML 示例:

  1. <main className="sample-page">
  2. <h1>With Scope</h1>
  3. <section className="first-section">
  4. <p>some text</p>
  5. <p>
  6. some text and then a <a href="/">back link</a>
  7. </p>
  8. </section>
  9. <section className="second-section">
  10. <h2>Dog Picture</h2>
  11. <div>
  12. <p>second section paragraph text</p>
  13. </div>
  14. <img src={'./DOG_1.jpg'} alt="dog" />
  15. </section>
  16. </main>

在此 HTML 中,可以使用以下方法对 second-section 样式区域内的元素进行样式设置:

  1. .second-section {
  2. display: flex;
  3. flex-direction: column;
  4. border: solid;
  5. padding: 40px;
  6. margin: 20px;
  7. }
  8. @scope (.second-section) {
  9. h2 {
  10. text-align: center;
  11. }
  12. img {
  13. max-width: 400px;
  14. max-height: 100%;
  15. }
  16. div {
  17. display: flex;
  18. justify-content: center;
  19. margin: 20px;
  20. }
  21. p {
  22. max-width: 200px;
  23. text-align: center;
  24. background-color: pink;
  25. color: forestgreen;
  26. padding: 10px;
  27. border-radius: 20px;
  28. font-size: 24px;
  29. }
  30. }

使用 @scope 时,还可以创建一个 “甜甜圈 “作用域,为一组样式及其中的元素定义起始和结束部分。使用上述相同的 HTML,甜甜圈作用域可以定义从 sample-page 的起始区域到 second-section 样式区域的样式:

  1. /* donut scope */
  2. @scope (.sample-page) to (.second-section) {
  3. p {
  4. font-size: 24px;
  5. background-color: forestgreen;
  6. color: pink;
  7. text-align: center;
  8. padding: 10px;
  9. }
  10. a {
  11. color: red;
  12. font-size: 28px;
  13. text-transform: uppercase;
  14. }
  15. }

最棒的是,它的功能与使用 BEM 造型非常相似,但代码量更少。

浏览器支持 CSS @scope

截至2023年10月2日,CSS @scope 还未正式发布,因此需要开启实验性网络功能标志来使用它。要做到这一点,首先在 Chrome 中打开一个标签页,前往 chrome://flags/,然后搜索并启用“实验性网络平台功能”标志:

什么是 BEM

BEM 是一种在HTML视图中分组样式的方式,可以轻松地进行导航。
考虑到一个大型 HTML 页面有许多具有不同样式的元素。在设置了几个初始样式名称后,随着页面的扩展,要保持样式就会变得很困难。BEM 试图通过围绕实际样式来构建样式名来缓解这一问题。
block 是一个包含 HTML 元素。考虑一下类似这样的 HTML

  1. <main className="sample-page">
  2. <h1 className="sample-page__title">With BEM</h1>
  3. <section className="sample-page__first-section">
  4. <p className="sample-page__first-section--first_line">
  5. some text
  6. </p>
  7. <p className="sample-page__first-section--second-line">
  8. some text and then a{' '}
  9. <a
  10. className="sample-page__first-section--second-line-link"
  11. href="/"
  12. >
  13. back link
  14. </a>
  15. </p>
  16. </section>
  17. </main>

在此 HTML 中

  • 块 = sample-page 样式就是 block 样式,因为它包裹了一组元素
  • 元素 = 在为<h1> 元素设计样式时,该元素被视为 element ,因此会在样式名称中添加额外的 ,从而创建 sample-pagetitle 。 sample-page__first-section 也是如此。

修改器 = 当在 <section> 元素中为

元素设计样式时,样式名称会多出一个 —first-line ,从而创建 sample-page__first-section—first-line ,所以:

  • 块为 sample-page
  • 元素是 first-section
  • 修饰词是 first-line

BEM 的扩展性很好,尤其是在使用 SASS 将样式分组并使用 & 操作符创建类似内容时:

  1. .sample-page {
  2. display: flex;
  3. flex-direction: column;
  4. margin-top: 10px;
  5. &__title {
  6. font-size: 48px;
  7. color: forestgreen;
  8. }
  9. &__first-section {
  10. font-size: 24px;
  11. border: solid;
  12. padding: 40px;
  13. margin: 20px;
  14. &--first-line{
  15. font-size: 24px;
  16. background-color: forestgreen;
  17. color: pink;
  18. text-align: center;
  19. padding: 10px;
  20. }
  21. }
  22. }

难点在于,在一个大型项目中,这会产生非常大的 CSS 或 SASS 文件,而这些文件仍然很难进行大规模管理。可以使用 @scope 替换 BEM 样式,使样式定义更小、更易于管理。

使用@scope重构BEM

展示使用 @scope 的优势的最佳方式是在使用 React 等主流框架或库的应用程序中使用 @scope。在 GitHub 上的示例应用程序中, react-example 文件夹中有一个项目,其中的页面首先使用 BEM 进行了样式设计,然后使用 @scope 进行了重构。
可以运行应用程序并单击 WithBEM 或 WithScope 按钮来查看具体实现。组件和样式表都有相应的名称,前缀为 WithBEM 或 WithScope ,分别位于 pages 和 styles 文件夹中。
从 BEM 样式组件 WithBEMPage.tsx 开始,首先看到了用 BEM 方法设计的 HTML 样式:

  1. <main className="sample-page">
  2. <h1 className="sample-page__title">With BEM</h1>
  3. <section className="sample-page__first-section">
  4. <p className="sample-page__first-section--first_line">
  5. some text
  6. </p>
  7. <p className="sample-page__first-section--second-line">
  8. some text and then a{' '}
  9. <a
  10. className="sample-page__first-section--second-line-link"
  11. href="/"
  12. >
  13. back link
  14. </a>
  15. </p>
  16. </section>
  17. <section className="sample-page__second-section">
  18. <h2 className="sample-page__second-section--title">
  19. Dog Picture
  20. </h2>
  21. <div className="sample-page__second-section--div">
  22. <p className="sample-page__second-section--div-paragraph">
  23. second section paragraph text
  24. </p>
  25. </div>
  26. <img
  27. className="sample-page__second-section--image"
  28. src={'./DOG_1.jpg'}
  29. alt="dog"
  30. />
  31. </section>
  32. </main>

在组件 WithScopePage.tsx 中,可以通过以下内容看到重构是多么干净利落:

  1. <main className="sample-page">
  2. <h1>With Scope</h1>
  3. <section className="first-section">
  4. <p>some text</p>
  5. <p>
  6. some text and then a <a href="/">back link</a>
  7. </p>
  8. </section>
  9. <section className="second-section">
  10. <h2>Dog Picture</h2>
  11. <div>
  12. <p>second section paragraph text</p>
  13. </div>
  14. <img src={'./DOG_1.jpg'} alt="dog" />
  15. </section>
  16. </main>

要将 BEM 重构为 @scope ,只需找到样式组,然后适当添加作用域样式。先来看看标题部分。在原始的 WithBEMPage.tsx 文件中,每个部分都定义了不同的样式。而在 @scope 版本中,则为特定元素定义了更简洁的样式:

  1. .sample-page {
  2. display: flex;
  3. flex-direction: column;
  4. margin-top: 10px;
  5. }
  6. /* replaced */
  7. /* .sample-page__title {
  8. font-size: 48px;
  9. color: forestgreen;
  10. } */
  11. /* donut scope */
  12. @scope (.sample-page) to (.first-section) {
  13. h1 {
  14. font-size: 48px;
  15. color: forestgreen;
  16. }
  17. }

同样,在第一部分内容中,原始 BEM 风格如下:

  1. .sample-page__first-section {
  2. font-size: 24px;
  3. border: solid;
  4. padding: 40px;
  5. margin: 20px;
  6. }
  7. .sample-page__first-section--first_line {
  8. font-size: 24px;
  9. background-color: forestgreen;
  10. color: pink;
  11. text-align: center;
  12. padding: 10px;
  13. }
  14. .sample-page__first-section--second-line {
  15. font-size: 24px;
  16. background-color: forestgreen;
  17. color: pink;
  18. text-align: center;
  19. padding: 10px;
  20. }
  21. .sample-page__first-section--second-line-link {
  22. color: red;
  23. font-size: 28px;
  24. text-transform: uppercase;
  25. }

@scope 重构第一部分,现在就有了一个更简洁的样式定义:

  1. .first-section {
  2. font-size: 24px;
  3. border: solid;
  4. padding: 40px;
  5. margin: 20px;
  6. }
  7. /* donut scope */
  8. @scope (.sample-page) to (.second-section) {
  9. p {
  10. font-size: 24px;
  11. background-color: forestgreen;
  12. color: pink;
  13. text-align: center;
  14. padding: 10px;
  15. }
  16. a {
  17. color: red;
  18. font-size: 28px;
  19. text-transform: uppercase;
  20. }
  21. }

这样做的另一个好处是,HTML 视图更小,更容易阅读。考虑到之前

  1. <section className="sample-page__first-section">
  2. <p className="sample-page__first-section--first_line">
  3. some text
  4. </p>
  5. <p className="sample-page__first-section--second-line">
  6. some text and then a{' '}
  7. <a
  8. className="sample-page__first-section--second-line-link"
  9. href="/"
  10. >
  11. back link
  12. </a>
  13. </p>
  14. </section>

然后

  1. <section className="first-section">
  2. <p>some text</p>
  3. <p>
  4. some text and then a <a href="/">back link</a>
  5. </p>
  6. </section>

通过这两个示例组件,可以对每个部分进行重构。最终注意到它是如何使样式更简洁、更易读的。

@scope 与 BEM 相比的其他优势

除了将 BEM 重构为 @scope 的优势外,使用 @scope 还可以更好地控制 CSS 级联。CSS 级联是一种算法,它定义了网络浏览器如何处理组成 HTML 页面上元素的样式条件。
在处理任何前端项目时,开发者可能需要处理由于样式层叠而产生的奇怪结果。通过使用@scope,可以通过紧密限定元素范围来控制层叠的副作用。
文件 no_scope.html 的样式和一些元素定义如下:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>Plain HTML</title>
  5. <style>
  6. .light {
  7. background: #ccc;
  8. }
  9. .dark {
  10. background: #333;
  11. }
  12. .light a {
  13. color: red;
  14. }
  15. .dark a {
  16. color: yellow;
  17. }
  18. div {
  19. padding: 2rem;
  20. }
  21. div > div {
  22. margin: 0 0 0 2rem;
  23. }
  24. p {
  25. margin: 0 0 2rem 0;
  26. }
  27. </style>
  28. </head>
  29. <body>
  30. <div class="light">
  31. <p><a href="#">First Level</a></p>
  32. <div class="dark">
  33. <p><a href="#">Second Level</a></p>
  34. <div class="light">
  35. <p><a href="#">Third Level</a></p>
  36. </div>
  37. </div>
  38. </div>
  39. </body>
  40. </html>

结果如下:
CSS @scope 如何取代 BEM - 图1
这里的问题是,根据已定义的 CSS, Third Level 应为红色文本,而不是黄色。这是 CSS 级联的副作用,因为页面样式是根据外观顺序来解释的,因此 Third Level 被认为是黄色而不是红色。通过 Bram.us 原文中的图表,可以看到 CSS 级联评估选择器和样式的顺序:
CSS @scope 如何取代 BEM - 图2
如果不使用 @scope ,CSS 级联将直接从 “特定性 “转为 “外观顺序”。使用 @scope 后,CSS 级联将首先考虑 @scope 元素。可以通过在示例中为 .light 和 .dark 样式添加 @scope 来了解其效果。
首先,将原始 HTML 和 CSS 修改如下:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>Plain HTML</title>
  5. <style>
  6. .light {
  7. background: #ccc;
  8. }
  9. .dark {
  10. background: #333;
  11. }
  12. div {
  13. padding: 2rem;
  14. }
  15. div > div {
  16. margin: 0 0 0 2rem;
  17. }
  18. p {
  19. margin: 0 0 2rem 0;
  20. }
  21. @scope (.light) {
  22. :scope {
  23. background: white;
  24. }
  25. a {
  26. color: red;
  27. }
  28. }
  29. @scope (.dark) {
  30. :scope {
  31. background: black;
  32. }
  33. a {
  34. color: yellow;
  35. }
  36. }
  37. </style>
  38. </head>
  39. <body>
  40. <div class="light">
  41. <p><a href="#">First Level</a></p>
  42. <div class="dark">
  43. <p><a href="#">Second Level</a></p>
  44. <div class="light">
  45. <p><a href="#">Third Level</a></p>
  46. </div>
  47. </div>
  48. </div>
  49. </body>
  50. </html>

输出结果如下
CSS @scope 如何取代 BEM - 图3

总结

在本文中,探讨了将 BEM 风格应用程序重构为使用 Chrome 浏览器中新推出的 @scope 功能的方法。介绍了 @scope 的工作原理,然后将一个简单的页面从 BEM 重构为 @scope
新的 @scope 功能有可能成为前端开发人员的一大优势。不过,其他浏览器也必须实现支持,这可能需要时间。在此之前,这绝对是一个有趣的功能,对前端项目的样式设计可能会有很大帮助。