8則未必知道且超級實用的純CSS佈局排版技巧 | 網易4年實踐

「本文已參與好文召集令活動,點擊查看:後端、大前端雙賽道投稿,2萬元獎池等你挑戰!css

做者:JowayYoung
倉庫:GithubCodePen
博客:官網掘金思否知乎
公衆號:IQ前端
特別聲明:原創不易,未經受權不得轉載或抄襲,如需轉載可聯繫筆者受權html

前言

最近有些讀者悄悄發現了筆者的我的官網,無一例外都使用驚喜驚歎等詞形容。沒錯,筆者使用大量CSS闡述了什麼叫作玩轉CSS的藝術之美。即便某些應用場景缺乏JS的加持,筆者也能將CSS玩得遊刃有餘,整個網站源碼裏CSS大概佔據60%的份量,不少效果不是爲了炫技而是想告訴你們CSS的重要性與實用性。所以筆者想經過本文分享一些你們未必知道且超級實用的純CSS佈局排版技巧實現一些常見或特殊的佈局排版前端

我的官網

開發每一張網頁都離不開佈局排版,基於良好佈局排版打下基礎,才能使後續的開發更順利。固然不能停留在IExplorer時代那種侷限思惟上,沒辦法解決的佈局排版都用JS實現😂。今時不一樣往日,現代CSS屬性能更好地快速實現各類佈局排版,節約更多時間去摸魚😉。git

不過按照筆者目前瞭解的狀況來看,大部分同窗即便在無需兼容IExplorer的狀況下仍是遵循CSS+JS的方式完成一些常見或特殊的佈局排版。從HTML/CSS/JS前端三劍客的定位來看,HTML映射網頁的結構CSS映射網頁的表現JS映射網頁的行爲github

佈局排版指將圖形、文本、圖像、媒體等可視化信息元素在頁面佈局上調整位置尺寸等屬性使頁面佈局變得條理化的過程。大部分同窗認爲佈局排版就是幾個合理的CSS屬性隨便拼湊在一塊兒,但多數狀況即便實現也會存在瑕疵,此時就可能使用JS介入。web

佈局排版的特徵可知它屬於表現範疇,所以筆者認爲大部分佈局排版都能使用純CSS完成,無需JS介入。segmentfault

本文秉承能使用CSS實現的效果都優先使用CSS的原則,爲你們講解筆者如何巧妙運用各類純CSS開發技巧完成一些常見或特殊的佈局排版。所以筆者建議你們認真看一遍如下內容,絕對讓你有所收貨和驚喜。後端

若對CSS無特別想法,建議體驗如下網站,相信你會認真踏實地閱讀本文。數組

  • 我的官網:暫時支持PC端瀏覽,拒絕支持IExplorer
  • 特效專輯:暫時支持PC端瀏覽,拒絕支持IExplorer

屬性

在進入主題前,筆者總結出佈局排版一些必備屬性,這些屬性能快速搭建總體效果,再經過一些經常使用選擇器加以修飾達到完美效果。看似簡單,但使用起來不必定徹底駕馭。瀏覽器

必備屬性都是一些幾何屬性,主要用於聲明位置尺寸

  • 浮動佈局float
  • 定位佈局position/left/right/top/bottom/z-index
  • 彈性佈局display:flex/inline-flexflex系列屬性
  • 盒子模型box-sizing/margin/padding/border/width/height

選擇器因CSS模塊衆多而派生出的數量也衆多,若無特別方式記熟這些選擇器對應的功能,也很難將其發揮到最大做用。

筆者根據選擇器的功能劃分出八大類,每一個類別的選擇器都能在一個應用場景裏互相組合,記熟這些類別的選擇器,相信就能將選擇器發揮到最大做用,也能遊刃有餘將其應用到一些常見或特殊的佈局排版裏。

佈局排版可能只應用到某些選擇器,但也不妨礙你們經過如下歸類方式記憶。選擇器做爲CSS的重要組成部分,比起屬性組合會有更多的玩法。

基礎選擇器

選擇器 別名 說明 版本 經常使用
tag 標籤選擇器 指定類型的標籤 1
#id ID選擇器 指定身份的標籤 1
.class 類選擇器 指定類名的標籤 1
* 通配選擇器 全部類型的標籤 2

層次選擇器

選擇器 別名 說明 版本 經常使用
elemP elemC 後代選擇器 元素的後代元素 1
elemP>elemC 子代選擇器 元素的子代元素 2
elem1+elem2 相鄰同胞選擇器 元素相鄰的同胞元素 2
elem1~elem2 通用同胞選擇器 元素後面的同胞元素 3

集合選擇器

選擇器 別名 說明 版本 經常使用
elem1,elem2 並集選擇器 多個指定的元素 1
elem.class 交集選擇器 指定類名的元素 1

條件選擇器

選擇器 說明 版本 經常使用
:lang 指定標記語言的元素 2 ×
:dir() 指定編寫方向的元素 4 ×
:has 包含指定元素的元素 4 ×
:is 指定條件的元素 4 ×
:not 非指定條件的元素 4
:where 指定條件的元素 4 ×
:scope 指定元素做爲參考點 4 ×
:any-link 全部包含href連接元素 4 ×
:local-link 全部包含href且屬於絕對地址的連接元素 4 ×

狀態選擇器

