##CSS通用類(lèi)和“關(guān)注點(diǎn)分離”

CSS通用類(lèi)和“關(guān)注點(diǎn)分離” - 眾成翻譯
http://www.zcfy.cc/article/css-utility-classes-and-quote-separation-of-concerns-quote-4149.html

CSS通用類(lèi)和“關(guān)注點(diǎn)分離”

過(guò)去的幾年里,我編寫(xiě)CSS的方式已經(jīng)從“語(yǔ)義化”轉(zhuǎn)變?yōu)椤昂瘮?shù)式”(經(jīng)常被這樣稱呼)了。

用“函數(shù)式”方式編寫(xiě)css可以使許多開(kāi)發(fā)者的內(nèi)心激動(dòng)起來(lái)。所以我想介紹一下我是如何做到的,并且和大家分享一些經(jīng)驗(yàn)和見(jiàn)解。

第 1 階段: "語(yǔ)義化" CSS

當(dāng)你正努力學(xué)習(xí)如何把CSS寫(xiě)的更好地時(shí)候,會(huì)有人告訴你最好的方法是“關(guān)注點(diǎn)分離”。

這種方式的主旨是,你的HTML只能包含與內(nèi)容相關(guān)的信息,所有與樣式相關(guān)的信息都應(yīng)寫(xiě)在CSS里。

看下面這段HTML:

<p>
    Hello there!
</p>

看到.text-center類(lèi)了嗎?文本居中屬于樣式,因此這段代碼違背了“關(guān)注點(diǎn)分離”原則,因?yàn)槲覀儼褬邮叫畔⒒烊際TML了。

那么推薦的方法是,根據(jù)內(nèi)容來(lái)給元素定義類(lèi)名。然后這些類(lèi)名就如同一個(gè)鉤子,你可以在CSS中用這些類(lèi)名給標(biāo)簽增加樣式:

&lt;style&gt;
.greeting {
    text-align: center;
}
&lt;/style&gt;

<p>
    Hello there!
</p>

這種方法的典范是禪意花園。如果使用“關(guān)注點(diǎn)分離”原則,那么想要改變一個(gè)網(wǎng)站的樣式只需要更換樣式表就可以了。

我寫(xiě)碼的過(guò)程大致如下:

  1. 根據(jù)設(shè)計(jì)稿把需要的標(biāo)簽寫(xiě)好(這里拿一個(gè)作者簡(jiǎn)介卡作為例子):
    <div>
      <img src="http://p0.qhimg.com/t01e2c15da7b6fa9aba.jpg" alt>
      <div>
        <h2>Adam Wathan</h2>
        <p>
          Adam is a rad dude who likes TDD, Active Record, and garlic bread with cheese. He also hosts a decent podcast and has never had a really great haircut.
        </p>
      </div>
    </div>

  1. 根據(jù)內(nèi)容添加一兩個(gè)具有描述性的類(lèi)名:
    - <div>
    + <div>
        <img src="http://p0.qhimg.com/t01e2c15da7b6fa9aba.jpg" alt>
        <div>
          <h2>Adam Wathan</h2>
          <p>
            Adam is a rad dude who likes TDD, Active Record, and garlic bread with cheese. He also hosts a decent podcast and has never had a really great haircut.
          </p>
        </div>
      </div>

  1. 將這些類(lèi)名作為“鉤子”,在我們的CSS/Less/Sass中給新標(biāo)簽添加樣式:
    .author-bio {
      background-color: white;
      border: 1px solid hsl(0,0%,85%);
      border-radius: 4px;
      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
      overflow: hidden;
      &gt; img {
        display: block;
        width: 100%;
        height: auto;
      }
      &gt; div {
        padding: 1rem;
        &gt; h2 {
          font-size: 1.25rem;
          color: rgba(0,0,0,0.8);
        }
        &gt; p {
          font-size: 1rem;
          color: rgba(0,0,0,0.75);
          line-height: 1.5;
        }
      }
    }

下面是結(jié)果演示:

See the Pen

"Semantic" mapping layer (terrible idea!)

by Adam Wathan (@adamwathan) on

CodePen.

我認(rèn)為這很有道理,所以很長(zhǎng)一段時(shí)間都是這么寫(xiě)HTML和CSS的。

但后來(lái),我感覺(jué)有點(diǎn)兒不對(duì)勁。

雖然我將“關(guān)注點(diǎn)分離”了,但HTML和CSS還是有很明顯的耦合。大多數(shù)時(shí)候我的CSS看起來(lái)就像是HTML標(biāo)簽的鏡子,嵌套的CSS選擇器將HTML結(jié)構(gòu)完全映射出來(lái)了。

我的標(biāo)簽確實(shí)與樣式分離了,但我的CSS卻與HTML結(jié)構(gòu)有很強(qiáng)的聯(lián)系。

也許是我的分離做的不夠徹底。

第 2 階段: 讓樣式與結(jié)構(gòu)解耦

在尋找解決辦法的過(guò)程中,我發(fā)現(xiàn)大家更傾向于給標(biāo)簽添加更多的類(lèi)名,這樣定義起來(lái)就會(huì)更直觀。能夠保持較低的優(yōu)先級(jí),并使CSS更少的依賴DOM結(jié)構(gòu)。

倡導(dǎo)這種思想并且最廣為人知的技術(shù)是

Block Element Modifer, 簡(jiǎn)稱

BEM.

用類(lèi)似BEM的方法,我們的作者簡(jiǎn)介標(biāo)簽看上去就會(huì)是這樣:

<div>
  <img src="http://p0.qhimg.com/t01e2c15da7b6fa9aba.jpg" alt>
  <div>
    <h2>Adam Wathan</h2>
    <p>
      Adam is a rad dude who likes TDD, Active Record, and garlic bread with cheese. He also hosts a decent podcast and has never had a really great haircut.
    </p>
  </div>
