天天看點

遇見未知的CSS

摘錄自《CSS核心技術詳解》

1.1 CSS中你可能會疑問的幾個問題

1.1.1 在CSS中為什麼要有層疊

在CSS中可能會有多個樣式表同時影響同一個元素的某個屬性,設計這個功能的主要原因有兩個,解決子產品化和作者、使用者、使用者代理樣式沖突。

  • 子產品化

一個頁面中的樣式可以拆分成多個樣式表,代碼如下。

@import url(style/base.css);
@import url(style/layer.css);
      

但這種方式也會随之産生一個問題,即如果對某個元素的同一個屬性設定樣式,到底應用誰的呢?

  • 作者/使用者/使用者代理

當作者(寫代碼的人)和使用者(浏覽頁面的人),以及使用者代理(一般指浏覽器)都能更改樣式表時,也會産生同樣的問題:究竟用誰設定的樣式,是以CSS層疊機制就顯得格外重要。

1.1.2 為什麼“@import”指令需要寫在樣式表的開頭

代碼如下。

@import url(style/layer.css);
body{
  background-color:red;
}
      

“@import”指令之是以需要寫在樣式表的開頭,是因為這樣可以使後面的樣式能更好地層疊導入進來的樣式。

1.1.3 當CSS值為0時為什麼可以省略機關

因為當CSS值為0時,任何機關的結果都是一樣的,就像數學中的0乘以任何數都得0。

1.1.4 margin垂直外邊距折疊的意義是什麼

margin垂直外邊距折疊的特性主要來自傳統排版,舉個例子,代碼如下。

<style>
  body,ul,li{
    margin:0;
    padding:0;
  }
  ul{
    list-style:none;
  }
  ul>li{
    margin:20px 0;
  }
</style>
<ul>
  <li>1111111111</li>
  <li>2222222222</li>
  <li>3333333333</li>
</ul>
      

效果如圖 1.1

遇見未知的CSS

從圖1.1中可以看到3行數字的垂直外邊距都是一樣的。如果沒有這個特性,第一行數字與下面兩行數字的外邊距就不一樣了,因為我們給每個li都設定了一個上下外邊距,假如沒有外邊距折疊,那麼第1個li的下邊距加上第2個li的上邊距,就是兩倍的間距了,但是第一個li上面沒有其他元素,是以它隻有一個上邊距,最終導緻的結果就是,第1個li和後面的幾個li的外邊距不一樣,這顯然不是我們所希望的。而margin外邊距折疊功能正是要在這種情況下,讓格式可以好看一點。

1.1.1 CSS層疊規則

在介紹CSS層疊規則之前首先舉個例子,代碼如下。

<style>
  .box{
    color:red;
    font-size:18px;
  }
</style>
<div class="box">
  <a href="#">層疊</a>
</div>
      

結果如圖1.2所示:

遇見未知的CSS

按理說顔色是可以繼承的,那麼為什麼a标簽的顔色沒有變成紅色呢?審查一下元素,如圖1.3所示。

遇見未知的CSS

從圖1.3中可以看到繼承的顔色被劃掉了,出現這個問題的原因是浏覽器對a标簽設定了預設樣式,将繼承的樣式層疊了,因為繼承的樣式權重最小。下面介紹CSS關于層疊規則是怎麼計算的。

在CSS中一個樣式可能會來自不同的地方,分别是作者,使用者以及使用者代理。那麼問題來了,如果在這幾份樣式中,他們對同一個元素的同一個屬性做了不同的操作,那麼使用者代理應該如何處理這段CSS呢?舉個例子,代碼如下。

/* 作者 */
.box{
  color:red;
}
/* 使用者代理 */
.box{
  color:green;
}
/* 使用者 */
.box{
  color:pink;
}
      

可以看到使用者代理以及使用者的代碼和作者寫的樣式起沖突了,而CSS的層疊規則就是為了解決這些問題的,以下是一些CSS層疊規則。

