做者:JowayYoung
倉庫:Github、CodePen
博客:官網、掘金、思否、知乎
公衆號:IQ前端
特別聲明:原創不易,未經受權不得轉載或抄襲,如需轉載可聯繫筆者受權css
前段時間筆者收到可愛美的小冊姐姐的邀請,作了人生首次直播分享。分享主題是《玩轉CSS的藝術之美》,跟筆者在9月底發佈的掘金小冊同名。html
9月底發佈的玩轉CSS的藝術之美,首日預售就達到709本
,預售僅三日就破1000本
。這也讓筆者感到驚訝,沒想到CSS技術仍是那麼受倔友們的歡迎,讓筆者以爲熬夜半年寫這本小冊仍是值得的,畢竟能將本身的學習心路分享出去,讓更多同窗學到更多東西,也是一件值得開心的事情。前端
因爲首次作直播分享,感受比較緊張,家裏網絡不是特別好,還有其餘緣由,致使認真準備的內容未在預料時間內完成分享,所以經過本文未來不及分享的內容整理出來。git
對分享內容感興趣的同窗可關注筆者的公衆號IQ前端,回覆CSSPPT
下載分享PPT
。分享內容包含歷史背景、概念原理和開發技巧三節。第一二節比較無聊,可自行查看PPT,在此就很少說了。主要是第三節的乾貨,是筆者認真準備了好幾天的內容,每一個主題都會有對應的源碼及其效果。github
筆者選擇了一些經常使用甚至有些小冊都未說起到的乾貨做爲分享內容,相信這些內容能幫助同窗們在短時間內提高CSS編碼素質,實現一些看似只能由JS才能實現的效果。算法
準備工做segmentfault
整個分享過程不搞那些亂七八糟的環境搭建。既然只玩CSS,那只有html文件
和css文件
就足夠了。另外還需一個瀏覽器Chrome
和一個編輯器VSCode
。設計模式
VSCode
還需安裝Live Sass Compiler和Live Server兩個插件。Live Sass Compiler
用於實時編譯sass/scss文件
爲css文件
。Live Server
用於啓動具備實時刷新功能的本地開發服務器,以處理靜態頁面和動態頁面。瀏覽器
新建index.html
和index.scss
。爲了使各大瀏覽器默認樣式一致,還需引入一個磨平瀏覽器默認樣式的css文件
,同窗們可下載筆者寫好的reset.css到本地目錄裏。sass
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, minimum-scale=1, maximum-scale=1">
<title>Hello CSS</title>
<link rel="stylesheet" href="./reset.css">
<link rel="stylesheet" href="./index.css">
</head>
<body class="flex-ct-x">
<!-- ... -->
</body>
</html>
複製代碼
body {
overflow: hidden;
height: 100vh;
}
複製代碼
上述文件骨架完成後,打開index.scss
,按F1
或Cmd + Shift + P
打開命令面板,輸入Watch Sass
監聽index.scss
並生成index.css
,再輸入Open With Live Server
啓動本地開發服務器並打開瀏覽器。到此爲止就完成了全部準備工做了。
不少CSS編碼習慣都是清一色的類而無相應的選擇器,層層嵌套的標籤都包含至少一個類。選擇器
和類
對比起來性能上確實沒後者那麼好,但現在瀏覽器對於CSS的解析速度已獲得大大的提高,徹底可忽略選擇器那丁點的性能問題。
但是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體系裏的核心,使用選擇器能帶來如下好處。
sass/less
編寫屬性時結構會更清晰易讀,減小不少無用或少用的類,保持css文件
的整潔性和觀賞性掌握一些經常使用佈局是一個前端必不可少的技能。養成看設計圖就能大概規劃出總體佈局的前提是必須熟悉這些經常使用佈局的特色與構造。曾經需結合不少屬性才能完成一個佈局,現在在現代屬性的加持下能更好地快速實現各類佈局,節約更多時間去作更重要的事情。
經典的全屏佈局由頂部
、底部
和主體
三部分組成,其特色爲三部分左右滿屏拉伸
、頂部底部高度固定
和主體高度自適應
。該佈局很常見,也是大部分Web應用主體的主流佈局。一般使用<header>
、<footer>
和<main>
三個標籤語義化排版,<main>
內還可插入<aside>
側欄或其餘語義化標籤。
<div class="fullscreen-layout">
<header></header>
<main></main>
<footer></footer>
</div>
複製代碼
position + left/right/top/bottom
三部分統一聲明left:0
和right:0
將其左右滿屏拉伸;頂部和底部分別聲明top:0
和bottom:0
將其吸頂和吸底,並聲明倆高度爲固定值;將主體的top
和bottom
分別聲明爲頂部高度和底部高度。經過絕對定位的方式將三部分固定在特定位置,使其互不影響。
.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區域
與外界隔離。BFC
相關詳情請查看小冊第4章盒模型。
.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列布局一致,可能就是一些細節需注意。
聖盃佈局
和雙飛翼佈局
在大致相同下也存在一點不一樣,區別在於雙飛翼佈局
中間列需插入一個子節點。在常規的實現方式中也是在這個中間列裏作文章,如何使中間列內容不被左右列遮擋
。
float
和margin負值
將其拉回與中間列處在同一水平線上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;
}
}
複製代碼
居中佈局由父容器
與若干個子容器
組成,子容器在父容器中橫向排列或豎向排列且呈水平居中或垂直居中。居中佈局是一個很經典的問題,因此筆者在小冊中羅列了全部居中佈局方式,詳情請查看小冊第6章佈局方式。
在此直接上一個目前最簡單最高效的居中方式。display:flex
與margin: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;
}
}
複製代碼
盒模型從理論上來講是一個標準的矩形,很難將其聯想到基於盒模型繪製一個三角形。固然存在一個叫clip-path
的屬性,可繪製三角形,鑑於其兼容性較差一般不會大範圍使用它繪製三角形。
不少同窗都會基於盒模型編寫三角形,但大部分都是複製粘貼的操做。從原理上正確理解其成因,才能無需複製粘貼就能駕輕就熟地繪製各類三角形。如下從零到一熟悉一次繪製三角形的原理。
繪製一個邊框分別爲四種顏色的正方形。
<div class="triangle"></div>
複製代碼
.triangle {
border: 50px solid;
border-left-color: #f66;
border-right-color: #66f;
border-top-color: #f90;
border-bottom-color: #09f;
width: 200px;
height: 200px;
}
複製代碼
分別將width
和height
累減到0
,發現正方形由四個不一樣顏色的等腰三角形組成。
.triangle {
border: 50px solid;
border-left-color: #f66;
border-right-color: #66f;
border-top-color: #f90;
border-bottom-color: #09f;
width: 0;
height: 0;
}
複製代碼
嘗試將右邊框顏色聲明爲透明,會發現右邊框隱藏起來。
.triangle {
border: 50px solid;
border-left-color: #f66;
border-right-color: transparent;
border-top-color: #f90;
border-bottom-color: #09f;
width: 0;
height: 0;
}
複製代碼
一樣原理,將上邊框顏色和下邊框顏色同時聲明爲透明,就會獲得一個指向右邊的三角形。
.triangle {
border: 50px solid;
border-left-color: #f66;
border-right-color: transparent;
border-top-color: transparent;
border-bottom-color: transparent;
width: 0;
height: 0;
}
複製代碼
可簡寫成如下代碼。細心的同窗可能還會發現三角形的寬是高的2倍,而高正好是邊框寬度border-width
。從中可得出一個技巧:若繪製三角形的方向爲左右上下,則將四條邊框顏色聲明爲透明且將指定方向的反方向的邊框着色,便可獲得所需方向的三角形。
.triangle {
border: 50px solid transparent;
border-left-color: #f66;
width: 0;
height: 0;
}
複製代碼
若繪製左上角、左下角、右上角或右下角的三角形,使用上述技巧就沒法完成了。可稍微變通思惟,其實指向左上角的三角形是由左邊框和上邊框組成,其餘三角形也是如此。
.triangle {
border: 50px solid transparent;
border-left-color: #f66;
border-top-color: #f66;
width: 0;
height: 0;
}
複製代碼
基於上述原理,可駕輕就熟繪製出左右上下、左上角、左下角、右上角和右下角的三角形了,再結合絕對定位(position/left/right/top/bottom
)、邊距(margin/margin-*
)或變換(transform
)調整位置便可。
.triangle {
border: 50px solid transparent;
width: 0;
height: 0;
&.left {
border-right-color: #f66;
}
&.right {
border-left-color: #f66;
}
&.top {
border-bottom-color: #f66;
}
&.bottom {
border-top-color: #f66;
}
&.left-top {
border-left-color: #f66;
border-top-color: #f66;
}
&.left-bottom {
border-left-color: #f66;
border-bottom-color: #f66;
}
&.right-top {
border-right-color: #f66;
border-top-color: #f66;
}
&.right-bottom {
border-right-color: #f66;
border-bottom-color: #f66;
}
}
複製代碼
變量又名自定義屬性,指可在整個文檔中重複使用的值。它由自定義屬性--var
和函數var()
組成,var()
用於引用自定義屬性。使用變量能帶來如下好處。
同時變量也是瀏覽器原生特性
,無需通過任何轉譯可直接運行,也是DOM對象,極大便利了CSS與JS間的聯繫。變量除了具有簡潔性和複用性,在重構組件樣式時能讓代碼更易控制,同時還隱藏了一個強大的技巧,那就是與calc()
結合使用。
看看一個簡單的例子。一個條形加載條一般由幾條線條組成,每條線條對應一個存在不一樣時延的相同動畫,經過時間差運行相同動畫,從而產生加載效果。估計大部分同窗可能會把代碼編寫成如下形式。
<ul class="strip-loading">
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
複製代碼
.strip-loading {
display: flex;
justify-content: center;
align-items: center;
width: 200px;
height: 200px;
li {
border-radius: 3px;
width: 6px;
height: 30px;
background-color: #f66;
animation: beat 1s ease-in-out infinite;
& + li {
margin-left: 5px;
}
&:nth-child(2) {
animation-delay: 200ms;
}
&:nth-child(3) {
animation-delay: 400ms;
}
&:nth-child(4) {
animation-delay: 600ms;
}
&:nth-child(5) {
animation-delay: 800ms;
}
&:nth-child(6) {
animation-delay: 1s;
}
}
}
@keyframes beat {
0%,
100% {
transform: scaleY(1);
}
50% {
transform: scaleY(.5);
}
}
複製代碼
分析代碼發現,每一個<li>
只是animation-delay
不一樣,其他代碼則徹底相同,換成其餘相似的List集合,那豈不是有10個<li>
就寫10個:nth-child(n)
。顯然這種方式不靈活也不易封裝成組件,若能像JS那樣封裝成一個函數,並根據參數輸出不一樣樣式效果,那就更棒了。
對於HTML部分的修改,讓每一個<li>
擁有一個本身做用域下的變量。對於CSS部分的修改,就需分析哪些屬性是隨着index
遞增而發生規律變化的,對規律變化的部分使用變量表達式代替便可。固然如下<li style="--line-index: n;"></li>
可用React JSX
或Vue Template
的遍歷語法編寫。
<ul class="strip-loading">
<li style="--line-index: 1;"></li>
<li style="--line-index: 2;"></li>
<li style="--line-index: 3;"></li>
<li style="--line-index: 4;"></li>
<li style="--line-index: 5;"></li>
<li style="--line-index: 6;"></li>
</ul>
複製代碼
.strip-loading {
display: flex;
justify-content: center;
align-items: center;
width: 200px;
height: 200px;
li {
--time: calc((var(--line-index) - 1) * 200ms);
border-radius: 3px;
width: 6px;
height: 30px;
background-color: #f66;
animation: beat 1.5s ease-in-out var(--time) infinite;
& + li {
margin-left: 5px;
}
}
}
@keyframes beat {
0%,
100% {
transform: scaleY(1);
}
50% {
transform: scaleY(.5);
}
}
複製代碼
代碼中的變量--line-index
和--time
使每一個<li>
擁有一個屬於本身的做用域。例如第二個<li>
,--line-index
的值爲2,--time
的計算值爲200ms
,換成第三個<li>
後這兩個值又會不一樣了。這就是變量的做用範圍所致(在當前節點塊做用域及其子節點塊做用域下有效
)。
calc(var(--line-index) * 200ms)
就像一個JS函數,在當前節點的做用域上讀取相應的變量,從而計算出具體數值並交由瀏覽器初始化。從中可得出一個技巧:List集合裏具有與索引遞增相關的屬性值均可用變量與calc()
結合使用生成出來。
還記得小學時代學習圓周率的場景嗎,曾經有學者將一個圓形劃分爲不少很小的矩形,若這些矩形劃分得足夠細,那麼也可拼在一塊兒變成一個圓形。
將圓形劃分爲360個小矩形且每一個矩形相對於父容器絕對定位,聲明transform-origin
爲center bottom
將小矩形的變換基準變動爲最底部最中間
,每一個小矩形按照遞增角度順時針旋轉N度,就會造成一個圓形。此時按照遞增角度調整小矩形的背景色相,就會看到意想不到的漸變效果了。
--Θ:calc(var(--line-index) / var(--line-count) * 1turn)
filter:hue-rotate(var(--Θ))
transform:rotate(var(--Θ))
若將小矩形的尺寸和數量設置更細更多,總體的漸變效果就會更均勻。
<ul class="gradient-circular" style="--line-count: 360;">
<li style="--line-index: 1;"></li>
...
<li style="--line-index: 360;"></li>
<!-- 360個<li>,可用模板語法生成 -->
</ul>
複製代碼
.gradient-circular {
position: relative;
width: 4px;
height: 200px;
li {
--Θ: calc(var(--line-index) / var(--line-count) * 1turn);
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: #3c9;
filter: hue-rotate(var(--Θ));
transform-origin: center bottom;
transform: rotate(var(--Θ));
}
}
複製代碼
有時爲了實現某個效果而往頁面裏反覆添加標籤變得很繁瑣,添加太多標籤反而很差處理而變得難以維護。此時會引入僞元素
這個概念解決上述問題。正是僞元素
能解決一些可不添加其餘標籤而起到佔位做用,筆者才稱僞元素
爲「添油加醋」
。
上述選擇器分類有說起僞元素
,狹義上來講選擇器除了僞元素
,其餘都是僞類
。僞元素
和僞類
雖然都是選擇器,但它們仍是存在一絲絲的差異。
僞元素
一般是一些實體選擇器,選擇知足指定條件的DOM,例如::selection
、::first-letter
和::first-line
僞類
一般是一些狀態選擇器,選擇處於特定狀態的DOM,例如:hover
、:focus
和:checked
僞元素指頁面裏不存在的元素。僞元素
在HTML代碼裏未聲明卻能正常顯示,在頁面渲染時看到這些原本不存在的元素髮揮着重要做用。:before
和:after
是兩個很重要的僞元素
,早在CSS2就出現了。
起初僞元素
的前綴使用單冒號語法。隨着CSS改革,僞元素
的前綴被修改爲雙冒號語法,:before/:after
今後變成::before/::after
,用來區分僞類
,未說起的僞元素
同理。若兼容低版本瀏覽器,還需使用:before
和:after
。
二者最主要的區別就是僞類
使用單冒號語法,僞元素
使用雙冒號語法。固然筆者仍是提倡同窗們使用單冒號語法標記僞類
,使用雙冒號語法標記僞元素
,這樣在代碼形式上就能一眼區分出來。
::before
和::after
的使用場景不少,也是筆者着重研究的技巧之一。::before/::after
必須結合content
使用,一般用做修飾節點,爲節點插入一些多餘的東西,但又不想內嵌一些其餘標籤。若插入2個如下(包含2個)的修飾,建議使用::before/::after
。
說時遲那時快,立馬結合上述繪製三角形的原理繪製一個經常使用的氣泡對話框,圓滾滾的身子帶上一個三角形的尾巴。氣泡對話框的身板就是一個圓角矩形,可用<div>
直接繪製,小尾巴是一個三角形,可用::after
佔位並繪製。這樣就無需在<div>
裏添加一個<i>
繪製小尾巴了。
<div class="bubble-box">iCSS</div>
複製代碼
.bubble-box {
position: relative;
border-radius: 5px;
width: 200px;
height: 50px;
background-color: #f90;
line-height: 50px;
text-align: center;
font-size: 20px;
color: #fff;
&::after {
position: absolute;
left: 100%;
top: 50%;
margin-top: -5px;
border: 5px solid transparent;
border-left-color: #f90;
content: "";
}
}
複製代碼
從中可得出一個技巧:若爲節點作一些修飾卻不想插入其餘標籤,可用::before和::after代替,但適用於2個佔位如下。其實這個也不算什麼特別技巧,只是不少同窗都不會去注意這種用法,有需求都是直接添加標籤。也許如下說起的障眼法
和內容插入
會讓同窗們對僞元素
刮目相看。
上述使用::after
簡單地繪製氣泡對話框的尾巴,然而複雜一點的帶邊框氣泡對話框可否也使用僞元素
繪製呢。看到這裏先不要往下看代碼,自行思考1分鐘想一想實現方法。
答案固然是可行的。如下是整個帶邊框氣泡對話框的拆解,總體由三部分組成:帶邊框圓角矩形、黑色三角形、橙色三角形。先將兩個三角形錯位疊加生成一個箭頭狀的圖形,再將該圖形疊加到帶邊框圓角矩形的右邊,最後將黑色三角形着色成白色,就能獲得上圖的帶邊框氣泡對話框了。
<div class="bubble-empty-box">iCSS</div>
複製代碼
.bubble-empty-box {
position: relative;
border: 2px solid #f90;
border-radius: 5px;
width: 200px;
height: 50px;
line-height: 46px;
text-align: center;
font-size: 20px;
color: #f90;
&::before {
position: absolute;
left: 100%;
top: 50%;
margin: -5px 0 0 2px;
border: 5px solid transparent;
border-left-color: #f90;
content: "";
}
&::after {
position: absolute;
left: 100%;
top: 50%;
margin-top: -4px;
border: 4px solid transparent;
border-left-color: #fff;
content: "";
}
}
複製代碼
總體實現思路就是一種障眼法,正確來講就是將圖形錯位疊加產生另外一種效果,在平面設計中叫作佔位疊加。有了這種設計思想,其實能使用CSS創造出不少意向不到的障眼法效果。
當你碰見心儀妹紙時,內心噗通噗通地跳動,此時此刻可用純CSS描繪你的心情。使用單個<div>
結合::before
和::after
,經過錯位疊加的方式生成一個心形。在疊加前看看如下圖形,是否是發現很像米老鼠呢。
<div>
形狀爲正方形
並以中心順時針旋轉45deg
::before
和::after
繼承<div>
尺寸並分別絕對定位到左上角和右上角::before
和::after
的圓角率爲100%
<div class="heart-shape"></div>
複製代碼
.heart-shape {
position: relative;
width: 200px;
height: 200px;
background-color: #f66;
transform: rotate(45deg);
&::before,
&::after {
position: absolute;
left: 0;
top: 0;
border-radius: 100%;
width: 100%;
height: 100%;
background-color: #f66;
content: "";
}
&::before {
transform: translateX(-50%);
}
&::after {
transform: translateY(-50%);
}
}
複製代碼
最後巧妙利用transform
將::before
和::after
平移到相應位置產生疊加錯覺。這時分別對::before
和::after
着色,看看其中的奧祕。
在這個基礎上來一個更高級的玩法,添加漸變效果讓心形變得更麼麼噠。
<div>
從上到下(實際效果是從右上角到左下角)漸變着色::before
從旋轉後的<div>
X軸往左平移過去,因此其着色效果與<div>
一致::after
從旋轉後的<div>
Y軸往上平移過去,因此其中線位置漸變着色必須與<div>
頂部漸變着色的顏色一致(具體往下分析)總體漸變效果的重點在::after
上,因爲::after
下半部疊加在<div>
上,因此下半部顏色必須透明,上半部底部(中線位置)漸變着色必須與<div>
頂部漸變着色的顏色一致,這樣才能作到無縫銜接。經過Windows系統
和MacOS系統
的測試,在Windows系統
下的透明漸變位置需在51%
的地方開始,這與屏幕設備的分辨率和廣色域有關。
最後爲了讓漸變心形看起來更具立體感,給它繪製個陰影吧。若以爲這個漸變更感心形很美,可隨手轉發給女朋友哇!
<div class="gradient-heart-shape"></div>
複製代碼
.gradient-heart-shape {
position: relative;
width: 200px;
height: 200px;
background-image: linear-gradient(to bottom, #09f, #f66);
box-shadow: 0 0 20px rgba(#000, .8);
transform: rotate(45deg);
&::before,
&::after {
position: absolute;
left: 0;
top: 0;
border-radius: 100%;
width: 100%;
height: 100%;
content: "";
}
&::before {
background-image: linear-gradient(to bottom, #09f, #f66);
transform: translateX(-50%);
}
&::after {
background-image: linear-gradient(to bottom, #3c9, #09f 50%, transparent 50%, transparent);
transform: translateY(-50%);
}
}
複製代碼
上述提到::before/::after
必須結合content
使用,那麼content
就真的只能插入普通字符串嗎?content
何止這麼簡單,如下推廣幾種少見但強大的內容插入技巧。經過這幾種技巧,就能很方便地將讀取到的數據動態插入到::before
或::after
中。
attr()
使用變量
和計數器
使用內容拼接
常規操做是content:"CSS"
,也可拼接多個字符串,有些同窗可能第一時間想起content:"Hello "+"CSS"
。拜託,這不是JS而是CSS,CSS字符串拼接固然有本身的規則。CSS字符串拼接既不能使用+
相連也可不用空格
間隔。
.elem {
content: "Hello ""CSS"; // 等價於"Hello " "CSS"
content: "Hello" attr(data-name); // 與attr()拼接
content: counter(progress) "%"; // 與counter()拼接
}
複製代碼
結合
attr()
使用
attr()
是一個被忽略的選擇器,它有着強大的屬性捕獲功能。有這麼一個場景,一個數據集合需遍歷到每一個DOM
上並把某個字段插入到其::after
上。這該怎麼辦,好像95%
的同窗都不會使用JS獲取節點的::before
或::after
。這時attr()
就派上用場了。
<li v-for="v in list" :key="v.id" :data-name="v.name">
複製代碼
li::after {
content: attr(data-name);
}
複製代碼
一行CSS代碼搞掂,還用什麼JS去獲取節點的::after
呢。固然content
和attr()
的使用場景不止那一點。
:hover
做用於鼠標懸浮的節點,是一個很好用的選擇器。在特定場景可代替mouseenter
和mouseleave
兩個鼠標事件,加上transtion
讓節點的動畫更絲滑。結合attr()
有一個很好用的場景,就是鼠標懸浮在某個節點上顯示提示浮層,提示浮層裏包含着該動做的文本。
data-*
:hover
attr()
獲取data-*
的內容data-*
的內容賦值到僞元素
的content
上<ul class="hover-tips">
<li data-name="姨媽紅"></li>
<li data-name="基佬紫"></li>
<li data-name="籮底橙"></li>
<li data-name="姣婆藍"></li>
<li data-name="大糞青"></li>
<li data-name="原諒綠"></li>
</ul>
複製代碼
$color-list: #f66 #66f #f90 #09f #9c3 #3c9;
.hover-tips {
display: flex;
justify-content: space-between;
width: 200px;
li {
position: relative;
padding: 2px;
border: 2px solid transparent;
border-radius: 100%;
width: 24px;
height: 24px;
background-clip: content-box;
cursor: pointer;
transition: all 300ms;
&::before,
&::after {
position: absolute;
left: 50%;
bottom: 100%;
opacity: 0;
transform: translate3d(0, -30px, 0);
transition: all 300ms;
}
&::before {
margin: 0 0 12px -35px;
border-radius: 5px;
width: 70px;
height: 30px;
background-color: rgba(#000, .5);
line-height: 30px;
text-align: center;
color: #fff;
content: attr(data-name);
}
&::after {
margin-left: -6px;
border: 6px solid transparent;
border-top-color: rgba(#000, .5);
width: 0;
height: 0;
content: "";
}
@each $color in $color-list {
$index: index($color-list, $color);
&:nth-child(#{$index}) {
background-color: $color;
&:hover {
border-color: $color;
}
}
}
&:hover {
&::before,
&::after {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
}
}
複製代碼
結合
變量
和計數器
使用
如今來玩高級一點的東西,先不作任何鋪墊,接着往下看便可,反正就是content
結合變量
和計數器
的使用場景。
筆者想作一個實時顯示進度的懸浮球,跟着筆者一塊兒敲代碼吧。先畫一個綠油油的波波。
<div class="state-ball">
<div class="wave"></div>
</div>
複製代碼
.state-ball {
overflow: hidden;
position: relative;
padding: 5px;
border: 3px solid #3c9;
border-radius: 100%;
width: 150px;
height: 150px;
background-color: #fff;
.wave {
position: relative;
border-radius: 100%;
width: 100%;
height: 100%;
background-image: linear-gradient(to bottom, #af8 13%, #3c9 91%);
}
}
複製代碼
進度一般都是從底部往頂部逐漸提高,可用::before
繪製一個圓形遮罩層,進度變化時將遮罩層一直往上提高產生障眼效果。提高過程可用絕對定位將遮罩層固定在底部,經過調整margin-bottom
平移遮罩層。
爲了方便演示,註釋父容器的overflow:hidden
,經過Chrome Devtools
微調margin-bottom
看看總體效果。後續記得將overflow:hidden
聲明回來。
.state-ball {
// overflow: hidden;
// ...
&::before {
position: absolute;
left: 50%;
bottom: 5px;
z-index: 9;
margin-left: -100px;
margin-bottom: 0;
border-radius: 100%;
width: 200px;
height: 200px;
background-color: #09f;
content: "";
}
// ...
}
複製代碼
爲了讓提高過程呈現動態效果,調整::before
的背景顏色和圓角率並追加一個旋轉動畫。
.state-ball {
// ...
&::before {
position: absolute;
left: 50%;
bottom: 5px;
z-index: 9;
margin-left: -100px;
margin-bottom: 0;
border-radius: 45%;
width: 200px;
height: 200px;
background-color: rgba(#fff, .5);
content: "";
animation: rotate 10s linear -5s infinite;
}
// ...
}
@keyframes rotate {
to {
transform: rotate(1turn);
}
}
複製代碼
爲了讓波浪呈現立體效果,追加::after
佔位並聲明總體樣式與::before
一致,在背景顏色、圓角率和動畫時延上略有差別便可。另外聲明::after
的margin-bottom
稍微比::before
高一點,這樣在旋轉過程當中能讓波浪產生動態的立體效果。
在提高過程當中,兩個遮罩層位移距離應該是一致的,因此可用變量計算公式表示且::after
比::before
高10px
。在這裏有個值得注意的地方,若變量結合calc()
使用,其結果必須帶上單位,以這兩條公式爲例,其變量初始值必須爲--offset:0px
,不能爲--offset:0
。
::before
:margin-bottom:var(--offset)
::after
:margin-bottom:calc(var(--offset) + 10px)
<div class="state-ball" style="--offset: 0px;">
<div class="wave"></div>
</div>
複製代碼
.state-ball {
// ...
&::before,
&::after {
position: absolute;
left: 50%;
bottom: 5px;
z-index: 9;
margin-left: -100px;
width: 200px;
height: 200px;
content: "";
}
&::before {
margin-bottom: var(--offset);
border-radius: 45%;
background-color: rgba(#fff, .5);
animation: rotate 10s linear -5s infinite;
}
&::after {
margin-bottom: calc(var(--offset) + 10px);
border-radius: 40%;
background-color: rgba(#fff, .8);
animation: rotate 15s infinite;
}
// ...
}
// ...
複製代碼
到此再優化一些細節,經過Chrome Devtools
檢查.wave
得知其尺寸爲134x134
,每次往上平移兩個僞元素只能1px
那樣遞增。如今想將其平移100次就能填充整個球體,那麼就需按照134/100
這個比例改造變量計算公式。
將--offset
聲明爲--offset:0
,取值區間在0~100
而不是0px~100px
。
::before
:margin-bottom:calc(var(--offset) * 1.34px)
::after
:margin-bottom:calc(var(--offset) * 1.34px + 10px)
<div class="state-ball" style="--offset: 0;">
<div class="wave"></div>
</div>
複製代碼
.state-ball {
// ...
&::before {
margin-bottom: calc(var(--offset) * 1.34px)
// ...
}
&::after {
margin-bottom: calc(var(--offset) * 1.34px + 10px);
// ...
}
// ...
}
// ...
複製代碼
如今已把位移距離控制在0~100
的比例了,那麼剩下步驟就是追加一個<div>
,使用其content
存放在offset
實時顯示進度了。
<div class="state-ball" style="--offset: 0;">
<div class="wave"></div>
<div class="progress"></div>
</div>
複製代碼
.state-ball {
// ...
.progress::after {
display: flex;
position: absolute;
left: 0;
top: 0;
z-index: 99;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
font-weight: bold;
font-size: 16px;
color: #09f;
content: var(--offset) "%";
}
}
// ...
複製代碼
但是發現無任何文本效果。狀況是這樣的,若變量是字符串類型可直接顯示,若變量是數值類型則需藉助counter()
顯示。而counter()
還需使用counter-reset
初始默認值,CSS計數器怎樣用在這裏就不講解了,感興趣的同窗可自行百度。
總體改造工程就這樣完成了,完整代碼以下。最後經過JS操做變量--offset
就能實時改變進度了。
<div class="state-ball" style="--offset: 0;">
<div class="wave"></div>
<div class="progress"></div>
</div>
複製代碼
.state-ball {
overflow: hidden;
position: relative;
padding: 5px;
border: 3px solid #3c9;
border-radius: 100%;
width: 150px;
height: 150px;
background-color: #fff;
&::before,
&::after {
position: absolute;
left: 50%;
bottom: 5px;
z-index: 9;
margin-left: -100px;
width: 200px;
height: 200px;
content: "";
}
&::before {
margin-bottom: calc(var(--offset) * 1.34px);
border-radius: 45%;
background-color: rgba(#fff, .5);
animation: rotate 10s linear -5s infinite;
}
&::after {
margin-bottom: calc(var(--offset) * 1.34px + 10px);
border-radius: 40%;
background-color: rgba(#fff, .8);
animation: rotate 15s infinite;
}
.wave {
position: relative;
border-radius: 100%;
width: 100%;
height: 100%;
background-image: linear-gradient(to bottom, #af8 13%, #3c9 91%);
}
.progress::after {
display: flex;
position: absolute;
left: 0;
top: 0;
z-index: 99;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
font-weight: bold;
font-size: 16px;
color: #09f;
content: counter(progress) "%";
counter-reset: progress var(--offset);
}
}
@keyframes rotate {
to {
transform: rotate(1turn);
}
}
複製代碼
:checked
做用於選項選中的表單節點,當<input>
的type
設置成radio
和checkbox
時可用。不少同窗都會使用input:checked + div {}
或input:checked ~ div {}
的操做模擬鼠標點擊事件。要讓input:checked + div {}
或input:checked ~ div {}
起效,其HTML結構必須像如下那樣。
<input type="radio">
<div></div>
複製代碼
這樣就沒法分離結構與行爲了,致使CSS必須跟着HTML走,只能使用絕對定位將<input>
固定到指定位置。使用<label>
綁定<input>
可將<input>
的鼠標選擇事件轉移到<label>
上,由<label>
控制選中狀態。那麼HTML結構可改成如下那樣,此時的<input>
可設置hidden
隱藏起來,不參與任何排版。
<input type="radio" id="btn" hidden>
<div>
<label for="btn">
</div>
複製代碼
<input>
使用id
與<label>
使用for
關聯起來,而hidden
使<input>
隱藏起來,不佔用頁面任何位置,此時<label>
放置在頁面任何位置都行。
input:checked + div {}
input:checked ~ div {}
複製代碼
有了這樣的思路,就很易實現一個純CSS標籤導航了。
<div class="tab-navbar">
<input id="tab1" type="radio" name="tabs" hidden checked>
<input id="tab2" type="radio" name="tabs" hidden>
<input id="tab3" type="radio" name="tabs" hidden>
<input id="tab4" type="radio" name="tabs" hidden>
<nav>
<label for="tab1">標題1</label>
<label for="tab2">標題2</label>
<label for="tab3">標題3</label>
<label for="tab4">標題4</label>
</nav>
<main>
<ul>
<li>內容1</li>
<li>內容2</li>
<li>內容3</li>
<li>內容4</li>
</ul>
</main>
</div>
複製代碼
.active {
background-color: #3c9;
color: #fff;
}
.tab-navbar {
display: flex;
overflow: hidden;
flex-direction: column-reverse;
border-radius: 10px;
width: 300px;
height: 400px;
input {
&:nth-child(1):checked {
& ~ nav label:nth-child(1) {
@extend .active;
}
& ~ main ul {
background-color: #f66;
transform: translate3d(0, 0, 0);
}
}
&:nth-child(2):checked {
& ~ nav label:nth-child(2) {
@extend .active;
}
& ~ main ul {
background-color: #66f;
transform: translate3d(-25%, 0, 0);
}
}
&:nth-child(3):checked {
& ~ nav label:nth-child(3) {
@extend .active;
}
& ~ main ul {
background-color: #f90;
transform: translate3d(-50%, 0, 0);
}
}
&:nth-child(4):checked {
& ~ nav label:nth-child(4) {
@extend .active;
}
& ~ main ul {
background-color: #09f;
transform: translate3d(-75%, 0, 0);
}
}
}
nav {
display: flex;
height: 40px;
background-color: #f0f0f0;
line-height: 40px;
text-align: center;
label {
flex: 1;
cursor: pointer;
transition: all 300ms;
}
}
main {
flex: 1;
ul {
display: flex;
flex-wrap: nowrap;
width: 400%;
height: 100%;
transition: all 300ms;
}
li {
display: flex;
justify-content: center;
align-items: center;
flex: 1;
font-weight: bold;
font-size: 20px;
color: #fff;
}
}
}
複製代碼
筆者曾經發表過一篇《純CSS免費讓掘金社區擁有暗黑模式切換功能》,探討了:checked
、+/~
與filter
的玩法,詳情請查看原文,在此就不囉嗦了。
來不及分享的內容,就用文章敘述完,那天看直播的掘友們,讓大家久等了。這幾年花了不少時間鑽研CSS,也許寫完本文就要對CSS告一段落了。雖然花了不少時間鑽研CSS,但也發佈了幾篇爆款CSS文章和一本CSS掘金小冊,也算是留下了這幾年的CSS學習成果了。
喜歡作的事情總不想留什麼遺憾。接下來也要將鑽研方向轉移到JS上了,仍是會像鑽研CSS那樣認真鑽研JS的性能優化
、設計模式
和數據算法
三大裝逼套件。指望在2021年能有新的突破吧,也感謝掘金社區讓我學習到別人的知識和別人學習到個人知識。
分享源碼存放在筆者的Github上,有須要的同窗可拷貝一份。還有就是筆者向可愛美的小冊姐姐要了100份玩轉CSS的藝術之美六折優惠碼WmOrR0hR,對該小冊感興趣的同窗可瞭解一下喲!
CSS是一門天馬行空的語言,說它簡單也行說它困難也行。想了解更多純CSS特效,可回看筆者往期文章,也可瀏覽筆者我的官網的純CSS特效專輯,保證知足你的眼球。
4200+
點贊量,125k+
閱讀量500+
點贊量,15k+
閱讀量100+
點贊量,5k+
閱讀量❤️關注+點贊+收藏+評論+轉發❤️,原創不易,鼓勵筆者創做更多高質量文章
關注公衆號IQ前端
,一個專一於CSS/JS開發技巧的前端公衆號,更多前端小乾貨等着你喔
資料
免費領取學習資料進羣
拉你進技術交流羣IQ前端
,更多CSS/JS開發技巧只在公衆號推送