</div>

...CSS是這樣:

.author-bio {
  background-color: white;
  border: 1px solid hsl(0,0%,85%);
  border-radius: 4px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  overflow: hidden;
}
.author-bio__image {
  display: block;
  width: 100%;
  height: auto;
}
.author-bio__content {
  padding: 1rem;
}
.author-bio__name {
  font-size: 1.25rem;
  color: rgba(0,0,0,0.8);
}
.author-bio__body {
  font-size: 1rem;
  color: rgba(0,0,0,0.75);
  line-height: 1.5;
}

View on CodePen

對(duì)我來(lái)說(shuō)這已經(jīng)是很大的進(jìn)步了。我的標(biāo)簽看上去仍然很“語(yǔ)義化”且與樣式分離,而且現(xiàn)在CSS也與標(biāo)簽結(jié)構(gòu)解耦合了。額外的好處是避免了不必要的選擇器嵌套,保持了低優(yōu)先級(jí)。

但是很快我又陷入了兩難境地。

處理相似的組件

話說(shuō)我需要給網(wǎng)站加個(gè)新的內(nèi)容:用一個(gè)卡片布局來(lái)展示文章預(yù)覽。

話說(shuō)這個(gè)文章預(yù)覽卡片的頭部是一張照片,下邊是內(nèi)容部分,包括一個(gè)粗體標(biāo)題和一些較小的正文文本。

話說(shuō)它看起來(lái)跟作者簡(jiǎn)介非常像。

[圖片上傳失敗...(image-f0f4c9-1513407635278)]

如果用“關(guān)注點(diǎn)分離”的原則,如何處理最好呢?

我們不能將

.author-bio的類(lèi)名用在文章預(yù)覽卡中,因?yàn)檫@不符合語(yǔ)義化原則。所以我們肯定要用

.article-preview來(lái)給組件命名。

下邊將是標(biāo)簽的樣子:

<div>
  <img src="http://p0.qhimg.com/t01f3a22fad3306d5cf.webp" alt>
  <div>
    <h2>Stubbing Eloquent Relations for Faster Tests</h2>
    <p>
      In this quick blog post and screencast, I share a trick I use to speed up tests that use Eloquent relationships but don't really depend on database functionality.
    </p>
  </div>
</div>

但是我們應(yīng)該如何處理CSS呢?

選擇 1: 復(fù)制樣式

一種方法是直接復(fù)制.author-bio的樣式,然后重命名一下:

.article-preview {
  background-color: white;
  border: 1px solid hsl(0,0%,85%);
  border-radius: 4px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  overflow: hidden;
}
.article-preview__image {
  display: block;
  width: 100%;
  height: auto;
}
.article-preview__content {
  padding: 1rem;
}
.article-preview__title {
  font-size: 1.25rem;
  color: rgba(0,0,0,0.8);
}
.article-preview__body {
  font-size: 1rem;
  color: rgba(0,0,0,0.75);
  line-height: 1.5;
}

這樣沒(méi)問(wèn)題,但很明顯不夠簡(jiǎn)潔。如果這個(gè)組件在樣式上與.author-bio只有稍微的不一樣(可能是不同的填充或字體顏色),改起來(lái)也會(huì)非常容易。

選擇 2:用

@extend

拓展作者簡(jiǎn)介卡

另一種選擇是使用預(yù)處理器的

@extend屬性。使你可以借用已經(jīng)在

.author-bio

組件定義好的樣式:

.article-preview {
  @extend .author-bio;
}
.article-preview__image {
  @extend .author-bio__image;
}
.article-preview__content {
  @extend .author-bio__content;
}
.article-preview__title {
  @extend .author-bio__name;
}
.article-preview__body {
  @extend .author-bio__body;
}

View on CodePen

一般不建議使用

@extend屬性。但撇開(kāi)這件事, 這樣可以解決我們的問(wèn)題對(duì)嗎?

我們移除了CSS中重復(fù)的部分,并且標(biāo)簽與樣式仍然是分離的。

但是讓我們?cè)倏匆粋€(gè)選項(xiàng)...

Option 3: 創(chuàng)建與內(nèi)容無(wú)關(guān)的組件

從“語(yǔ)義化”角度來(lái)看,.author-bio

.article-preview

組件并沒(méi)有共同處。一個(gè)是作者簡(jiǎn)介卡, 一個(gè)是文章預(yù)覽卡。

但是正如我們已經(jīng)看到的,他們從設(shè)計(jì)的角度來(lái)看有很多共同點(diǎn)。

因此,我們可以這樣做。創(chuàng)建一個(gè)新的組件,根據(jù)他們的共同點(diǎn)起類(lèi)名,然后在兩個(gè)內(nèi)容中復(fù)用。

我們給它起名叫

.media-card。

下面是CSS:

.media-card {
  background-color: white;
  border: 1px solid hsl(0,0%,85%);
  border-radius: 4px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  overflow: hidden;
}
.media-card__image {
  display: block;
  width: 100%;
  height: auto;
}
.media-card__content {
  padding: 1rem;
}
.media-card__title {
  font-size: 1.25rem;
  color: rgba(0,0,0,0.8);
}
.media-card__body {
  font-size: 1rem;
  color: rgba(0,0,0,0.75);
  line-height: 1.5;
}

...我們的作者簡(jiǎn)介標(biāo)簽看起來(lái)會(huì)像下邊這樣:

<div>
  <img src="http://p0.qhimg.com/t01e2c15da7b6fa9aba.jpg" alt>
  <div>
    <h2>Adam Wathan</h2>
    <p>
      Adam is a rad dude who likes TDD, Active Record, and garlic bread with cheese. He also hosts a decent podcast and has never had a really great haircut.
    </p>
  </div>
