CSS 中的簡寫到底有多少坑?以後不敢了...

零一發表於2022-05-07

大家好,我是零一,簡寫(語法糖)可能給我們編碼帶來了很多便利,但簡寫也會帶來一些問題,今天來討論一下 CSS 中的簡寫的"愛恨情仇"

為什麼說是愛恨情仇呢?因為簡寫給我們帶來了很多的便利,但凡事都有好有壞,你不能說簡寫一點壞處都沒有。所以我們就聊聊簡寫的 "好""壞"

background

這個 CSS 屬性大家肯定是再熟悉不過了,給元素設定背景色

是這樣?

.demo {
  background: #333;
}

還是這樣?

.demo {
  background-color: #333;
}

應該都有吧,換作我自己,平常這兩者用哪個好像也是看心情,如果你說肯定用前者啊,因為前者就一個單詞,那其實後者也不麻煩,現在大家都用編輯器,在智慧提示的輔佐下輸入 bgc 再按回車就能打出 background: ; 了,其實也不麻煩

bgc

回到正題,其實這兩者寫法我更推薦後者,為什麼?來看個例子?

<style>
  .demo {
    background: #333;  /* 給元素設定了背景色#333 */
  }
  
  /* ... 中間隔了很多樣式程式碼 */

  .demo:hover {
    background: url("example.png"); 
  }
</style>

<div class="demo"/>

這個場景很簡單:滑鼠移到元素上就展示某張照片,未載入前用一個純色佔位

然而實際效果是?

效果不盡人意

為了效果明顯,我加了邊框、文字,網速調成 slow 3G

可以看到 hover 時的 background 覆蓋掉了前者的 background,使得效果差強人意

是因為前者和後者都是 background,所以後者自然會覆蓋前者嗎?不全是

即便我們前者用的 background-color: #333;,也仍然會被後者覆蓋

大家都知道 background 是一個語法糖,即很多屬性的簡寫 ?

那像例子中用 background: url('example.png')時,做了什麼呢?

如上圖所示,它預設把所有值都設定成了 initial,因此無論之前用到了其中哪個值都會被覆蓋,雖然 initial 設定了跟沒設定是一樣,都表示保持元素該屬性的初始值

會不會有人想說:我一直都這麼用的,都沒遇到啥問題啊!

我只想說一句:可能運氣比較好,等程式碼比較複雜了,你可能會回來補這個窟窿的

結論:這就是一個簡寫造成的隱患,大家能避免則避免

margin

又提了一個大家再熟悉不過的屬性 margin,這裡面又有啥坑呢?沒啥坑,就是想介紹一下其它用法

以下都是它的簡寫:

  • margin: 10px 20px 30px 40px
  • margin: 10px 20px 30px
  • margin: 10px

這些簡寫確實幫我們省去了不少的程式碼量

讓我們投身到一個例子?中:現在我想讓我的元素水平居中,我想用 margin 來實現

<style>
  .parent {
    width: 300px;
    height: 300px;
    border: 1px solid black;
  }

  .child {
    width: 100px;
    height: 100px;
    background-color: red;
    margin: auto;  /* 水平居中 */
  }
</style>

<div class="parent">
  <div class="child"/>
</div>

效果如你所願:

但你使用 margin: auto 時有沒有那麼一瞬間想過,前面是否設定過 margin-topmargin-bottom 呢?比如這樣:

<style>
  .parent {
    width: 300px;
    height: 300px;
    border: 1px solid black;
  }
  
+ .child {
+   margin-top: 100px;
+ }
  
+ /* ...省略幾百行程式碼 */

  .child {
    width: 100px;
    height: 100px;
    background-color: red;
    margin: auto;  /* 水平居中 */
  }
</style>

<div class="parent">
  <div class="child"/>
</div>

預期的效果是什麼樣的?而此時的效果是什麼樣的?

預期的效果

現在的效果

可以看到,預期是想要既水平居中,又距離頂部 100px,而現在 margin-top 被覆蓋了

其實你單純想實現水平居中完全沒必要用 margin: auto,因為你本意是不想去修改頂部和底部的間距的,只是因為你用了這個簡寫,不得不這麼做

不然試試另一個簡寫?讓你只處理水平的間距

<style>
  .parent {
    width: 300px;
    height: 300px;
    border: 1px solid black;
  }
  
  .child {
    margin-top: 100px;
  }
  
  /* ...省略幾百行程式碼 */

  .child {
    width: 100px;
    height: 100px;
    background-color: red;
-   margin: auto;  /* 水平居中 */
+   margin-inline: auto;  /* 真正的只是水平居中 */
  }
</style>

<div class="parent">
  <div class="child"/>
</div>

這樣同樣能實現我們想要的效果,且不會影響 margin-topmargin-bottom 的屬性

那同理,有沒有能隻影響豎直方向的 margin 的簡寫呢?當然有,那就是 margin-block

一起來看另一個例子?

