前言
在 11 月的時候,twitter 上有個 #nodivember 的活動,在 CodePen 上也找得到許多作品,這個活動顧名思義就是 "no div",在 html 沒有任何 div 的情況下做出各種樣式,差不多是「給我一個 html,我就給你全世界」的感覺,程式碼大概長這樣:
這裡放幾個大神們的 CodePen:
其他還有許多厲害的作品,都是沒有使用任何 div 完成的,雖然實際工作上不太會被要求做一個沒有 div 的網站,但可以去思考 css 的延伸使用,當作挑戰賽也很有趣 😂,no-div 的作品其實是把 body 當作一個 div,並結合偽元素一起運用,以下會以 "one div" 的概念來分享一個 div 可以做到哪些事情
常用屬性
在只有一個 div 的情況下,較常用來畫圖的屬性有:
- pseudo element
- border
- box-shadow
- background
# pseudo element 偽元素
在 no-div 的作品中,大部分都有使用偽元素來處理,因為他本身也可以做出區塊,而且有個很好用的 content
屬性,以下介紹幾個 content
的用法:
1. content: ''
當成普通的 div 使用:結合 flexbox 做出文字左右的線
<div>hello</div>
div {
display: flex;
}
div:before, div:after {
content: '';
flex: 1;
border-bottom: 1px solid;
margin: auto 12px;
}
2. content: '{string}'
像 div 一樣直接寫入字串
div:before {
content: 'hello';
}
3. content: '{unicode}'
除了字串之外,只要用反斜線加上 unicode 就可以寫出各種文字及特殊符號,可以搜尋 unicode list,找到 U+
開頭的 code
/* ★: U+2605 */
div:before {
content: '\2605';
}
另外像是撲克牌的四種花色,也有他們對應的 unicode,可以用此做出撲克牌圖案
4. content: attr()
attr()
可以抓取其父元素上的屬性值,包含 class & id
<div data-title="main title"></div>
div:before {
content: attr(data-title);
/* 結果同等於 content: 'main title' */
}
5. content: url()
url()
其實就像 <img>
一樣,可以放圖片路徑,需要注意的是 url 的圖片無法調整大小
div:before {
content: url(../icon.png);
}
⚠️ 偽元素的動畫依賴性:若對 div 寫動畫,有些會影響到偽元素,使用時需要再注意。例如:在 div 寫了右移的動畫 translateX(100px)
,則其偽元素也會跟著右移
# border 邊框
除了方方正正的線條外,border 還有其他不同的用途
1. 三角形
如果上網查如何用 css 做三角形,大多數的回答應該都是用 border,因為在沒有寬度的情況下,border 的樣式剛好就是三角形:
div {
width: 0;
border: 30px solid;
border-color: red orange gold green;
}
因此可以用此概念做出像是對話框、蝴蝶結等包含三角形的圖案
/* msg box */
.msg:before {
content: '';
// ...
border-style: solid;
border-width: 16px 8px 0px;
border-color: seagreen transparent;
}
/* knot */
.knot {
width: 0px;
border-style: solid;
border-color: transparent red;
border-width: 20px 35px 50px 50px;
}
2. border-radius
border 加上圓角也可以做出許多不同的應用,像是弦月
.moon {
width: 100px;
height: 100px;
border-radius: 50%;
border-right: 30px solid gold;
}
或是畫個瀏海
.head {
width: 130px;
height: 80px;
color: tan;
border-style: solid;
border-width: 80px 15px 0;
border-radius: 50%;
background-color: wheat;
/* 眼睛是另外加上去的 */
}
同場加映側面照
.head {
/* css 同上 */
...,
border-width: 80px 0 0 15px;
}
# box-shadow 區塊陰影
說到陰影,好像就是那個只能低調躲在 div 背後的夥伴,但他也有可以當主角的時候!
1. border-radius
陰影搭配 border-radius 也可以做出弦月,這裡使用的是 inset
內陰影,可以控制大小在寬度內
.moon {
width: 100px;
height: 100px;
border-radius: 50%;
box-shadow: inset -30px 0px gold;
}
2. 複製人
你知道 box-shadow 擁有 css 界的複製人技術嗎?在畫複製人之前,先複習一下他的參數:box-shadow(x y blur spread color)
,分別有 x 和 y 的座標、模糊程度和發散成度,我們先把發散和模糊都拿掉,並且座標設定超過該元素的大小,就可以用 box-shadow 做出另一個相同的區塊了!若有寫 border-radius,則陰影的樣式也會跟著改變哦
div {
width: 100px;
height: 100px;
background-color: orange;
box-shadow: 110px 0px #fbdf8b; /* 110px -> 大於元素的寬度 100px */
}
3. loading
下面這個 loading 就是用 one-div 加上 box-shadow 做的
利用複製人的概念,我們可以以元素為中心(下圖紅點),先做出上下左右四個點
.loading {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: indianred;
box-shadow:
50px 0px gold, /* right */
-50px 0px gold, /* left */
0px 50px gold, /* bottom */
0px -50px gold; /* top */
}
接著斜對角的四個點,需要用到直角三角形邊長的計算:45-45-90 的直角三角形斜邊等於圓形的半徑 1,那麼短邊長就會是 1 / (根號 2) ,在此例中半徑是 50,所以短邊長為 50 / 1.414 大約是 35,得出 x y 距離皆為 35,即可畫出斜對角的四個點了!
最後再把中心的顏色拿掉,就做出八個點了~之後再利用 animation 讓 box-shadow 每個點輪流變色,就可以做出 loading 的效果了
.loading {
...,
box-shadow:
...,
35px -35px gold,
35px 35px gold,
-35px 35px gold,
-35px -35px gold;
}
@keyframes loading {
0%, 100% {
box-shadow: 0px -50px peru, 35px -35px gold, ...;
}
12.5% {
box-shadow: 0px -50px gold, 35px -35px peru, 50px 0 gold, ... ;
}
...
}
4. box-shadow vs. border
剛剛有提到 border 和 box-shadow 都可以做出弦月的樣式,但效果其實還是有些微不同:border 是邊框畫好後,再被 border-radius 切掉;box-shadow 則是在元素被 border-radius 切掉後,再長出的一段(為了把 border 跟 box-shadow 畫在一起,下圖用的是外陰影)
在 border-radius 比例不同時,兩者的效果也有顯著的差異(上面是 box-shadow,下面是 border):
在同為 border-radius: 50%
時,不同邊寬的動畫比較:
# background 背景
background 是在 one div 的情況下畫出各種樣式的主要功臣,大部分會著重在漸層色的應用,包含線性漸層(linear-gradient)、放射漸層(radial-gradient)、錐形漸層(conic-gradient)以及重複漸層(repeating gradient)
1. background-color
說到背景色,最常使用的應該還是 background-color,但他有個缺點,就是無法設定 background-size,大大減少了他的使用彈性,像是如果要做一個小的方形區塊,就無法用 background-color 來完成:
.bg-color {
width: 100px;
height: 100px;
background-color: gold;
background-size: 50px 50px;
background-repeat: no-repeat;
}
這個情況下,使用 linear-gradient
就可以解決這個問題了~
2. background: linear-gradient
延續上述例子,若要做出一個固定大小的區塊,可將線性漸層的兩個顏色設為相同 linear-gradient(gold, gold)
;或是將主色邊界設定為 100%,另一個給透明色:
.bg-gradient {
width: 100px;
height: 100px;
background-image: linear-gradient(gold 100%, transparent);
background-size: 50px 50px;
background-repeat: no-repeat;
}
要讓漸層色有清楚的界線(等於沒有漸變區),原理就是讓兩色的交接點相同,例如:linear-gradient(red 30%, pink 30%)
,這樣就會在 30% 的位置有個明顯的分界點,用此方式也可以畫出三角形:
- google logo
下圖是大家應該都很熟悉的 google 文字配色,若要做出這個樣式,比較直覺的想法應該是切成六個 span,再分別給不同個 class 去設定顏色,但這裡也可以用一個 div 就解決囉
作法是用 linear-gradient
加上 background-clip: text
把背景切成文字的形狀,需要注意的是每個字型的字母寬度不太一樣,像這裡使用的字型,每個字母寬度都有小小的差別,就需要找出每個漸層的分界點
/* 找出分界點 */
$point1: 17%;
$point2: $point1 + 16.5%;
$point3: $point2 + 16.5%;
$point4: $point3 + 17%;
$point5: $point4 + 16%;
/* 線性漸層:由左至右 */
background-image:
linear-gradient(
90deg,
$blue $point1,
$red $point1, $red $point2,
$yellow $point2, $yellow $point3,
$blue $point3, $blue $point4,
$green $point4, $green $point5,
$red $point5
);
/* 將背景剪裁成文字的前景色 */
background-clip: text;
/* 文字本身要設定透明,才可以顯示出背景 */
color: transparent;
- 擦亮效果
這個亮晶晶的效果用 linear-gradient 就可以做到:
首先做出一個包含白色小區塊的漸層,再把寬度放大,最後做個動畫讓背景位置由左到右移動,就可以做出亮晶晶的效果了~
.title {
background-image:
linear-gradient(-45deg, indianred 40%, white 50%, indianred 60%);
background-size: 250% 100%; /* 放大漸層背景 */
background-clip: text;
animation: shiny 2.5s both infinite;
}
@keyframes shiny {
0% {
background-position: 100% 100%;
}
}
3. background: radial-gradient
放射狀漸層可以做出圓形或橢圓形,像是三個點的 loading icon 只需要一個 div 加上 radial-gradient 就可以做到
- loading
這裡主要是用 circle
圓形漸層做出三個點,再控制 position 平分每個點:
/* radial-gradient */
.loading {
width: 90px;
height: 15px;
background-image:
radial-gradient(circle, gold 50%, transparent 50%),
radial-gradient(circle, gold 50%, transparent 50%),
radial-gradient(circle, gold 50%, transparent 50%);
background-size: 15px 15px;
background-position: 0 50%, 50% 50%, 100% 50%;
background-repeat: no-repeat;
}
不過除了放射漸層以外,pseudo element 跟 box-shadow 都可以做出這個 loading icon 哦,需要注意的是 box-shadow 需要往上/下/左/右推,所以會有點位移,可以用 margin 或 transform 再把位置移回來
/* pseudo element */
.loading {
width: 15px;
height: 15px;
border-radius: 50%;
background-color: gold;
margin: auto;
position: relative;
}
.loading:before,
.loading:after {
content: '';
position: absolute;
width: 100%;
height: 100%;
border-radius: inherit;
background-color: inherit;
}
.loading:before { left: 30px; }
.loading:after { right: 30px; }
/* box-shadow */
.loading {
width: 15px;
height: 15px;
border-radius: 50%;
box-shadow: 30px 0 0 gold, 60px 0 0 gold, 90px 0 0 gold;
transform: translateX(-20px); /* 把 shadow 位置調整回來 */
}
做出三個點之後,可以再加上動畫做出彈跳效果:
- 無限輪迴
除了 bounce 以外,也可以做這種無限冒出/消失的 loading
在開始之前,可以先思考一下無限輪迴的動畫效果是怎麼做到的,像是常看到的 gif 梗圖,就很容易看出起始點和結束點
但無限輪迴的 gif 就看不太出來,原理是讓起始點與結束點的樣子相同,等於結束時要回到原點
回到剛剛的 loading 圖,可以先把點分成四個,第一個不斷地由小到大冒出,最後一個則相反,不斷地由大到小最後消失,中間兩個則固定大小,不斷往右移動,就可以做出無限輪迴的效果了
$dot: radial-gradient(circle, $color 50%, transparent 50%);
.loading {
background: $dot, $dot, $dot, $dot;
background-repeat: no-repeat;
animation: move 0.5s both infinite $bezier;
}
@keyframes move {
from {
background-position: 0 50%, 0 50%, 50% 50%, 100% 50%;
background-size: $bg-small, $bg-normal, $bg-normal, $bg-normal;
}
to {
background-position: 0 50%, 50% 50%, 100% 50%, 100% 50%;
background-size: $bg-normal, $bg-normal, $bg-normal, $bg-small;
}
}
- 半徑知多少
關於 radial-gradient 的半徑有點不是那麼直觀,所以想來分享一下:以下是一個寬高 300px 的正方形,我們放了一個以 50% 為分界點的 radial-gradient 圓形背景,這個圓形的半徑應該會是多少呢?
.box {
width: 300px;
height: 300px;
background-image: radial-gradient(
circle at 50% 50%,
orange 5%,
#ececec 5% 50%,
transparent 50%);
/* orange 5% 是用來做出圓心當作參考 */
}
這邊要介紹一下 radial-gradient
裡有個屬性叫 extent-keyword
,用來設定漸層的終點位置,預設值為最遠的角落(farthest-corner),對分界點是 50% 的圓形來說,從中心點到最遠角落的距離,剛好就是他的直徑(下圖綠線),而這裡又出現了 45-45-90 的直角三角形,利用 1:1:根號2 的算法,可以得知半徑為 150 * (根號 2) / 2,大約是 106
extent-keyword
的屬性另外可以設定最近的角落(closest-corner)、最近的邊(closest-side)和最遠的邊(farthest-side),是若希望圓形撐滿整個區塊,則可以設定成 closest-side + 邊界 100%
.box {
...,
background-image: radial-gradient(
closest-side circle at 50% 50%,
orange 5%, #ececec 5% 100%,
transparent);
}
4. background: conic-gradient
錐形漸層可以用來製作色相環、金屬的反射效果或是 instagram 限時動態的外框
.hue {
background-image: conic-gradient(
#ff0000, #ffff00,
#00ff00, #00ffff,
#0000ff, #ff00ff,
#ff0000);
}
.metal {
background-image: conic-gradient(
white 0%, silver 10%,
white 35%, silver 45%,
white 60%, silver 70%,
white 80%, silver 95%,
white 100%);
}
.story {
background-image: conic-gradient(
#8a509d 30%, #e14e53 ,
#fbc66b, #e14e53, #8a509d);
}
也可以做出邊界或改變中心點、起始角度
.box {
background-image: conic-gradient(
from 60deg at 20% 50%,
indianred 25%,
seagreen 25%, seagreen 50%,
gold 50%, gold 75%,
steelblue 75%);
}
5. background: repeating gradient
上述的三種漸層都有重複性可以使用,像是重複線性漸層是 repeating-linear-gradient
,但這個重複跟 background-repeat
有什麼不同呢?先看個例子 🌰
假設今天要做個懷舊理髮廳的旋轉燈樣式,應該會先想到用 linear-gradient
,但寫下去才發現要一條一條刻,重複寫了很多相同的 code(右下角落最後一條還沒刻到 QQ)
/* linear-gradient */
.light {
width: 100px;
height: 300px;
background-image:
linear-gradient(135deg,
#fff, #fff 30px, /* white */
#ec1d25 30px, #ec1d25 60px, /* red */
#fff 60px, #fff 90px, /* white */
#015f9f 90px, #015f9f 120px, /* blue */
#fff 120px, #fff 150px,
#ec1d25 150px, #ec1d25 180px,
#fff 180px, #fff 210px,
#015f9f 210px,#015f9f 240px,
#fff 240px, #fff 270px);
}
既然寫了重複的東西,就用 background-repeat
來解決吧!我們在寫好第一段顏色之後,加上 size 和 repeat,雖然達到了重複性,但銜接處很突兀,跟理想的樣子還是差了不少
/* linear-gradient + bg-repeat */
.light {
width: 100px;
height: 300px;
background-image:
linear-gradient(135deg,
#fff, #fff 30px,
#ec1d25 30px, #ec1d25 60px,
#fff 60px, #fff 90px,
#015f9f 90px, #015f9f 120px,
#fff 120px, #fff 150px);
background-size: 100px 100px;
background-repeat: repeat-y;
}
這時候就輪到 repeating-linear-gradient
出場了,一樣寫好第一段顏色後,只要加上 repeating 的前綴,就可以讓這個樣式按照同個步調重複畫下去
/* repeating-linear-gradient */
.light {
width: 100px;
height: 300px;
background-image:
repeating-linear-gradient(135deg,
#fff, #fff 30px,
#ec1d25 30px, #ec1d25 60px,
#fff 60px, #fff 90px,
#015f9f 90px, #015f9f 120px);
}
同理可以應用在另外兩種漸層,像是飛天小女警裡會出現的愛心可以用 repeating-radial-gradient
div {
background:
repeating-radial-gradient(
circle at 50% 50%,
#e60103 0px, #e60103 30px,
#de7878 30px, #de7878 60px,
#eaa7b5 60px, #eaa7b5 90px
);
}
漫畫裡出現類似爆炸文字的條紋背景則可以用 repeating-conic-gradient
div {
background:
repeating-conic-gradient(
from 5deg at 30% 30%,
#f9e4c9 0deg 15deg,
#ea4445 15deg 30deg
);
}
6. 漸層背景的小問題
雖然漸層可以做到的事情很多,但還是有幾個要注意的地方:
- safari 上的 bug
一個是透明色的問題:漸層色若有使用到 transparent,在 safari 上會變成黑灰色而非透明色,但如果是有明確分界點的寫法就不會有這個狀況,解決辦法可以使用 rgba(255, 255, 255, 0)
來取代 transparent
🎃 2022.6.15 更新:safari 15.4 已經修正這個問題了 🎉🎉
另一個是與 flexbox 的衝突:gradient 如果跟 display: flex
並用,在 safari 上會直接消失(?),但反白的話會發現其實文字還在,只是變成透明的了,解決辦法是把 flex 跟 gradient 分在不同層寫
<!-- failed -->
<div class="flex gradient">gradient with flex</div>
<!-- success -->
<div class="flex">
<span class="gradient">gradient inside flex</span>
</div>
- transition 轉場問題
漸層色本身沒有動畫的轉場,像是以下的例子雖然有加上 transition,但 hover 時不會有顏色的轉場效果
div {
background-image: linear-gradient(gold, pink);
transition: all 0.3s;
}
div:hover {
background-image: linear-gradient(orange, red);
}
解決方式可以蓋一個隱藏的偽元素,hover 時再控制 opacity 讓偽元素出現:
div:before {
content: '';
...,
background-image: linear-gradient(orange, red);
opacity: 0;
visibility: hidden;
transition: all 0.3s;
}
div:hover:before {
opacity: 1;
visibility: visible;
}
另外也可用 background-size
做一個較大的漸層背景,再用 background-position
控制 hover 時要顯示的位置:
div {
background-image: linear-gradient(steelblue, pink 50%, gold);
background-size: 100% 200%; /* 放大背景 */
transition: all 0.2s;
}
div:hover {
background-position: 0 100%; /* 移動背景 */
}
- border 邊框問題
這個問題應該較不容易遇到,只有在較寬的 border 且有透明度的情況下,同時使用漸層背景才會發生,理想中的半透明邊框應該會蓋在漸層背景上才對,但下圖的邊框長得很奇特,還包含了漸層色
div {
width: 300px;
height: 200px;
border: 20px solid rgba(255, 255, 255, 0.3);
background-image: linear-gradient(45deg, pink 50%, steelblue 50%);
}
這裡要介紹一個背景的屬性 background-origin,他可以設定背景的起始位置,預設是 padding-box
,也就是從 padding 的位置開始(包含 padding),只要改為 border-box
就可以讓背景從邊框開始,達到我們想要的樣式了
div {
// ...,
background-origin: border-box;
}
# 小總結
以上介紹了偽元素的 content 應用,利用 border 與 border-radius 畫幾何圖形,box-shadow 的複製人概念及 loading 圖製作,比較 box-shadow 及 border 的差異,還有花了不少篇幅介紹三種漸層背景:線性 (linear)、放射 (radial)、錐形 (conic),及重複漸層的使用場景,也有補充漸層背景會遇到的問題與注意事項,看完後也來玩玩用 one div 畫個圖吧~
# 本篇同步分享於 medium
參考資料
- #nodivember - Twitter
- Animating Single Div Art | CSS-Tricks
- CSS Background Patterns by MagicPattern
- great works
- A Single Div #非常厲害
- Only CSS: Linear Gradient Animation #linear-gradient
- Bat Pixel Art Animation on one Div #box-shadow