</div>

...我們的文章預(yù)覽看起來(lái)會(huì)像下邊這樣:

<div>
  <img src="http://p0.qhimg.com/t01f3a22fad3306d5cf.webp" alt>
  <div>
    <h2>Stubbing Eloquent Relations for Faster Tests</h2>
    <p>
      In this quick blog post and screencast, I share a trick I use to speed up tests that use Eloquent relationships but don't really depend on database functionality.
    </p>
  </div>
</div>

這種方法也不會(huì)有重復(fù)的CSS,但現(xiàn)在是不是“結(jié)構(gòu)與樣式混合”了?

我們希望兩個(gè)文本片段都使用.media-card做樣式。那如果我們想改變作者簡(jiǎn)介卡的樣式,而不改變文章預(yù)覽的,那該怎么辦呢?

之前,我們只需要打開(kāi)樣式表,給需要改變的組件隨便添加樣式就可以了。但現(xiàn)在我們需要編輯HTML!這很糟!

但讓我們先花幾分鐘換個(gè)角度想一想。

如果我們要添加一個(gè)有著相同樣式的新內(nèi)容,該怎么辦呢?

如果用“語(yǔ)義化”方法,我們需要寫(xiě)一段新的HTML,添加一些根據(jù)內(nèi)容起的類(lèi)名,打開(kāi)樣式表,為新的內(nèi)容添加一個(gè)新的CSS樣式組件,并通過(guò)復(fù)制、@extend、mixin來(lái)借用已經(jīng)存在的樣式。

如果用與內(nèi)容無(wú)關(guān)的

.media-card

類(lèi)名, 我們所需要做的只是寫(xiě)一段新的HTML,不需要修改樣式表。

如果我們真的將“結(jié)構(gòu)與樣式混合”了,那么無(wú)論HTML還是CSS,不都得修改嗎? (譯者:作者想表達(dá)的是,其實(shí)結(jié)構(gòu)與樣式還是分離的。)

“關(guān)注點(diǎn)分離”是個(gè)稻草人

當(dāng)你用"關(guān)注點(diǎn)分離"的原則來(lái)思考HTML和CSS的關(guān)系時(shí),就會(huì)是非黑即白的。

分離了(好!)或者沒(méi)分離(糟糕?。?/p>

這并不是思考HTML與CSS關(guān)系的正確方式。

相反,

要從依賴的角度來(lái)思考

有兩種編寫(xiě)HTML和CSS方式:

  1. "關(guān)注點(diǎn)分離"

    CSS依賴HTML。

根據(jù)內(nèi)容給類(lèi)起名(例如

.author-bio),將CSS視為HTML的附屬品。

HTML是獨(dú)立的。他并不在乎你是怎么定義它的,它只暴露像.author-bio這樣的鉤子。

另一方面,你的CSS并不是獨(dú)立的。他需要知道你的HTML暴露了哪些類(lèi)名,然后通過(guò)這些類(lèi)名給HTML添加樣式。

在這個(gè)模型中,你可以任意編寫(xiě)HTML,但CSS卻不能被復(fù)用。

  1. "結(jié)構(gòu)與樣式混合"

    HTML依賴CSS

根據(jù)設(shè)計(jì)稿提煉出樣式相同的部分,并用與內(nèi)容無(wú)關(guān)的名字作為類(lèi)名,就是將HTML作為CSS的附屬品。

CSS是獨(dú)立的。它并不關(guān)注自己被應(yīng)用的地方內(nèi)容是什么。他只是提供一系列代碼塊,哪個(gè)標(biāo)簽需要就添加在哪。

HTML不是獨(dú)立的。它需要使用由CSS提供的類(lèi), 它需要知道有哪些類(lèi)是定義好的,并且將這些類(lèi)結(jié)合起來(lái),來(lái)完成跟設(shè)計(jì)稿一樣的樣式。

在這個(gè)模型中,你的CSS是可復(fù)用的,但你的HTML不能隨意編寫(xiě)。

CSS禪意花園采用第一種方式,而UI框架如Bootstrap

Bulma

則用的第二種方式。

兩種方法本質(zhì)上都不是“錯(cuò)誤的”。只是你要弄清楚你所在的語(yǔ)境中,什么才是最重要的?;谶@點(diǎn),再?zèng)Q定你要用哪種方法。

對(duì)于你正在編寫(xiě)的項(xiàng)目, 什么會(huì)更有價(jià)值: 可以隨意編寫(xiě)的HTML,還是可復(fù)用的CSS?

選擇可重用性

當(dāng)我閱讀了Nicolas Gallagher的

關(guān)于HTML語(yǔ)義化和前端架構(gòu)一文后,我改變了自己的觀點(diǎn)。

我不會(huì)在這里重申他的所有觀點(diǎn)。但毫無(wú)疑問(wèn),這個(gè)博客給了我深刻印象。我完全相信,對(duì)于我所編寫(xiě)的各種項(xiàng)目來(lái)說(shuō),選擇可復(fù)用的CSS是最正確的選擇。

第3階段: 與內(nèi)容無(wú)關(guān)的CSS組件

此時(shí)我的目標(biāo)很清楚,就是避免根據(jù)內(nèi)容來(lái)創(chuàng)建類(lèi)名。而是盡可能使起的類(lèi)名復(fù)用性更高。

比如類(lèi)名看上去如下:

  • .card

  • .btn,

    .btn--primary,

    .btn--secondary

  • .badge

  • .card-list,

    .card-list-item

  • .img--round

  • .modal-form,

    .modal-form-section

...等等等等.

當(dāng)我開(kāi)始專(zhuān)注于創(chuàng)建可復(fù)用的類(lèi)名時(shí),我注意到一些事情:

組件做的事情越多,組件細(xì)節(jié)越多,越難以服用。