<style>
    .parent {
      position: relative;
      border: 1px solid black;
      width: 300px;
      height: 300px;
    }

    .child {
      position: absolute;
      top: 0;
      bottom: 0;
      left: 0;
      right: 0;
      width: 100px;
      height: 100px;
      margin: auto;
      background-color: red;
    }
</style>

<div class="parent">
  <div class="child"/>
</div>

效果如下:

這是一種對於非相對定位的垂直水平居中方法(記好了,面試官問你垂直水平居中的方法又多了一個),我是從 HTML 原生的 <dialog/> 標籤中瞭解到的(之前在12個可能你沒見過,但非常實用的 HTML 標籤介紹過 <dialog/>標籤)

dialog的水平居中實現

為什麼要用這個例子呢,我就是想引申出這個知識點,跟大家分享一下我最近看到的小 tips

我們可以刪除 margin: auto,用上前面說的 margin-inline: automargin-block: auto

結論margin 的簡寫不如 background 那麼複雜,但使用上了 margin-inlinemargin-block 也可以給自己降低心智負擔

inset

上面說了那麼多簡寫帶來的隱患,要不再來說說簡寫帶來的好處?

還是舉個例子?

<style>
  .parent {
    position: relative;
    border: 3px solid blue;
    margin: 200px;
    width: 300px;
    height: 300px;
  }

  .child {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    background-color: red;
  }
</style>

<div class="parent">
  <div class="child"/>
</div>

這段程式碼大家應該都很熟悉,我們給 .child 元素設定成了絕對定位,並賦予了以下屬性:

  • top: 0;
  • bottom: 0;
  • left: 0;
  • right: 0;

然後元素就撐滿父元素了,達到了 width: 100% + height: 100% 的效果,那為啥不直接設定寬高都 100% 呢?只用設定兩個屬性

❌❌❌ 不這麼做的原因還是要回到 position 本身,當一個元素脫離文件流時,若未設定 top、bottom、left、right,預設元素停留的位置就是其未脫離文件流時的位置

可能有點繞,直接上張圖

可以看到,零一 這個元素在脫離文件流後,仍然是停留在它處於文件流時的位置,那此時如果給他設定寬高 100% 會是什麼樣呢?

漂亮,超出父元素了,雖然解決這個問題也很簡單,直接加一個 left: 0 即可但我們還有更簡單的方法,那就是用 inset 這個屬性

其實 inset 就是 top、right、bottom、left 的簡寫,不知道為什麼,我看過很多人的程式碼,都沒用過這個屬性,所以也給大家安利一下

語法跟 margin 類似,因此我們用它來簡化剛才的程式碼

<style>
  .parent {
    position: relative;
    border: 3px solid blue;
    margin: 200px;
    width: 300px;
    height: 300px;
  }

  .child {
    position: absolute;
-   top: 0;
-   bottom: 0;
-   left: 0;
-   right: 0;
+   inset: 0;
    background-color: red;
  }
</style>

<div class="parent">
  <div class="child"/>
</div>

這裡為什麼我又推薦大家用 inset 了呢?本質是因為此處我們確實是需要同時設定上下左右四個值的,那為何不用 inset 呢?

border

? 天吶,受不了了!怎麼全是平時常用到的屬性,有這麼多坑嗎?

其實 border 這個還好,還是建議用簡寫的哈,只不過有一個特殊的 case,想給大家分享一下,避免踩坑

有這樣一個場景:一個元素本身沒有邊框,當滑鼠移入時出現邊框,邊框從有到無要有過渡動畫;同時滑鼠移除,邊框消失,也伴隨有過渡動畫

<style>
  .demo {
    width: 100px;
    height: 100px;
    background-color: lightblue;
    border: none;
    box-sizing: border-box;
    transition: border 1s linear;
  }

  .demo:hover {
    border: 4px solid red
  }
</style>

<div class="demo"/>

大部分人都會這麼寫對不對?效果如何呢?可惜只成功了一半!

為什麼滑鼠移出時,border 的過渡動畫消失了?

圖中可以看到,border: none 其實就是把 border-style 設定成了 none,大家都知道 transition 對於 border 的過渡動畫其實是針對 border-colorborder-width 的,但如果 border-style 都沒有,那還怎麼看得到過渡動畫呢?

所以我們想要實現滑鼠移出時的過渡動畫,就不能用 border: none 這個簡寫了,那該怎麼辦?

我想到了一個思路,可能不是最完美的,但根本看不出瑕疵,大家可以借鑑一下:

將元素的 border 初始狀態設定為 border: 0px solid transparent,這樣既保證了 border-style 的存在,又能保證邊框會從 4px 過渡到 0px,顏色也從 過渡到

效果如下:

總結

對於 「我們到底該如何使用簡寫?」 這個問題,我認為:需要一次性設定簡寫屬性中全部或絕大部分屬性時,可以使用簡寫;反之,則不太應該使用簡寫

我是零一,分享技術,不止前端!

希望本文對你有所幫助,我在用心寫文章,希望大家不要吝嗇自己的贊?

若有疑問或者建議,歡迎評論區留言,一起互相交流討論,共同進步!

相關文章