在層疊中每個樣式規則都有一個權重值,當其中幾條規則同時生效時,權重值最大的規則優先。一般來說作者指定的樣式權重值高于使用者樣式權重值,使用者樣式權重值高于用戶端(使用者代理)權重值。

在層疊順序中,以下權重值從小到大。
  1. 使用者代理樣式
  2. 使用者一般樣式
  3. 作者一般樣式
  4. 作者重要樣式(!important)
  5. 使用者重要樣式(!important)
  6. 如果是兩個樣式來自同一個地方,如都來自作者,并且它們的樣式聲明同樣重要,則根據特異度來計算,特異度高的會覆寫特異度低的
  7. 如果特異度也相同,則越往後的樣式優先級越高
!important聲明規則

!important聲明的樣式比一般聲明優先級高,并且使用者設定的!important比作者設定的!important優先級高。這樣做的原因是為了友善使用者實作一些特殊的要求,例如頁面字型大小的調整等。

下面舉一個!important規則的例子,代碼如下。

<style>
  .box{
    color:red !important;
  }
  .box{
    color:green;
  }
</style>
<div class="box">!important</div>
      

在正常情況下,後一個“color:green”會層疊前一個“color:red”,但這裡我們給“color:red”設定了!important規則,是以前一個優先級高。

選擇器特異度的計算
  1. 如果一個聲明出現在元素的style屬性中,則将a計為1
  2. b等于選擇器中所有id選擇器加起來的數量和
  3. c等于選擇器中所有class選擇器和屬性選擇器,以及僞類選擇器加起來的數量和
  4. d等于選擇器中所有标簽選擇器和僞元素選擇器加起來的數量和

将a、b、c、d這4個數字連接配接起來(a-b-c-d)就組成了選擇器的特異度。一段特異度的計算,如下所示。

.box{}           /* a=0 b=0 c=1 d=0 特異度 = 0,0,1,0 */
.box div{}       /* a=0 b=0 c=1 d=1 特異度 = 0,0,1,1 */
#nav li{}        /* a=0 b=1 c=0 d=1 特異度 = 0,1,0,1 */
p:first-line{}   /* a=0 b=0 c=0 d=2 特異度 = 0,0,0,2 */
style=""         /* a=1 b=0 c=0 d=0 特異度 = 1,0,0,0 */
      

它們的比較順序是先比較a,如果a的值都相同,那麼接着比較b、c、d的值,誰的數大則優先級就越高。

在使用CSS選擇器時,你需要注意以下兩點。

  • 繼承的優先級最低,沒有特異度;
  • 結合符(如+、>等)及通用選擇符(*)特異度為0。

是以,可以知道之前a标簽color屬性為什麼沒有被應用了,因為繼承的優先級最低。

1.1.6 CSS的命名

在代碼複用時,也許你寫過類似以下這樣的代碼,代碼如下

size-10{
 font-size:10px;
}
      

雖然這段代碼看起來沒什麼問題,但如果考慮到可維護性,那就有很大問題了。假如有一天你不想用10px,想改成12px,也許你會想再加一個class就行了,修改後的代碼如下

size-10{
	font-size:10px;
}
size-12{
 	font-size:12px;
}
      

但那些頁面中原本用的size-10的class都得修改成size-12,是以不建議這麼修改代碼。筆者建議用語義的方式命名,代碼如下

.size-small{
  font-size:12px;
}
      

這樣寫代碼的好處是當需要調整字型大小時,隻需修改一處,而不必修改添加到元素上的class。也就是說不要按照顯示的效果命名,而是根據這個class的用意命名。

1.2 CSS的一些技巧

1.2.1 使用pointer-events控制滑鼠事件

可以用CSS中的pointer-events來控制元素什麼時候響應滑鼠事件,比較常用的一個場景是擷取驗證碼,如圖1.4所示。

遇見未知的CSS

圖1.4 擷取驗證碼