下面是一個(gè)直觀的例子。

話說(shuō)我們要建立一個(gè)表單,包括幾個(gè)表單部分和一個(gè)在底部的提交按鈕。

如果我們將所有表單內(nèi)容都視為.stacked-form

組件的一部分,我們就會(huì)把按鈕命名成

.stacked-form__button

&lt;form class="stacked-form" action="#"&gt;
  <div>

  </div>
  <div>

  </div>
  <div>
    &lt;button class="stacked-form__button"&gt;Submit&lt;/button&gt;
  </div>
&lt;/form&gt;

但也許在我們的網(wǎng)站中還有另外一個(gè)按鈕,它并不是表單的一部分,但是卻跟表單按鈕看上去一樣。

給這個(gè)按鈕使用

.stacked-form__button

類(lèi)名就不太合適了,它不是.stacked-form組件的一部分。

這兩個(gè)按鈕在各自的頁(yè)面上都是主要行為,那么如果我們根據(jù)組件的共同特征來(lái)給按鈕命名的話,就是.btn--primary,完全去掉

.stacked-form__

前綴。

 &lt;form class="stacked-form" action="#"&gt;

    <div>
-     &lt;button class="stacked-form__button"&gt;Submit&lt;/button&gt;
+     &lt;button class="btn btn--primary"&gt;Submit&lt;/button&gt;
    </div>
  &lt;/form&gt;

話說(shuō)現(xiàn)在我們想讓.stacked-form組件跟前文中的作者簡(jiǎn)介、文章預(yù)覽一樣,使用卡片樣式的布局。

一種方法是創(chuàng)建一個(gè)修飾符并將其應(yīng)用于此表單:

- &lt;form class="stacked-form" action="#"&gt;
+ &lt;form class="stacked-form stacked-form--card" action="#"&gt;

  &lt;/form&gt;

但是如果我們已經(jīng)有了.card類(lèi),那我們?yōu)楹尾挥靡呀?jīng)存在的.card.stacked-form組合起來(lái),實(shí)現(xiàn)設(shè)計(jì)稿的樣式呢?

+ <div>
    &lt;form class="stacked-form" action="#"&gt;

    &lt;/form&gt;
+ </div>

用這個(gè)方法,我們可以用.card

類(lèi)作為任何內(nèi)容的容器,然后.stacked-form類(lèi)則可以用在任何容器內(nèi)。

我們已經(jīng)從組件中提取出了更多的可復(fù)用部分,

并且我們不需要編寫(xiě)任何新的CSS

構(gòu)建子組件

話說(shuō)我們需要在表單的底部添加另一個(gè)按鈕,并且和之前的按鈕之間有一點(diǎn)兒距離:

&lt;form class="stacked-form" action="#"&gt;

  <div>
    &lt;button class="btn btn--secondary"&gt;Cancel&lt;/button&gt;

    &lt;button class="btn btn--primary"&gt;Submit&lt;/button&gt;
  </div>
&lt;/form&gt;

一種方法是創(chuàng)建一個(gè)新的子組件,例如.stacked-form__footer,為每個(gè)按鈕添加一個(gè)類(lèi),例如.stacked-form__footer-item,并使用子選擇器添加一些邊距:

 &lt;form class="stacked-form" action="#"&gt;

-   <div>
+   <div>
-     &lt;button class="btn btn--secondary"&gt;Cancel&lt;/button&gt;
-     &lt;button class="btn btn--primary"&gt;Submit&lt;/button&gt;
+     &lt;button class="stacked-form__footer-item btn btn--secondary"&gt;Cancel&lt;/button&gt;
+     &lt;button class="stacked-form__footer-item btn btn--primary"&gt;Submit&lt;/button&gt;
    </div>
  &lt;/form&gt;

CSS看上去就像下邊這樣:

.stacked-form__footer {
  text-align: right;
}
.stacked-form__footer-item {
  margin-right: 1rem;
  &:last-child {
    margin-right: 0;
  }
}

那如果我們?cè)谧訉?dǎo)航或者h(yuǎn)eader中遇到相同的問(wèn)題時(shí),怎么辦?

我們不能在.stacked-form組件之外復(fù)用.stacked-form__footer, 因此也許我們會(huì)在header里新建一個(gè)新的子組件:

 <header>
    <h2>New Product</h2>
+   <div>
+     &lt;button class="header-bar__action btn btn--secondary"&gt;Cancel&lt;/button&gt;
+     &lt;button class="header-bar__action btn btn--primary"&gt;Save&lt;/button&gt;
+   </div>
  </header>

...但現(xiàn)在我們不得不把已經(jīng)寫(xiě)好的.stacked-form__footer類(lèi),復(fù)制到新的.header-bar__actions子組件中。

這非常像剛開(kāi)始我們根據(jù)內(nèi)容來(lái)給類(lèi)命名時(shí)遇到的問(wèn)題,對(duì)不對(duì)?

解決這個(gè)問(wèn)題的一個(gè)方法是,想出一個(gè)全新的組件,這個(gè)組件易于復(fù)用,并且易于組合。

例如我們可以給它起名

.actions-list:

.actions-list {
  text-align: right;
}
.actions-list__item {
  margin-right: 1rem;
  &:last-child {
    margin-right: 0;
  }
}

現(xiàn)在我們可以完全擺脫

.stacked-form__footer

.header-bar__actions

子組件, 而是用.actions-list

在各自的場(chǎng)景中替換他們:


&lt;form class="stacked-form" action="#"&gt;

  <div>
    <div>
      &lt;button class="actions-list__item btn btn--secondary"&gt;Cancel&lt;/button&gt;
      &lt;button class="actions-list__item btn btn--primary"&gt;Submit&lt;/button&gt;
    </div>
  </div>
&lt;/form&gt;