選擇器 說明 版本 經常使用
:active 鼠標激活的元素 1 ×
:hover 鼠標懸浮的元素 1
:link 未訪問的連接元素 1 ×
:visited 已訪問的連接元素 1 ×
:target 當前錨點的元素 3 ×
:focus 輸入聚焦的表單元素 2
:required 輸入必填的表單元素 3
:valid 輸入合法的表單元素 3
:invalid 輸入非法的表單元素 3
:in-range 輸入範圍之內的表單元素 3 ×
:out-of-range 輸入範圍之外的表單元素 3 ×
:checked 選項選中的表單元素 3
:optional 選項可選的表單元素 3 ×
:enabled 事件啓用的表單元素 3 ×
:disabled 事件禁用的表單元素 3
:read-only 只讀的表單元素 3 ×
:read-write 可讀可寫的表單元素 3 ×
:target-within 內部錨點元素處於激活狀態的元素 4 ×
:focus-within 內部表單元素處於聚焦狀態的元素 4
:focus-visible 輸入聚焦的表單元素 4 ×
:blank 輸入爲空的表單元素 4 ×
:user-invalid 輸入合法的表單元素 4 ×
:indeterminate 選項未定的表單元素 4 ×
:placeholder-shown 佔位顯示的表單元素 4
:current() 瀏覽中的元素 4 ×
:past() 已瀏覽的元素 4 ×
:future() 未瀏覽的元素 4 ×
:playing 開始播放的媒體元素 4 ×
:paused 暫停播放的媒體元素 4 ×

結構選擇器

選擇器 說明 版本 經常使用
:root 文檔的根元素 3 ×
:empty 無子元素的元素 3
:nth-child(n) 元素中指定順序索引的元素 3
:nth-last-child(n) 元素中指定逆序索引的元素 3 ×
:first-child 元素中爲首的元素 2
:last-child 元素中爲尾的元素 3
:only-child 父元素僅有該元素的元素 3
:nth-of-type(n) 標籤中指定順序索引的標籤 3
:nth-last-of-type(n) 標籤中指定逆序索引的標籤 3 ×
:first-of-type 標籤中爲首的標籤 3
:last-of-type 標籤中爲尾標籤 3
:only-of-type 父元素僅有該標籤的標籤 3

屬性選擇器

選擇器 說明 版本 經常使用
[attr] 指定屬性的元素 2
[attr=val] 屬性等於指定值的元素 2
[attr*=val] 屬性包含指定值的元素 3
[attr^=val] 屬性以指定值開頭的元素 3
[attr$=val] 屬性以指定值結尾的元素 3
[attr~=val] 屬性包含指定值(完整單詞)的元素(不推薦使用) 2 ×
[attr|=val] 屬性以指定值(完整單詞)開頭的元素(不推薦使用) 2 ×

僞元素

選擇器 說明 版本 經常使用
::before 在元素前插入的內容 2
::after 在元素後插入的內容 2
::first-letter 元素的首字母 1 ×
::first-line 元素的首行 1 ×
::selection 鼠標選中的元素 3 ×
::backdrop 全屏模式的元素 4 ×
::placeholder 表單元素的佔位 4

技巧

有了上述前置知識,接下來跟着筆者體驗一次如何巧妙運用各類純CSS開發技巧完成一些常見或特殊的佈局排版吧。爲了方便瀏覽器自動計算某些樣式,需全局設置box-sizing:border-box,編碼前請引入筆者整理的reset.css

主體佈局

主體佈局指在大部分狀況下通用且具有統一特徵的佔位佈局。掌握主體佈局是一個前端必不可少的技能,養成看設計圖就能大概規劃出總體佈局的前提是必須熟悉這些主體佈局的特色與構造。

全屏佈局

經典的全屏佈局頂部底部主體三部分組成,其特色爲三部分左右滿屏拉伸頂部底部高度固定主體高度自適應。該佈局很常見,也是大部分Web應用主體的主流佈局。一般使用<header><footer><main>三個標籤語義化排版,<main>內還可插入<aside>側欄或其餘語義化標籤。

全屏佈局

<div class="fullscreen-layout">
    <header></header>
    <main></main>
    <footer></footer>
</div>
複製代碼

position + left/right/top/bottom

三部分統一聲明left:0right:0將其左右滿屏拉伸;頂部和底部分別聲明top:0bottom:0將其吸頂和吸底,並聲明倆高度爲固定值;將主體的topbottom分別聲明爲頂部高度和底部高度。經過絕對定位的方式將三部分固定在特定位置,使其互不影響。

.fullscreen-layout {
    position: relative;
    width: 400px;
    height: 400px;
    header,
    footer,
    main {
        position: absolute;
        left: 0;
        right: 0;
    }
    header {
        top: 0;
        height: 50px;
        background-color: #f66;
    }
    footer {
        bottom: 0;
        height: 50px;
        background-color: #66f;
    }
    main {
        top: 50px;
        bottom: 50px;
        background-color: #3c9;
    }
}
複製代碼

flex

使用flex實現會更簡潔。display:flex默認會令子節點橫向排列,需聲明flex-direction:column改變子節點排列方向爲縱向排列;頂部和底部高度固定,因此主體需聲明flex:1讓高度自適應。

.fullscreen-layout {
    display: flex;
    flex-direction: column;
    width: 400px;
    height: 400px;
    header {
        height: 50px;
        background-color: #f66;
    }
    footer {
        height: 50px;
        background-color: #66f;
    }
    main {
        flex: 1;
        background-color: #3c9;
    }
}
複製代碼

<main>需表現成可滾動狀態,千萬不要聲明overflow:auto讓容器自適應滾動,這樣作有可能由於其餘格式化上下文的影響而致使自適應滾動失效或產生其餘未知效果。需在<main>內插入一個<div>並聲明以下。

div {
    overflow: hidden;
    height: 100%;
}
複製代碼
兩列布局

經典的兩列布局左右兩列組成,其特色爲一列寬度固定另外一列寬度自適應兩列高度固定且相等。如下以左列寬度固定和右列寬度自適應爲例,反之同理。

兩列布局

<div class="two-column-layout">
    <div class="left"></div>
    <div class="right"></div>