當使用者單擊“擷取驗證碼”按鈕後,需要等待60秒才能再次單擊“重發驗證碼”按鈕,在這種情況下,就可以嘗試用pointer-events實作禁用滑鼠單擊事件。在pointer-events屬性中有一個none值,将pointer-events的值設定成none就不會響應滑鼠事件了。舉一個擷取驗證碼的例子,代碼如下。

<style>
    a{
        color:red;
    }
    .disable{
        pointer-events:none;
        color:#666;
    }
</style>
<a href="javascript:;" id="btn">發送驗證碼</a>
<script>
    var oBtn = document.getElementById('btn');
    oBtn.onclick = function(){
        oBtn.classList.add('disable');
        setTimeout(function(){
            oBtn.classList.remove('disable');
        },3000)
    };
</script>
      

如果看不懂這段代碼也沒關系,将這段代碼複制下來即可。這段代碼的意義就是定義了一個滑鼠事件禁用的class,單擊“發送驗證碼”按鈕後加上剛剛定義的.disable,3秒以後再将這個class去掉。預設情況下的按鈕,如圖1.5所示

遇見未知的CSS

圖1.5 預設情況下

單擊此按鈕後,在3秒内不會再次響應單擊事件。

pointer-events除了可以實作此功能之外,還有很多用處,比如實作a标簽禁止頁面跳轉,提高網頁性能,使用者在滾動頁面時可能會不小心碰到一些元素上綁定的事件,這些事件就會被觸發,進而浪費資源,但如果在頁面滾動時給body加上pointer-events:none;屬性,那麼就避免了這個問題。

pointer-events還有一個妙用,比如将一個遮罩層元素的屬性設定為pointer-events:none;,這樣就可以單擊到遮罩層後面的内容,如圖1.6所示。

遇見未知的CSS

圖1.6 運用了pointer-events以後

如圖1.6所示可以看到選中了遮罩層後面的内容,但需要注意的是,pointer-events:none隻是用來禁用滑鼠的事件,通過其他方式綁定的事件還是會觸發的,比如鍵盤事件等。另外,如果将一個元素的子元素pointer-events設定成其他值,如auto,那麼當單擊子元素時,還是會通過事件冒泡的形式觸發父元素的事件。

1.2.2 玩轉CSS選擇器

1. 當父元素隻有一個子元素時會被選中,代碼如下

<style>
    div:first-of-type:last-of-type{
        color:red;
    }
</style>
<div>123</div>
      

當隻有一個div元素時,效果如圖1.7所示。當有多個div時不會被選中,效果如圖1.8所示。

遇見未知的CSS

圖1.7 當隻有一個div時

遇見未知的CSS

圖1.8 當有多個div時

當然更簡單的方法是直接用CSS3中的結構性僞類選擇器,當父元素隻有一個子元素時會被選中,如下:

:only-child
      

不妨去試試。

2.當父元素有多個子元素時,選中第1個

<style>
    div:not(:last-of-type):first-of-type{
        color:red;
    }
</style>
<div>11111</div>
      

隻有一個子元素時,不會被選中,效果如圖1.9所示。當有多個子元素時,它會選中第一個,效果如圖1.10所示。

遇見未知的CSS

圖1.9 隻有一個子元素時

遇見未知的CSS

圖1.10 當有多個子元素時

當然,如果有多個子元素時,也可以選擇其中任意一個子元素,但最後一個是選中不了的,因為我們已經用“:not”否定了最後一個元素。如果想要擺脫這種限制,可以使用下面這種方案,代碼如下。

:not(:only-child)
      

以有多個子元素時選中最後一個子元素為例,代碼如下

<style>
    div:not(:only-child):last-of-type{
        color:red;
    }
</style>
<div>11111</div>
<div>22222</div>
<div>33333</div>
      

當一個父元素有多個子元素時,最後一個元素會被選中,效果如圖所示。

遇見未知的CSS
案例