<header>
  <h2>New Product</h2>
  <div>
    &lt;button class="actions-list__item btn btn--secondary"&gt;Cancel&lt;/button&gt;
    &lt;button class="actions-list__item btn btn--primary"&gt;Save&lt;/button&gt;
  </div>
</header>

但如果其中一個(gè).actions-list支持左對(duì)齊,而另一個(gè)支持右對(duì)齊,我們應(yīng)該怎么辦呢?難道要用.actions-list--left

.actions-list--right

來(lái)作修飾嗎?

第4階段: 內(nèi)容無(wú)關(guān)組件 + 通用類(lèi)

給這些組件起名字總是很耗費(fèi)時(shí)間。

當(dāng)你創(chuàng)建例如.actions-list--left這樣的修飾類(lèi)名時(shí), 你完全就是在創(chuàng)建一個(gè)新的組件,這個(gè)組件只是為了適配一個(gè)CSS屬性。 因?yàn)轭?lèi)名里已經(jīng)有了left,因此你不可能欺騙任何人說(shuō)這是“語(yǔ)義化”的(譯者:作者的意思是結(jié)構(gòu)與樣式在這里是混合的)。

如果我們有一個(gè)組件需要使用left-align和right-align來(lái)修飾,那我們用這個(gè)當(dāng)做新的組件類(lèi)名來(lái)用,合適嗎?

這個(gè)就回到了當(dāng)初我們面臨的一個(gè)相同問(wèn)題,那個(gè)問(wèn)題我們是用.actions-list子組件替代

.stacked-form__footer

.header-bar__actions來(lái)解決的:

我們傾向于組合,而不是復(fù)制

那么如果我有兩個(gè)地方用到.actions-list組件,一個(gè)需要左對(duì)齊,而另一個(gè)需要右對(duì)齊,那我們?cè)趺从媒M合的方式來(lái)解決這個(gè)問(wèn)題呢?

創(chuàng)建好通用類(lèi)

要用組合的方法解決這個(gè)問(wèn)題,我們就需要給我們的組件添加一個(gè)新的可復(fù)用的類(lèi)名,以便達(dá)到我們想要的效果:

我們已經(jīng)想用

.actions-list--left

.actions-list--right來(lái)當(dāng)做類(lèi)名了,那么給這些新的類(lèi)起名為.align-left

.align-right也是合情合理的:

.align-left {
  text-align: left;
}
.align-right {
  text-align: right;
}

現(xiàn)在我們可以用組合的方式使我們的表單按鈕左對(duì)齊了:

&lt;form class="stacked-form" action="#"&gt;

  <div>
    <div>
      &lt;button class="actions-list__item btn btn--secondary"&gt;Cancel&lt;/button&gt;
      &lt;button class="actions-list__item btn btn--primary"&gt;Submit&lt;/button&gt;
    </div>
  </div>
&lt;/form&gt;

...在header標(biāo)簽里的按鈕右對(duì)齊:

<header>
  <h2>New Product</h2>
  <div>
    &lt;button class="actions-list__item btn btn--secondary"&gt;Cancel&lt;/button&gt;
    &lt;button class="actions-list__item btn btn--primary"&gt;Save&lt;/button&gt;
  </div>
</header>

不要害怕

如果在HTML的類(lèi)名中見(jiàn)到"left" 和 "right"會(huì)使你感到不舒服,請(qǐng)記住,我們已經(jīng)不是在使用“關(guān)注點(diǎn)分離”方法給類(lèi)命名了,我們是根據(jù)設(shè)計(jì)稿提煉出通用的部分來(lái)給類(lèi)命名的。所有類(lèi)名中帶有樣式的描述是很自然的。

不要在假裝.stacked-form.align-right更“語(yǔ)義化”了。他們都是在決定如何給標(biāo)簽添加樣式之后命名的。我們?cè)跇?biāo)簽中使用這些類(lèi)名是為了達(dá)到特殊的樣式效果。

我們正在寫(xiě)依賴于CSS的HTML。如果我們想把類(lèi)名從.stacked-form改為.horizontal-form的話,那我們就是在改變標(biāo)簽,而不是CSS。

刪除無(wú)用的抽象

解決了這個(gè)問(wèn)題后,一件有趣的事情出現(xiàn)了?,F(xiàn)在我們的.actions-list組件基本上沒(méi)用了。它之前的功能只有一個(gè)就是把內(nèi)容右對(duì)齊。

讓我們把它刪除:

- .actions-list {
-   text-align: right;
- }
  .actions-list__item {
    margin-right: 1rem;
    &:last-child {
      margin-right: 0;
    }
  }

但現(xiàn)在,沒(méi)有了.actions-list類(lèi),而只有.actions-list__item類(lèi),看上去有些奇怪。有沒(méi)有一個(gè)方法,可以使我們?cè)跊](méi)有創(chuàng)建.actions-list__item組件的情況下,解決我們最初的問(wèn)題?

如果你回想一下,我們創(chuàng)建這個(gè)組件的原因,只是為了在兩個(gè)按鈕之間增加一些間距。對(duì)于一組按鈕來(lái)說(shuō).actions-list是非常恰當(dāng)?shù)拿?,因?yàn)樗芡ㄓ貌⑶铱梢院芎玫谋粡?fù)用。但肯定也會(huì)有這種情況,就是我們需要在兩個(gè)元素之間設(shè)置相同的間距,但這個(gè)元素并不屬于.actions組件,對(duì)不?

也許我們可以創(chuàng)建一個(gè)更適合復(fù)用的名字,比如

.spaced-horizontal-list怎么樣?我們已經(jīng)刪除了

.actions-list,因?yàn)樗且粋€(gè)不需要任何樣式的子組件。

間距通用類(lèi)