</div>
複製代碼

float + margin-left/right

左列聲明float:left和固定寬度,因爲float使節點脫流,右列需聲明margin-left爲左列寬度,以保證兩列不會重疊。

.two-column-layout {
    width: 400px;
    height: 400px;
    .left {
        float: left;
        width: 100px;
        height: 100%;
        background-color: #f66;
    }
    .right {
        margin-left: 100px;
        height: 100%;
        background-color: #66f;
    }
}
複製代碼

overflow + float

左列聲明同上,右列聲明overflow:hidden使其造成BFC區域與外界隔離。

.two-column-layout {
    width: 400px;
    height: 400px;
    .left {
        float: left;
        width: 100px;
        height: 100%;
        background-color: #f66;
    }
    .right {
        overflow: hidden;
        height: 100%;
        background-color: #66f;
    }
}
複製代碼

flex

使用flex實現會更簡潔。左列聲明固定寬度,右列聲明flex:1自適應寬度。

.two-column-layout {
    display: flex;
    width: 400px;
    height: 400px;
    .left {
        width: 100px;
        background-color: #f66;
    }
    .right {
        flex: 1;
        background-color: #66f;
    }
}
複製代碼
三列布局

經典的三列布局左中右三列組成,其特色爲連續兩列寬度固定剩餘一列寬度自適應三列高度固定且相等。如下以左中列寬度固定和右列寬度自適應爲例,反之同理。總體的實現原理與上述兩列布局一致。

三列布局

<div class="three-column-layout">
    <div class="left"></div>
    <div class="center"></div>
    <div class="right"></div>
</div>
複製代碼

爲了讓右列寬度自適應計算,就不使用float + margin-left的方式了,若使用margin-left還得結合左中列寬度計算。

overflow + float

.three-column-layout {
    width: 400px;
    height: 400px;
    .left {
        float: left;
        width: 50px;
        height: 100%;
        background-color: #f66;
    }
    .center {
        float: left;
        width: 100px;
        height: 100%;
        background-color: #66f;
    }
    .right {
        overflow: hidden;
        height: 100%;
        background-color: #3c9;
    }
}
複製代碼

flex

.three-column-layout {
    display: flex;
    width: 400px;
    height: 400px;
    .left {
        width: 50px;
        background-color: #f66;
    }
    .center {
        width: 100px;
        background-color: #66f;
    }
    .right {
        flex: 1;
        background-color: #3c9;
    }
}
複製代碼
聖盃佈局/雙飛翼佈局

經典的聖盃佈局雙飛翼佈局都是由左中右三列組成,其特色爲左右兩列寬度固定中間一列寬度自適應三列高度固定且相等。其實也是上述兩列布局和三列布局的變體,總體的實現原理與上述N列布局一致,可能就是一些細節需注意。

聖盃佈局雙飛翼佈局在大致相同下也存在一點不一樣,區別在於雙飛翼佈局中間列需插入一個子節點。在常規實現方式裏也是在這個中間列裏作文章,如何使中間列內容不被左右列遮擋

  • 相同
    • 中間列放首位且聲明其寬高佔滿父節點
    • 被擠出的左右列使用floatmargin負值將其拉回與中間列處在同一水平線上
  • 不一樣
    • 聖盃佈局:父節點聲明padding爲左右列留出空位,將左右列固定在空位上
    • 雙飛翼佈局:中間列插入子節點並聲明margin爲左右列讓出空位,將左右列固定在空位上

聖盃佈局

聖盃佈局float + margin-left/right + padding-left/right

因爲浮動節點在位置上不能高於前面或平級的非浮動節點,不然會致使浮動節點下沉。所以在編寫HTML結構時,將中間列節點挪到右列節點後面。

<div class="grail-layout-x">
    <div class="left"></div>
    <div class="right"></div>
    <div class="center"></div>
</div>
複製代碼
.grail-layout-x {
    padding: 0 100px;
    width: 400px;
    height: 400px;
    .left {
        float: left;
        margin-left: -100px;
        width: 100px;
        height: 100%;
        background-color: #f66;
    }
    .right {
        float: right;
        margin-right: -100px;
        width: 100px;
        height: 100%;
        background-color: #66f;
    }
    .center {
        height: 100%;
        background-color: #3c9;
    }
}
複製代碼

雙飛翼佈局float + margin-left/right

HTML結構大致同上,只是在中間列裏插入一個子節點<div>。根據二者區別,CSS聲明會與上述聖盃佈局有一點點出入,可觀察對比找出不一樣地方。

<div class="grail-layout-y">
    <div class="left"></div>
    <div class="right"></div>
    <div class="center">
        <div></div>
    </div>
</div>
複製代碼
.grail-layout-y {
    width: 400px;
    height: 400px;
    .left {
        float: left;
        width: 100px;
        height: 100%;
        background-color: #f66;
    }
    .right {
        float: right;
        width: 100px;
        height: 100%;
        background-color: #66f;
    }
    .center {
        margin: 0 100px;
        height: 100%;
        background-color: #3c9;
    }
}
複製代碼

聖盃佈局/雙飛翼佈局flex

使用flex實現聖盃佈局/雙飛翼佈局可忽略上述分析,左右兩列寬度固定,中間列寬度自適應。

<div class="grail-layout">
    <div class="left"></div>
    <div class="center"></div>
    <div class="right"></div>
</div>
複製代碼
.grail-layout {
    display: flex;
    width: 400px;
    height: 400px;
    .left {
        width: 100px;
        background-color: #f66;
    }
    .center {
        flex: 1;
        background-color: #3c9;
    }
    .right {
        width: 100px;
        background-color: #66f;
    }
}
複製代碼
均分佈局