合理使用這些選擇器可以做很多事情,比如當隻有一個子元素時,可以讓它居中顯示,如果有多個子元素時,可以讓它水準排列,代碼如下

<style>
   .box div{
        width:100px;
        height:100px;
        border:1px solid red;
        margin:0 auto;
   }
   .box div:not(:only-child){
        float:left;
        margin-left:20px;
   }
</style>
<div class="box">
    <div></div>
</div>
      

當隻有一個子元素時,這個div就會被居中顯示,如下圖1.12

遇見未知的CSS

圖1.12 當隻有一個子元素時,這個div就會被居中顯示

當有多個子元素時,效果如圖所示

遇見未知的CSS
1.2.3利用padding實作元素等比例縮放

padding和margin有一個很奇怪的特點,它們的上下外邊距的百分比是根據父元素的寬度來計算的。舉個例子,代碼如下。

<style>
   .box{
        width:100px;
        height:10px;
   }
   .box div{
        width:100%;
        padding-bottom:100%;
        background-color:red;
   }
</style>
<div class="box">
    <div></div>
</div>
      

效果如圖1.14

遇見未知的CSS

圖1.14 padding、margin上下外邊距的百分比

在此例子中可以看到 div 的寬度和高度都是 100px。如果根據父元素的高度來計算,那麼div 的高度最終應該是 10px,而不是 100px,是以,若需要實作一個等比例的元素,就可以利用這個特性,但如果使用這種方式,還需要解決另外一個問題,就是如果直接在子元素div中寫入内容,那麼高度會被“撐開”,那就不是等比例了。代碼如下。

<div class="box">
    <div>padding-bottom</div>
</div>
      

若在div中加入一段文字,那麼高度就不再是等比例了,效果如圖1.15所示。

遇見未知的CSS

圖1.15 在div中加入一段文字後的高度

但是可以将代碼進行修改,修改後的代碼如下。

<style>
   .box{
        width:30%;
        height:10px;
   }
   .box div{
        position:relative;
        overflow:hidden;
        background-color:red;
   }
   .box div::after{
        content:'';
        display:block;
        padding-top:100%;
   }
   .box div span{
        position:absolute;
        left:0;
        top:0;
   }
</style>
<div class="box">
    <div>
        <span>圖檔</span>
    </div>
</div>
      

利用僞元素的padding-top來“撐開”父元素的高,内容通過絕對定位來使用,因為絕對定位的元素是不占位置的,這樣一個等比例寬高縮放就完成了。有時這種特性很有用,比如針對下面這個需求,如圖1.16所示。

現在需要将圖檔等比例縮放,也就是寬和高一樣,但圖檔的寬度是自适應螢幕大小的,img标簽在隻寫寬度不寫高度的情況下,高度會自适應寬度。圖檔沒有加載出來之前的情況,如圖1.17所示。

遇見未知的CSS

圖1.16 需求

這個需求是這樣的,圖檔等比例縮放,也就是寬和高得一樣,但問題是圖檔的寬度是自适應螢幕大小的,原本很簡單因為img标簽在隻寫寬度不寫高度的情況下,高度會自适應寬度,但問題不在這,而是如果圖檔在沒有加載出來的情況下,會是這樣的,如圖1.17

遇見未知的CSS

圖1.17 在圖檔沒有加載出來時

從圖1.17可以看到在圖檔沒有加載出來之前高度就沒有了,這時利用CSS屬性paddding-top就可以解決這個問題,代碼如下

.photo a{
	position:relative;
	float:left;
	width: calc(33.33% - 1.6rem);
	margin:1.2rem 0 0 1.2rem;
	outline:1px solid #dedede;
}
.photo a::before{
	content:'';
	display:block;
	padding-top:100%;
}
.photo a img{
	position:absolute;
	left:0;
	top:0;
	width:100%;
	height:100%;
}
      

使用一個僞元素将高度“撐起來”,而圖檔通過定位來做。還有一種更簡單的做法,就是直接給a标簽設定高度,機關使用vw。vw機關是相對于視口(螢幕)寬度的,代碼如下。