如果只是子元素需要樣式,也許只給子元素添加樣式會(huì)比使用偽選擇器把它們當(dāng)做一個(gè)組件來(lái)寫(xiě)樣式更簡(jiǎn)單呢?

給元素添加間距的,復(fù)用性更好的方法也許是給它取這樣一個(gè)名字,一看便知“這個(gè)元素應(yīng)該與它周?chē)脑赜幸恍╅g距”。

我們已經(jīng)添加了諸如.align-left

.align-right的通用類(lèi), 那我們?cè)偌右粋€(gè)只能添加右邊距的新通用類(lèi)怎么樣?

讓我們來(lái)創(chuàng)建一個(gè)通用類(lèi),例如

.mar-r-sm。功能是在一個(gè)元素的右邊添加一段距離的間距:

- .actions-list__item {
-   margin-right: 1rem;
-   &:last-child {
-     margin-right: 0;
-   }
- }
+ .mar-r-sm {
+   margin-right: 1rem;
+ }

下面是我們的表單和header現(xiàn)在的樣子:


&lt;form class="stacked-form" action="#"&gt;

  <div>
    &lt;button class="btn btn--secondary mar-r-sm"&gt;Cancel&lt;/button&gt;
    &lt;button class="btn btn--primary"&gt;Submit&lt;/button&gt;
  </div>
&lt;/form&gt;

<header>
  <h2>New Product</h2>
  <div>
    &lt;button class="btn btn--secondary mar-r-sm"&gt;Cancel&lt;/button&gt;
    &lt;button class="btn btn--primary"&gt;Save&lt;/button&gt;
  </div>
</header>

.actions-list

組件已經(jīng)沒(méi)有了, 我們的CSS量更小, 類(lèi)更利于復(fù)用。

第5階段: 實(shí)用性第一的 CSS

在達(dá)到這個(gè)階段的時(shí)候,我剛剛建立了一整套實(shí)用類(lèi),用于常見(jiàn)的視覺(jué)微調(diào),例如:

  • 文字的 大小、顏色、粗細(xì)

  • 邊框的 顏色、寬度、位置

  • 背景的 顏色

  • Flexbox 通用類(lèi)

  • Padding 和 margin 的助手

你甚至可以在不寫(xiě)新的CSS的情況下,創(chuàng)建一個(gè)全新的UI組件,這真是件令人興奮的事情。

從我的一個(gè)項(xiàng)目中來(lái)看下這個(gè)“產(chǎn)品卡”組件:

[圖片上傳失敗...(image-381772-1513407635276)]

以下是我的標(biāo)記:

<div>
    <a href>
        <img src>
    </a>
    <div>
        <div>
            <a href>
                Test-Driven Laravel
            </a>
        </div>
        <a href>
            @icon('link')
        </a>
    </div>
    <div>
        <div>
            @icon('currency-dollar', 'icon-sm text-dark-softest mr-4')
            <span>$3,475</span>
        </div>
        <div>
            @icon('user', 'icon-sm text-dark-softest mr-4')
            <span>25</span>
        </div>
    </div>
</div>

第一眼看到這么多類(lèi)名,也許會(huì)使你感到失望。但是說(shuō)我們確實(shí)想使它成為一個(gè)真正的CSS組件,而不是從通用類(lèi)庫(kù)里挑出需要的類(lèi)名進(jìn)行組合。那么我們應(yīng)該怎么稱呼它呢?

我們不打算使用根據(jù)具體內(nèi)容而起的類(lèi)名,因?yàn)槟菢拥脑捨覀兊慕M件只能在特定情境中使用一次。

難道是向下邊這樣命名?

.image-card-with-a-full-width-section-and-a-split-section

當(dāng)然不是,這看上去太荒謬了。 我們更傾向于將它組合成小一些的組件,就像我們之前討論過(guò)的那樣。

那么這些小一些的組件會(huì)是什么?

它可能被放在.card容器里。 并不是所有卡片都有陰影,所以我們可以用.card--shadowed來(lái)修飾作為類(lèi)名。或者我們可以創(chuàng)建一個(gè)新的通用類(lèi)

.shadow

。這個(gè)類(lèi)可以用在任何元素上。這樣看起來(lái)更加利于復(fù)用,那我們就這么做吧!

我們的網(wǎng)站大部分卡片樣式?jīng)]有圓角,但是這個(gè)有。我們可以給他起名.card--rounded。但我們的網(wǎng)站會(huì)有一些別的元素也有相同幅度的圓角樣式,但他們不是卡片。一個(gè)

.rounded

通用類(lèi)的復(fù)用率也許更高。

那位于上邊的圖片呢?因?yàn)樗3峙c父元素寬度一樣,所以給它起名叫.img--fitted合適嗎? 這個(gè)網(wǎng)站上有些元素也是需要保持與父元素寬度一樣的,但并不一定就是個(gè)圖片。 那么僅僅起名為

.fit

也許會(huì)更合適。

...你可以看出我們最終想要的效果。

如果您將足夠的重點(diǎn)放在可復(fù)用性上,那么就會(huì)很自然的做到用可復(fù)用的通用類(lèi)來(lái)創(chuàng)建組件。

加強(qiáng)一致性

使用較小的,可組合的通用類(lèi)的最大好處是,你團(tuán)隊(duì)的所有開(kāi)發(fā)者可以從一個(gè)固定的列表里面選擇類(lèi)名。

想想有多少次你寫(xiě)樣式的時(shí)候心里想著“這個(gè)文本需要更深一點(diǎn)的顏色”,然后用darken()

函數(shù)去微調(diào)變量

$text-color的值?

又或者,

"這個(gè)字號(hào)應(yīng)該再小一點(diǎn),"

然后給你正在開(kāi)發(fā)的組件添加了

font-size: .85em