經典的均分佈局多列組成,其特色爲每列寬度相等每列高度固定且相等。整體來講也是最簡單的經典佈局,因爲每列寬度相等,因此很易找到合適的方式處理。

均分佈局

<div class="average-layout">
    <div class="one"></div>
    <div class="two"></div>
    <div class="three"></div>
    <div class="four"></div>
</div>
複製代碼
.one {
    background-color: #f66;
}
.two {
    background-color: #66f;
}
.three {
    background-color: #f90;
}
.four {
    background-color: #09f;
}
複製代碼

float + width

每列寬度聲明爲相等的百分比,如有4列則聲明width:25%。N列就用公式100 / n求出最終百分比寬度,記得保留2位小數,懶人還可用width:calc(100% / n)自動計算呢。

.average-layout {
    width: 400px;
    height: 400px;
    div {
        float: left;
        width: 25%;
        height: 100%;
    }
}
複製代碼

flex

使用flex實現會更簡潔。節點聲明display:flex後,生成的FFC容器裏全部子節點的高度都相等,由於容器的align-items默認爲stretch,全部子節點將佔滿整個容器的高度。每列聲明flex:1自適應寬度。

.average-layout {
    display: flex;
    width: 400px;
    height: 400px;
    div {
        flex: 1;
    }
}
複製代碼
居中佈局

居中佈局父容器若干個子容器組成,子容器在父容器中橫向排列或豎向排列且呈水平居中或垂直居中。居中佈局是一個很經典的問題,相信你們都會常常遇到。

居中佈局

在此直接上一個目前最簡單最高效的居中方式。display:flexmargin:auto的強行組合,同窗們自行體會。

<div class="center-layout">
    <div></div>
</div>
複製代碼
.center-layout {
    display: flex;
    width: 400px;
    height: 400px;
    background-color: #f66;
    div {
        margin: auto;
        width: 100px;
        height: 100px;
        background-color: #66f;
    }
}
複製代碼

自適佈局

自適佈局指相對視窗任何尺寸都能佔據特定百分比的佔位佈局。自適佈局的容器都是根據視窗尺寸計算,即便父節點祖先節點的尺寸發生變化也不會影響自適佈局的容器尺寸。

搭建自適佈局就離不開視窗比例單位。在CSS3裏增長了與viewport相關的四個長度單位,隨着時間推移,目前大部分瀏覽器對這四個長度單位都有較好的兼容性,這也是將來最建議在伸縮方案裏使用的長度單位。

  • 1vw表示1%視窗寬度
  • 1vh表示1%視窗高度
  • 1vmin表示1%視窗寬度和1%視窗高度裏最小者
  • 1vmax表示1%視窗寬度和1%視窗高度裏最大者

視窗寬高在JS裏分別對應window.innerWdithwindow.innerHeight。若不考慮低版本瀏覽器兼容性,徹底可用一行CSS代碼秒殺全部移動端的伸縮方案。

/* 基於UI width=750px DPR=2的網頁 */
html {
    font-size: calc(100vw / 7.5);
}
複製代碼

上述代碼使用calc()實現font-size的動態計算。calc()自適佈局裏的核心存在,無它就不能愉快地實現自適佈局全部動態計算了。

calc()用於動態計算單位,數值長度角度時間百分比都能做爲參數。因爲執行數學表達式後返回運算後的計算值,因此可減小大量人工計算甚至無需人工計算。

calc()飢不擇食,全部計量單位都能做爲參數參加整個動態計算。

  • 數值整數浮點數
  • 長度pxemremvwvh
  • 角度degturn
  • 時間sms
  • 百分比%

calc()雖然好用,但新手不免會遇到一些坑,謹記如下特色,相信就能玩轉calc()了。

  • 四則運算:只能使用+-*/做爲運算符號
  • 運算順序:遵循加減乘除運算順序,可用()提高運算等級
  • 符號鏈接:每一個運算符號必須使用空格間隔起來
  • 混合計算:可混合不一樣計量單位動態計算

第三點尤其重要,若未能遵照,瀏覽器直接忽略該屬性。

上述font-size:calc(100vw / 7.5)其實就是根據設計圖與瀏覽器視窗的比例動態計算<html>font-size100/750 = x/100vw

在SPA裏有遇過由於有滾動條或無滾動條而致使頁面路由在跳轉過程裏發生向左或向右的抖動嗎?這讓強迫症患者很難受,此時可用calc()巧妙解決該問題。

.elem {
    padding-right: calc(100vw - 100%);
}
複製代碼

不直接聲明padding-right爲滾動條寬度是由於每一個瀏覽器的默認滾動條寬度均可能不一致。100vw是視窗寬度,100%內容寬度,那麼100vw - 100%就是滾動條寬度,聲明padding-right用於保留滾動條出現的位置,這樣滾動條出不出現都不會讓頁面抖動了。

有了calc()作保障就可迅速實現一些與視窗尺寸相關的佈局了。例如實現一個視窗寬度都爲50%的彈窗。

自適佈局-彈窗

<div class="modal">
    <div class="modal-wrapper"></div>
</div>
複製代碼
.modal {
    display: flex;
    position: fixed;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    justify-content: center;
    align-items: center;
    background-color: rgba(0, 0, 0, .5);
    &-wrapper {
        width: 50vw;
        height: 200px;
        background-color: #f66;
    }
}
複製代碼

固然使用calc()也不必定結合視窗比例單位計算。例如自適佈局已知部分節點高度,不想手動計算最後節點高度但又想其填充佈局剩餘空間。

自適佈局

<ul class="selfadaption-layout">
    <div class="box-1"></div>
    <div class="box-2"></div>
    <div class="box-3"></div>