.photo a{
    float:left;
    width: calc(33.33% - 1.6rem);
    height: calc(33.33vw - 1.6rem);
    margin:1.2rem 0 0 1.2rem;
    outline:1px solid #dedede;
}
.photo a img{
    display:block;
    width:100%;
    height:100%;
}
      

寬度怎麼設定,高度就怎麼設定,就是把百分比換成vw。但是隻在自适應方面才能這樣用,如果是固定的寬、高,直接設定成一樣的就行了,雖然vw可以實作,但相容性還不是很好。

1.2.4 calc函數

在CSS中,如果需要用計算的功能,那麼calc函數将非常有用。calc函數允許進行任何長度值的計算,運算符可以是加(+)、減(-)、乘(*)、除(/)等。但需要注意的是,運算符前後都需要保留一個空格,雖然在某些特殊情況下可能不需要,但最好都加上,下面來介紹一些calc函數的使用場景。

場景一:

如圖1.18所示,圖中的内容一旦超過了浮動元素的高,那麼這些文本就會與圖檔左對齊,這種效果并不是我們想要的。我們想要的效果,如圖1.19所示。

遇見未知的CSS

圖1.18 實際不理想的效果

遇見未知的CSS

圖1.19 預期理想的效果

如果知道圖檔的寬度,那麼解決這個問題也很簡單,給這段文本添加一個左邊距即可,但如果圖檔使用的是百分比,那麼就無能為力了,而如果使用calc函數可以很好地解決這個問題,代碼如下。

<style>
    .box img{
        width:50%;
        float:left;
    }
    .box p{
        margin-left:calc(50% + 10px);
    }
</style>
<div class="box">
    <img src="psb.jpg" alt="">
    <p>......</p>
</div>
      

利用calc函數更改代碼後的效果如圖1.20所示

遇見未知的CSS

圖1.20 利用calc函數的效果

場景二:

有時使用百分比會出現一個問題,如圖1.21所示。

遇見未知的CSS

圖1.21 使用百分比時可能會出現的問題

其中CSS代碼為

<style>
    .box img{
        width:25%;
        margin:20px;
        float:left;
    }
</style>
      

導緻這個問題出現的原因是使用了margin值,而代碼中的width:25%并沒有減去這個margin值。是以隻需要用calc函數減去margin值就可以了,代碼如下

<style>
    .box img{
        width:calc(25% - 40px);
        margin:20px;
        float:left;
    }
</style>
      

最終效果如圖1.22所示

遇見未知的CSS

圖1.22 使用calc函數的最終效果圖

場景三:

如果再結合媒體查詢,那麼就很容易實作一個響應式的布局,代碼如下。

<style>
    .box img{
        width:calc(100% / 4 - 40px);
        margin:20px;
        float:left;
    }
    @media (max-width:600px){
        .box img{
            width:calc(100% / 2 - 40px);
        }
    }
</style>
      

這段代碼表示在螢幕不小于600px時,一行最多可以放4張圖檔,如果螢幕小于或等于600px時,一行最多隻能放兩張圖檔。

1.3 隐藏元素

千萬不要小看“隐藏”這個技能,多了解一點,就多一種選擇。如果你是一個新手,就會發現在本節将出現很多你不認識的屬性,它們可能是在CSS 2中就有的屬性,也可能是在CSS 3中出現的新屬性。

1. 通過設定width:0;或height:0;隐藏一個元素
div{width:0;}
      

div{height:0;}
      

一個物體是由寬和高組成的,那麼至少這個物體得有寬和高,這種方式的缺點是隐藏不了文字。可以将元素的color設定成與背景色一樣的顔色,這樣就看不見了。也可以設定成透明色(transparent),但問題是它們的内容還是存在的,是以需要将文字的大小設定成0,代碼如下

div{font-size:0;}
      
2. 将元素的opacity:0;設定成0
div{opacity:0;}
      