看上去你做的很“對(duì)”,因?yàn)槟闶褂昧讼鄬?duì)的顏色和相對(duì)的字號(hào),而不是固定的值。

但如果你想在組件中把字色調(diào)深10%,而其他人則想調(diào)深12%,那該怎么辦?當(dāng)你回過(guò)神來(lái)的時(shí)候,發(fā)現(xiàn)你的樣式表中 有380種獨(dú)特的文字顏色.

只要你正在寫(xiě)新的CSS,這種情況在每個(gè)代碼庫(kù)中都會(huì)發(fā)生:

  • GitLab: 380個(gè)文本顏色,202個(gè)背景顏色,58個(gè)字體大小

  • Buffer: 124個(gè)文本顏色,86個(gè)背景顏色,54個(gè)字體大小

  • HelpScout: 198個(gè)文字顏色,133個(gè)背景顏色,67個(gè)字體大小

  • Gumroad: 91個(gè)文字顏色,28個(gè)背景顏色,48個(gè)字體大小

  • Stripe: 189個(gè)文字顏色,90個(gè)背景顏色,35個(gè)字體大小

  • GitHub: 157個(gè)文字顏色,155個(gè)背景顏色,56個(gè)字體大小

  • ConvertKit: 124個(gè)文本顏色,123個(gè)背景顏色,69個(gè)字體大小

這是因?yàn)槟阋獙?xiě)的每個(gè)CSS代碼塊都可以視為一個(gè)空白的畫(huà)布,你可以不受限制的隨意使用任何值。

你可以通過(guò)聲明變量和使用mixins加強(qiáng)代碼的一致性。但

每一行新的CSS仍然有可能會(huì)使樣式變得更復(fù)雜,添加更多的CSS是永遠(yuǎn)不會(huì)使CSS更簡(jiǎn)單的。

相反,用已經(jīng)存在的CSS類(lèi)名來(lái)給HTML添加樣式,用這種解決方式的話,空白畫(huà)布的問(wèn)題就不存在了。

想要顏色更深的字色嗎?添加

.text-dark-soft

類(lèi)。

想要稍小一些的字號(hào)嗎?使用

.text-sm

類(lèi)。

當(dāng)項(xiàng)目組中的每個(gè)人都可以從一個(gè)有限的列表中,選擇他們的樣式時(shí)。CSS樣式表容量就不會(huì)跟隨項(xiàng)目變大而直線上升,你就會(huì)獲得了相對(duì)的自由。

你仍然應(yīng)該創(chuàng)建組件

我的觀點(diǎn)與那些頑固的實(shí)用派CSS擁護(hù)者的區(qū)別之一就是,我不認(rèn)為你應(yīng)該僅用通用類(lèi)來(lái)給標(biāo)簽添加樣式。

如果你看了一些時(shí)下流行的通用類(lèi)框架,例如

Tachyons

(一個(gè)非常棒的庫(kù)),你會(huì)發(fā)現(xiàn)他們?cè)诩兇獾耐ㄓ妙?lèi)之外,甚至創(chuàng)建了按鈕的樣式:

&lt;button class="f6 br3 ph3 pv2 white bg-purple hover-bg-light-purple"&gt;
  Button Text
&lt;/button&gt;

哇哦,讓我來(lái)把這個(gè)分解一下:

  • f6: 使用字體大小中的第6個(gè)字號(hào) ( 在 Tachyons 是.875rem)

  • br3: 使用圓角弧度中的第3號(hào)(.5rem)

  • ph3: 使用上下padding中的第3號(hào)(1rem)

  • pv2: 使用左右padding中的第2號(hào)(.5rem)

  • white: 使用白色字色

  • bg-purple: 使用字色背景色

  • hover-bg-light-purple: 鼠標(biāo)懸浮時(shí)使用淺紫色背景作為高亮

如果你需要多個(gè)有相同類(lèi)的按鈕(譯者:這個(gè)例子里的標(biāo)簽由多個(gè)類(lèi)名組合修飾。),Tachyons 建議的方法是創(chuàng)建一個(gè)抽象的HTML模版,而不是CSS。

舉個(gè)例子,如果你正在使用

Vue.js,你可以創(chuàng)建一個(gè)組件,像下邊這樣使用類(lèi)名:

`&lt;ui-button color="purple"&gt;Save&lt;/ui-button&gt;`

...組件的內(nèi)容如下:

&lt;template&gt;
  &lt;button class="f6 br3 ph3 pv2" :class="colorClasses"&gt;
    &lt;slot&gt;&lt;/slot&gt;
  &lt;/button&gt;
&lt;/template&gt;

&lt;script&gt;
export default {
  props: ['color'],
  computed: {
    colorClasses() {
      return {
        purple: 'white bg-purple hover-bg-light-purple',
        lightGray: 'mid-gray bg-light-gray hover-bg-light-silver',
        // ...
      }[this.color]
    }
  }
}
&lt;/script&gt;

對(duì)于大多數(shù)項(xiàng)目來(lái)說(shuō),這是一個(gè)很好的方法。但

我依然認(rèn)為創(chuàng)建一個(gè)CSS組件比

創(chuàng)建一個(gè)基于HMTL模版的組件更加實(shí)用

在我工作的這些項(xiàng)目中,會(huì)將這7個(gè)通用類(lèi)組合起來(lái),創(chuàng)建一個(gè)新的

.btn-purple類(lèi)。這要比給標(biāo)簽一個(gè)一個(gè)添加顆粒度很小的類(lèi)名要簡(jiǎn)單的多。(譯者:一旦其中一個(gè)類(lèi)名不需要了,每個(gè)用到這個(gè)組合類(lèi)的標(biāo)簽都要被修改,而作者的方法只要改新添加的

.btn-purple類(lèi)就可以了,不需要改HTML)。