</ul>
複製代碼
.selfadaption-layout {
    width: 200px;
    height: 567px;
    .box-1 {
        height: 123px;
        background-color: #f66;
    }
    .box-2 {
        height: 15%;
        background-color: #3c9;
    }
    .box-3 {
        height: calc(100% - 123px - 15%);
        background-color: #09f;
    }
}
複製代碼

吸附佈局

吸附佈局指相對視窗任何滾動都能佔據特定位置的佔位佈局。視窗滾動到特定位置,佈局固定在該位置,後續不隨視窗滾動而滾動。該佈局產生的效果俗稱吸附效果,是一種常見網頁效果。譬如吸頂效果吸底效果都是該範疇,常常在跟隨導航移動廣告懸浮提示等應用場景裏出現。

jQuery時代就有不少吸附效果插件,如今三大前端框架也有自身第三方的吸附效果組件。它們都有着共通的實現原理:監聽scroll事件,判斷scrollTop目標節點的位置範圍,符合條件則將目標節點position聲明爲fixed使目標節點相對於視窗定位,讓用戶看上去就像釘在視窗指定位置上。

JS實現吸附效果的代碼在網上一搜一大堆,更況且筆者喜歡耍CSS,在此就不貼相關的JS代碼了。在此推薦一個不多見不多用的CSS屬性position:sticky。簡單的兩行核心CSS代碼就能完成十多行核心JS代碼的功能,何樂而不爲呢。

簡單回顧position屬性值,怎樣用就不說了,你們應該都熟悉。

取值 功能 版本
inherit 繼承 2
static 標準流 2
relative 相對定位 2
absolute 絕對定位 2
fixed 固定定位 2
sticky 粘性定位 3

當值爲sticky時將節點變成粘性定位粘性定位相對定位固定定位的結合體,節點在特定閾值跨越前爲相對定位,跨越後爲固定定位

吸附佈局

<div class="adsorption-position">
    <ul>
        <li>Top 1</li>
        <li>Top 2</li>
        <li>Normal</li>
        <li>Bottom 1</li>
        <li>Bottom 2</li>
    </ul>
</div>
複製代碼
.adsorption-position {
    overflow: auto;
    position: relative;
    width: 400px;
    height: 280px;
    outline: 1px solid #3c9;
    ul {
        padding: 200px 0;
    }
    li {
        position: sticky;
        height: 40px;
        line-height: 40px;
        text-align: center;
        color: #fff;
        &:nth-child(1) {
            top: 0;
            z-index: 9;
            background-color: #f66;
        }
        &:nth-child(2) {
            top: 40px;
            z-index: 9;
            background-color: #66f;
        }
        &:nth-child(3) {
            background-color: #f90;
        }
        &:nth-child(4) {
            bottom: 0;
            z-index: 9;
            background-color: #09f;
        }
        &:nth-child(5) {
            bottom: 40px;
            z-index: 9;
            background-color: #3c9;
        }
    }
}
複製代碼

兩行核心CSS代碼分別是position:stickytop/bottom:npx。上述5個節點都聲明position:sticky,但因爲top/bottom賦值有所不一樣就產生不一樣吸附效果。

細心的同窗可能發現這些節點在某些滾動時刻處於相對定位,在特定滾動時刻處於固定定位

  • 第1個<li>top0px,滾動到容器頂部就固定
  • 第2個<li>top40px,滾動到距離容器頂部40px就固定
  • 第3個<li>:未聲明top/bottom,就一直保持相對定位
  • 第4個<li>bottom40px,滾動到距離容器底部40px就固定
  • 第5個<li>bottom0px,滾動到容器底部就固定

固然,換成leftright也同樣能實現橫向的吸附效果

值得注意,粘性定位的參照物並不必定是position:fixed。當目標節點的任意祖先節點都未聲明position:relative|absolute|fixed|sticky,才與position:fixed表現一致。當離目標節點最近的祖先節點聲明position:relative|absolute|fixed|sticky目標節點就相對該祖先節點產生粘性定位。簡單來講確認參照物的方式與position:absolute一致。

兼容性勉強還行,近2年發版的瀏覽器都能支持,SafariFirefox的兼容性仍是挺讚的。有吸附效果需求的同窗建議一試,要兼容IExplorer就算了。期待該屬性有更好的發展,畢竟吸附佈局真的是一種常見佈局。

吸附佈局-sticky兼容性

橫向佈局

橫向佈局指容器內節點以水平方向排列且溢出部分被隱藏的佔位佈局。豎向佈局很常見,聲明overflow:hidden;width:xpx;height:ypx就能實現,但橫向佈局卻不能使用相似方式實現。

爲了方便使用多種方式實現橫向佈局,如下將通用代碼拆分出來。

橫向佈局

<div class="horizontal-layout">
    <ul>
        <li>Alibaba</li>
        <li>Tencent</li>
        <li>Baidu</li>
        <li>Jingdong</li>
        <li>Ant</li>
        <li>Netease</li>
        <li>Meituan</li>
        <li>ByteDance</li>
        <li>360</li>
        <li>Sina</li>
    </ul>
</div>
複製代碼
.horizontal-layout {
    overflow: hidden;
    width: 300px;
    height: 100px;
    ul {
        overflow-x: auto;
        cursor: pointer;
        &::-webkit-scrollbar {
            height: 10px;
        }
        &::-webkit-scrollbar-track {
            background-color: #f0f0f0;
        }
        &::-webkit-scrollbar-thumb {
            border-radius: 5px;
            background-color: #f66;
        }
    }
    li {
        overflow: hidden;
        height: 90px;
        background-color: #66f;
        line-height: 90px;
        text-align: center;
        font-size: 16px;
        color: #fff;
        &:not(:first-child) {
            margin-left: 10px;
        }
    }
}
複製代碼