元素本身還在,隻是看不見而已

div{
    opacity:0;
    filter:alpha(opacity:0);
}
      
3. 通過定位将元素移出螢幕範圍
div{
   position:absolute;
   left:-9999px;
}
      

元素還在,隻是超出了螢幕範圍,看不見了而已。

4. 通過text-indent實作隐藏文字效果
div{text-indent:-999999px;}
      

給頁面添加LOGO圖檔,若還想讓搜尋引擎搜尋到,則需要添加這段文字,但如果又不想顯示這段文字,就可以使用這個方法。

5. 通過z-index隐藏一個元素
<style>
   .box{
   	    position:relative;
   }
   .box .item{
   	    position:absolute;
   	    left:0;
   	    top:0;
   	    width:100px;
   	    height:100px;
   	    border:1px solid red;
   	    z-index:-1;
   }
   .box .item:first-of-type{
   		z-index:1;
   }
</style>
<div class="box">
	<div class="item">程式員</div>
	<div class="item">設計師</div>
</div>
      

但你會發現文字被“透”上來了,效果如圖

遇見未知的CSS

因為預設的背景設定是透明的,并且允許下面的元素“透”上來,想解決這個問題很簡單,就是給元素添加一個背景,代碼如下。

.box .item{
	    position:absolute;
	    left:0;
	    top:0;
	    width:100px;
	    height:100px;
	    border:1px solid red;
	    background-color:#fff;
	    z-index:-1;
}
      

設定完成後,效果如圖

遇見未知的CSS
6. 通過給元素設定overflow來隐藏元素
div{
    width:100px;
    height:100px;
    overflow:hidden;
}
      

如果元素超出所設定的寬和高,溢出的部分就會被隐藏。如果想讓整個元素隐藏,将元素的寬和高設定成0即可。經常通過這種方式将超出的文字隐藏,代碼如下

<style>
	h2{
		width:16ch;
		overflow:hidden;
		white-space:pre;
		text-overflow:ellipsis;
	}
</style>
<h2>享受一片甯靜的天空。</h2>
      

當中文超出7個字元以後,文字就會被隐藏,效果如圖

遇見未知的CSS
7. 通過visibility将元素設定為不可見
div{visibility:hidden;}
      

雖然元素不可見,但還占位置。

8. 通過display将元素徹底隐藏
div{display:none;}
      

元素會被隐藏,并且不占位置。

9. 将元素的背景設定為透明,字型大小設定為0
div{
    font-size:0;
    background-color:transparent;
}
      

元素還在,隻是看不見。

10. 将元素的max-width或max-height設定為0
div{max-height:0;}
      
div{max-width:0;}
      

這樣元素的寬度就隻能是0了,但是還有文字溢出的問題,如圖1.26所示。

遇見未知的CSS

圖1.26 文字溢出

盡管元素寬度是 0,但文字還是被顯示出來了,若想解決這個問題,将文字大小設定成0就可以了,或者使用代碼overflow:hidden;如果你仔細看這個效果,會發現它實際上是一個文字豎排的效果,不過對于英文來說,還得設定一個換行屬性,換行屬性代碼如下

<style>
	h2{
		max-width:0px;
		word-break:break-all;
	}
</style>
<h2>享受一片甯靜的天空AAA</h2>
      

效果如圖1.27

遇見未知的CSS

圖1.27 通過設定word-break:break-all;解決英文不換行問題

11. 通過transform的translate函數來隐藏一個元素
div{transform:translate(-99999px);}
      

和left:-99999px;原理一樣,把元素移出螢幕可視區。

12. 将元素的縮放設成0
transform:scale(0);
      

看不見我,看不見我。

13. 讓元素重疊
div{transform:skew(90deg);}
      
14. 設定margin負值
div{margin-left:-999999px;}
      
15. 将元素裁剪
-webkit-clip-path:polygon(0px 0px,0px 0px,0px 0px,0px 0px);