...但是先要用通用類(lèi)構(gòu)建樣式

我的方法需要先使用通用類(lèi)給標(biāo)簽添加樣式的原因是,我希望使用通用類(lèi)來(lái)構(gòu)建網(wǎng)站樣式(譯者:言外之意,不添加任何新樣式)。而且只有當(dāng)他們出現(xiàn)時(shí)才會(huì)精確的復(fù)用重復(fù)的部分。

如果你用

Less

作為你的預(yù)處理器,你可以將現(xiàn)有的類(lèi)用minxins混合。那意味著你只需要利用編輯器的多光標(biāo)功能就可以創(chuàng)建

.btn-purple

組件了:

[圖片上傳失敗...(image-abe0f4-1513407635274)]

但如果你用的是Sass或者其他沒(méi)有這種可以將多個(gè)通用類(lèi)進(jìn)行混合而生成新類(lèi)的方法的話,就會(huì)稍微麻煩一些,你需要做一些別的工作,才能支持。

當(dāng)然并不是每個(gè)組件類(lèi)都可以通過(guò)多個(gè)通用類(lèi)組合而來(lái)。元素間復(fù)雜的交互是很難只是用通用類(lèi)來(lái)解決的。例如當(dāng)鼠標(biāo)懸浮在父元素時(shí)希望子元素改變屬性時(shí)。所以你要經(jīng)過(guò)思考,選擇你認(rèn)為最簡(jiǎn)單的方法。

不要提前抽象

使用“組件優(yōu)先”的方法寫(xiě)CSS,意味著你創(chuàng)建的組件有可能永遠(yuǎn)不會(huì)被復(fù)用。這種提前抽象就是樣式表如此臃腫和復(fù)雜的原因。

以一個(gè)導(dǎo)航條為例。在你的應(yīng)用中重復(fù)寫(xiě)了多少次主導(dǎo)航標(biāo)簽?

在我的項(xiàng)目里,我一般只會(huì)寫(xiě)一次,在我的主布局文件里。

如果你先創(chuàng)建通用類(lèi),然后將這些通用類(lèi)組合。只有當(dāng)你看到令人煩惱的重復(fù)時(shí)再提取出小型組件,你也許永遠(yuǎn)不需要提取一個(gè)導(dǎo)航條組件。

相反,你的導(dǎo)航條看起來(lái)也許會(huì)像下邊這樣:

<nav>
  <div></div>
  <div>

  </div>
</nav>

這里沒(méi)有什么可值得提取的。

難道這不是內(nèi)聯(lián)樣式嗎?

這種方法很容易讓人認(rèn)為是內(nèi)聯(lián)樣式。這種樣式是在你需要的時(shí)候?qū)⒁恍邮綄傩苑旁贖TML的標(biāo)簽上。但以我的經(jīng)驗(yàn)來(lái)看,這兩者有很大不同。

如果是內(nèi)聯(lián)樣式的話,你在選擇值的時(shí)候是沒(méi)有任何約束的。

這個(gè)標(biāo)記可能是

font-size: 14px,另一個(gè)就可能是font-size: 13px, 還有一個(gè)就可能是

font-size: .9em, 也有可能是

font-size: .85rem.

當(dāng)為每個(gè)新組件編寫(xiě)新的CSS時(shí),它與您所面臨的空白畫(huà)布問(wèn)題相同。

通用類(lèi)則強(qiáng)迫你選擇:

是用

text-sm

還是

text-xs?

我們可以用

py-3

py-4

嗎?

我是想用

text-dark-soft

還是

text-dark-faint?

你不能隨意使用你想用的值,而是只能從一個(gè)策劃好的列表里選擇。

你最終只用了10個(gè)或者12個(gè)文字顏色,而不是380個(gè)。

我的經(jīng)驗(yàn)是先創(chuàng)建通用類(lèi),再創(chuàng)建組件類(lèi),可以使設(shè)計(jì)保持一致性,剛開(kāi)始時(shí)這聽(tīng)起來(lái)可能不太直觀。

如何開(kāi)始

如果你對(duì)這個(gè)方法感興趣,下邊是一些值得參考的框架,不妨一試:

我在開(kāi)發(fā)一個(gè)免費(fèi)的開(kāi)源LESS框架,叫做

Tailwind CSS

這個(gè)框架是圍繞本文的主旨開(kāi)發(fā)的,即先創(chuàng)建通用類(lèi),然后從重復(fù)的部分中提取可復(fù)用的組件:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 最近學(xué)習(xí)到CSS的繼承屬性,正好看到這篇文章,便將它翻譯出來(lái)。作者的思想,在平時(shí)的項(xiàng)目中或多或少都有用過(guò),但是從來(lái)...
    hershin閱讀 985評(píng)論 0 1
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,220評(píng)論 4 61
  • 有這樣一些孩子,你常常會(huì)看到他們?cè)诜笇W(xué)習(xí),他們的作業(yè)通常也沒(méi)有什么問(wèn)題,總體上給人的感覺(jué)是一直很努力的,然而,只...
    飛花如雪閱讀 1,311評(píng)論 0 15
  • 12月6日自然小組冬季第二次活動(dòng),集合地點(diǎn)感通停車(chē)場(chǎng),主題冬季林鳥(niǎo)和蒼山草木。因?yàn)槲易约旱臅r(shí)間安排沖突,沒(méi)能夠參加...
    我才是櫻花樹(shù)閱讀 475評(píng)論 0 0
  • 比特幣作為最成功的數(shù)字貨幣,如果可以,還有什么需要改進(jìn)的地方嗎? 因?yàn)樵谥斜韭斨?。從?lái)沒(méi)有成功去中心化數(shù)字貨幣。...
    李白起不來(lái)閱讀 221評(píng)論 0 0

友情鏈接更多精彩內(nèi)容