有些同窗可能會使用行內元素實現橫向排版,但必須聲明overflow-y:hidden使容器在Y軸方向隱藏溢出部分。因爲行內元素在當前行排版產生溢出會自動將其他節點排版到下一行,所以還需聲明white-space:nowrap使全部行內元素在一行內排版完畢。若產生滾動條,還需對容器的height作適當的微調。

.horizontal-layout.inline {
    height: 102px;
    ul {
        overflow-y: hidden;
        white-space: nowrap;
    }
    li {
        display: inline-block;
        width: 90px;
    }
}
複製代碼

上述方式在筆者在開發認知裏以爲太繁瑣,實質上將全部節點當成文本排列,也是醉了。筆者推薦使用flex佈局完成上述佈局,flex佈局做爲目前最多見的佈局方式,相信也不用筆者多說。如下實現方式不知你們是否見過呢?在移動端上體驗會更棒喔!

.horizontal-layout.flex {
    ul {
        display: flex;
        flex-wrap: nowrap;
        justify-content: space-between;
    }
    li {
        flex-shrink: 0;
        flex-basis: 90px;
    }
}
複製代碼

凸顯佈局

凸顯佈局指容器內節點以同一方向排列且存在一個節點在某個方向上較突出的佔位佈局。該佈局描述起來可能比較拗口,直接看如下效果吧,這是一個橫向列表,節點從左往右排列,最右邊的節點特別突出。這就是凸顯佈局的特徵,凸顯的節點可在凸顯佈局任意位置,上下左右左上左下右上右下都行。

凸顯佈局

這裏巧妙運用margin-*:auto實現了凸顯佈局。相信你們實現水平居中固定寬度的塊元素都會使用margin:0 auto

在此一樣原理,當節點聲明margin-*:auto時,瀏覽器會自動計算剩餘空間並將該值賦值給該節點。在使用該技巧時必須基於flex佈局

凸顯佈局-左重右輕

凸顯佈局-左輕右重

凸顯佈局-上重下輕

凸顯佈局-上輕下重

<ul class="highlight-layout">
    <li>Alibaba</li>
    <li>Tencent</li>
    <li>Baidu</li>
    <li>Jingdong</li>
    <li>Ant</li>
    <li>Netease</li>
</ul>
複製代碼
.highlight-layout {
    display: flex;
    align-items: center;
    padding: 0 10px;
    width: 600px;
    height: 60px;
    background-color: #3c9;
    li {
        padding: 0 10px;
        height: 40px;
        background-color: #3c9;
        line-height: 40px;
        font-size: 16px;
        color: #fff;
    }
    &.left li {
        &:not(:first-child) {
            margin-left: 10px;
        }
        &:last-child {
            margin-left: auto;
        }
    }
    &.right li {
        &:not(:last-child) {
            margin-right: 10px;
        }
        &:first-child {
            margin-right: auto;
        }
    }
    &.top {
        flex-direction: column;
        width: 120px;
        height: 400px;
        li {
            &:not(:first-child) {
                margin-top: 10px;
            }
            &:last-child {
                margin-top: auto;
            }
        }
    }
    &.bottom {
        flex-direction: column;
        width: 120px;
        height: 400px;
        li {
            &:not(:last-child) {
                margin-bottom: 10px;
            }
            &:first-child {
                margin-bottom: auto;
            }
        }
    }
}
複製代碼

在此還有一個小技巧,那就是:not():first-child:last-child的巧妙運用。這樣組合讓特殊位置的節點直接減小屬性覆蓋的問題,不只易讀還能裝逼。

  • :not(:first-child):排除首節點,其餘節點都使用某些樣式
  • :not(:last-child):排除尾節點,其餘節點都使用某些樣式

間距佈局

間距佈局指容器內節點從左往右從上往下排列且以特定間距間隔的佔位佈局。間距佈局常見於各大列表,是筆者認爲最重要的佈局之一。爲什麼如此簡單的佈局仍是花費一些篇幅講解呢?最近筆者查看了Github上不少與間隔佈局相關的CSS代碼,雖然總體效果看上去無大礙,但margin/padding結構選擇器卻亂用,所以筆者想從零到一糾正間距佈局的正確編碼方式。

在進入編碼環節前,筆者想重點講解:nth-child()的點睛之筆。大部分同窗可能只認得:nth-child(n):nth-child(2n-1):nth-child(2n):nth-child(xn)的平常用法,但其實還有一些你可能未見過的用法。在此筆者藉此次機會將:nth-child()全部用法總結下,n/x/y表明正整數,最小值爲1

  • :nth-child(n):選擇第n個元素
  • :nth-child(odd):選擇奇數位置元素,至關於:nth-child(2n-1)
  • :nth-child(even):選擇偶數位置元素,至關於:nth-child(2n)
  • :nth-child(xn):選擇第x*n個元素
  • :nth-child(x-n):選擇前x個元素
  • :nth-child(y-n):nth-child(n+x):選擇第x~y個元素

分析間距佈局的一切特色,捕獲特徵頗有利於將特徵轉換成CSS代碼。

  • A:肯定容器間的間距,使用margin聲明
  • B:肯定容器內的間距,使用padding聲明,後續方便聲明background-color(該步驟很易與上一步驟混淆,請特別注意)
  • C:肯定靠近容器邊界的節點與容器的間距,使用padding聲明容器而不是使用margin聲明節點(該步驟說明上一步驟的處理結果)
  • D:確認每行節點的左右間距,使用margin-left/margin-right(二選一)聲明節點
  • E:確認最左列節點或最右列節點與容器的間距,使用margin-left:0聲明最左列節點或使用margin-right:0聲明最右列節點
  • F:除了首行節點,使用margin-top聲明其他節點
  • G:若但願容器頂部底部留空,使用border-top/border-bottom代替padding-top/padding-bottom

所有步驟串聯起來理解可能會產生混亂,但結合如下代碼理解相信就能很快熟悉。以一行排列3個節點總共8個節點爲例,最終效果爲三行三列。

間距佈局

間距佈局-留空

<ul class="spacing-layout">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
    <li>6</li>
    <li>7</li>
    <li>8</li>
</ul>
複製代碼
.spacing-layout {
    display: flex;
    overflow: auto;
    flex-wrap: wrap;
    margin-top: 20px; // 對應A
    padding: 20px; // 對應B和C
    // padding-top: 0; // 對應G
    // padding-bottom: 0; // 對應G
    // border-top: 20px solid #f66; // 對應G
    // border-bottom: 20px solid #f66; // 對應G
    width: 700px; // 稍微留空用於顯示滾動條
    height: 400px;
    background-color: #f66;
    li {
        width: 200px;
        height: 200px;
        background-color: #66f;
        line-height: 200px;
        text-align: center;
        font-size: 20px;
        color: #fff;
        &:not(:nth-child(3n)) {
            margin-right: 20px; // 對應D和E
        }
        &:nth-child(n+4) {
            margin-top: 20px; // 對應F
        }
    }
}
複製代碼

空載佈局

空載佈局指容器內無任何節點時使用其餘形式代替的佔位佈局。還有使用JS判斷列表集合爲空時顯示佔位符嗎?相信不少使用MVVM框架開發的同窗都會使用條件判斷的方式渲染虛擬DOM,若列表長度不爲0則渲染列表,不然渲染佔位符。

<div>
    <ul v-if="list.length">...</ul>
    <div v-esle>Empty</div>
</div>
複製代碼

然而CSS提供一個空判斷的選擇器:empty,這應該不多同窗會注意到吧。

:empty做用於無子節點的節點,該子節點也包括行內匿名盒(單獨的文本內容)。如下三種狀況均視爲非空狀態,若不出現這三種狀態則視爲空狀態,此時:empty纔會觸發。

  • 僅存在節點:<div><p>CSS</p></div>
  • 僅存在文本:<div>CSS</div>
  • 同時存在節點和文本:<div>Hello <p>CSS</p></div>

空載佈局

<ul class="empty-layout">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
    <li>6</li>
    <li>7</li>
    <li>8</li>
    <li>9</li>
</ul>
<ul class="empty-layout"></ul>
複製代碼
$empty: "https://yangzw.vip/img/empty.svg";
.empty-layout {
    overflow: auto;
    width: 200px;
    height: 150px;
    outline: 1px solid #3c9;
    &:empty {
        display: flex;
        justify-content: center;
        align-items: center;
        background: url($empty) no-repeat center/100px auto;
        &::after {
            margin-top: 90px;
            font-weight: bold;
            content: "沒錢就沒數據";
        }
    }
    li {
        padding: 0 10px;
        height: 30px;
        background-color: #09f;
        line-height: 30px;
        color: #fff;
        &:nth-child(even) {
            background-color: #f90;
        }
    }
}
複製代碼

另外還存在一種特殊的空載佈局,就是不作任何處理。這樣最終渲染的DOM只有容器,若已聲明margin/padding/border但未聲明width/height的狀況下,就會出現如下佔位效果。無任何子節點的容器還聲明着margin/padding/border,看着都尷尬。

空載佈局-尷尬

沒事,:empty幫你搞掂!對於無任何子節點的容器直接聲明display:none解決全部無效佔位,固然也可做用於指定節點。一招制敵,勁!

// 做用於全部節點
:empty {
    display: none;
}
// 做用於指定節點
.empty-layout:empty {
    display: none;
}
複製代碼

多格佈局

多格佈局指容器內節點以動態數量的格子形式排列的佔位佈局。微信朋友圈的相冊就是最多見的多格佈局了,當單張照片排列、兩張照片排列、三張照片排列等等,每種狀況下照片的尺寸均可能不一致。筆者製做了一個動態多格相冊懷念我家狗狗AB。你們感覺下純CSS實現動態數量的多格佈局吧。

在此留個懸念,不講解如何實現,看看你們能不能根據筆者列出的提示嘗試將該效果復原。主要原理是根據結構選擇器限制節點範圍實現,在本文也可找到原理的答案喔!記得實現完再看如下源碼哈!

多格佈局

<ul class="multigrid-layout">
    <li class="item"><img src="https://static.yangzw.vip/codepen/ab-3.jpg"></li>
    <li class="item"><img src="https://static.yangzw.vip/codepen/ab-3.jpg"></li>
    <li class="item"><img src="https://static.yangzw.vip/codepen/ab-3.jpg"></li>
    <li class="item"><img src="https://static.yangzw.vip/codepen/ab-3.jpg"></li>
    <li class="item"><img src="https://static.yangzw.vip/codepen/ab-3.jpg"></li>
    <li class="item"><img src="https://static.yangzw.vip/codepen/ab-3.jpg"></li>
    <li class="item"><img src="https://static.yangzw.vip/codepen/ab-3.jpg"></li>
    <li class="item"><img src="https://static.yangzw.vip/codepen/ab-3.jpg"></li>
    <li class="item"><img src="https://static.yangzw.vip/codepen/ab-3.jpg"></li>
</ul>
複製代碼
@mixin square($count: 2) {
    $length: calc((100% - #{$count} * 10px) / #{$count});
    width: $length;
    height: $length;
}
.multigrid-layout {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-start;
    align-content: flex-start;
    padding: 5px;
    border: 1px solid #ccc;
    border-radius: 5px;
    width: 400px;
    height: 400px;
    li {
        display: flex;
        overflow: hidden;
        justify-content: center;
        margin: 5px;
        background-color: #f0f0f0;
        @include square(3);
    }
    img {
        width: 100%;
        height: 100%;
        object-fit: cover;
    }
}
// 一個元素
.item:only-child {
    border-radius: 10px;
    width: auto;
    max-width: 80%;
    height: auto;
    max-height: 80%;
}
// 兩個元素
.item:first-child:nth-last-child(2),
.item:first-child:nth-last-child(2) ~ .item:nth-child(2) {
    @include square(2);
}
.item:first-child:nth-last-child(2) {
    border-radius: 10px 0 0 10px;
}
.item:first-child:nth-last-child(2) ~ .item:nth-child(2) {
    border-radius: 0 10px 10px 0;
}
// 三個元素
.item:first-child:nth-last-child(3),
.item:first-child:nth-last-child(3) ~ .item:nth-child(2),
.item:first-child:nth-last-child(3) ~ .item:nth-child(3) {
    @include square(2);
}
.item:first-child:nth-last-child(3) {
    border-top-left-radius: 10px;
}
.item:first-child:nth-last-child(3) ~ .item:nth-child(2) {
    border-top-right-radius: 10px;
}
.item:first-child:nth-last-child(3) ~ .item:nth-child(3) {
    border-bottom-left-radius: 10px;
}
// 四個元素
.item:first-child:nth-last-child(4),
.item:first-child:nth-last-child(4) ~ .item:nth-child(2),
.item:first-child:nth-last-child(4) ~ .item:nth-child(3),
.item:first-child:nth-last-child(4) ~ .item:nth-child(4) {
    @include square(2);
}
.item:first-child:nth-last-child(4) {
    border-top-left-radius: 10px;
}
.item:first-child:nth-last-child(4) ~ .item:nth-child(2) {
    border-top-right-radius: 10px;
}
.item:first-child:nth-last-child(4) ~ .item:nth-child(3) {
    border-bottom-left-radius: 10px;
}
.item:first-child:nth-last-child(4) ~ .item:nth-child(4) {
    border-bottom-right-radius: 10px;
}
// 五個元素
.item:first-child:nth-last-child(5) {
    border-top-left-radius: 10px;
}
.item:first-child:nth-last-child(5) ~ .item:nth-child(3) {
    border-top-right-radius: 10px;
}
.item:first-child:nth-last-child(5) ~ .item:nth-child(4) {
    border-bottom-left-radius: 10px;
}
// 六個元素
.item:first-child:nth-last-child(6) {
    border-top-left-radius: 10px;
}
.item:first-child:nth-last-child(6) ~ .item:nth-child(3) {
    border-top-right-radius: 10px;
}
.item:first-child:nth-last-child(6) ~ .item:nth-child(4) {
    border-bottom-left-radius: 10px;
}
.item:first-child:nth-last-child(6) ~ .item:nth-child(6) {
    border-bottom-right-radius: 10px;
}
// 七個元素
.item:first-child:nth-last-child(7) {
    border-top-left-radius: 10px;
}
.item:first-child:nth-last-child(7) ~ .item:nth-child(3) {
    border-top-right-radius: 10px;
}
.item:first-child:nth-last-child(7) ~ .item:nth-child(7) {
    border-bottom-left-radius: 10px;
}
// 八個元素
.item:first-child:nth-last-child(8) {
    border-top-left-radius: 10px;
}
.item:first-child:nth-last-child(8) ~ .item:nth-child(3) {
    border-top-right-radius: 10px;
}
.item:first-child:nth-last-child(8) ~ .item:nth-child(7) {
    border-bottom-left-radius: 10px;
}
// 九個元素
.item:first-child:nth-last-child(9) {
    border-top-left-radius: 10px;
}
.item:first-child:nth-last-child(9) ~ .item:nth-child(3) {
    border-top-right-radius: 10px;
}
.item:first-child:nth-last-child(9) ~ .item:nth-child(7) {
    border-bottom-left-radius: 10px;
}
.item:first-child:nth-last-child(9) ~ .item:nth-child(9) {
    border-bottom-right-radius: 10px;
}
複製代碼

總結

不少同窗可能以爲CSS很簡單,但真正玩起來也能與JS有得一比。筆者從事前端領域多年,一直致力於CSS技術的研究與應用,固然真的不是爲了玩,而是在玩的過程裏將實踐到的知識充分應用於工做上。

JS重要但CSS一樣重要,但願喜歡CSS的同窗多多關注筆者,相信你必定會有更多CSS方面的收穫。在你不太願意學習CSS時,請瀏覽如下網站,相信你會有不一樣的體驗。

  • 我的官網:暫時支持PC端瀏覽,拒絕支持IExplorer
  • 特效專輯:暫時支持PC端瀏覽,拒絕支持IExplorer,查看源碼請戳這裏

筆者更多的CSS開發經驗已撰寫成掘金小冊《玩轉CSS的藝術之美》,做爲一本小衆的小冊同時也是掘金社區裏惟一一本關於CSS的小冊,相信關注CSS的你必定會喜歡。筆者已向小冊姐姐申請了100份小冊6折優惠碼OGecoefC,喜歡CSS的同窗可瞭解下喔。

筆者往期過萬閱讀量的掘金爆文

結語

❤️關注+點贊+收藏+評論+轉發❤️,原創不易,鼓勵筆者創做更多高質量文章

關注公衆號IQ前端,一個專一於CSS/JS開發技巧的前端公衆號,更多前端小乾貨等着你喔

  • 關注後回覆資料免費領取學習資料
  • 關注後回覆進羣拉你進技術交流羣
  • 歡迎關注IQ前端,更多CSS/JS開發技巧只在公衆號推送
相關文章
相關標籤/搜索