# 用 CSS 實作漸層邊框
Elantris
[TOC]
## 需求
合作的設計師在某個專案的產品設計圖中大量地使用漸層邊框,無論在 Button、Panel、Card、甚至一個倒數計時器上都可以看到邊框是由青藍、深藍、粉紅如此鮮豔又強烈的顏色變化,正當我開始實作的時候才發現事情沒有我想得如此簡單,以下紀錄我這幾天以來研究的過程。
## 實作
<style>
.box {
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
width: 15rem;
aspect-ratio: 3/2;
}
.wrap {
display: flex;
justify-content: center;
gap: 1rem;
background: linear-gradient(to bottom right, #212529, #adb5bd);
padding: 2rem;
}
</style>
### 方案一:邊框圖片
最直覺的想法就是用 `border-image` 搭配 `linear-gradient` 事情應該就會如我想像般順利,對吧?
```css
.box {
border-width: 0.5rem;
border-style: solid;
border-color: transparent;
border-radius: 2rem;
border-image-source: linear-gradient(to right, #68e2ff, #4253eb, #d851ff);
}
```
<div class="box box1-1">ABC</div>
<style>
.box1-1,
.box1-2,
.box1-3 {
border-width: 0.5rem;
border-style: solid;
border-color: transparent;
border-radius: 2rem;
border-image-source: linear-gradient(to right, #68e2ff, #4253eb, #d851ff);
}
</style>
結果實際上竟然只有四個角落出現漸層色的小正方形!
這才發現原來 CSS 邊框圖片有一個屬性 [`border-image-slice`](https://developer.mozilla.org/en-US/docs/Web/CSS/border-image-slice) 用來定義一張圖片在填滿邊框時要如何裁切以適用在一個元素的九個區域內,預設值為 100% 代表整張圖都被放進屬於角落的區域,導致邊線上只有透明的區域可以填滿。這時只要簡單填入 1 即可正常顯示(或是填入其他小於元素邊長一半的數值)。
```css
.box {
/* ... */
border-image-slice: 1;
}
```
<div class="box box1-2">ABC</div>
<style>
.box1-2,
.box1-3 {
border-image-slice: 1;
}
.box1-3 {
color: white;
}
</style>
不過這個方法有一個致命的缺點,不支援 `border-radius` 屬性,邊框一定是方方正正的,看來這個方案明顯不符合設計師的期待。
### 方案二:背景
接著我試著從背景下手,如果能在一層漸層背景上再疊一層上去應該可行。雖然可以用兩個 div 來實作,不過 background 可以用逗號分隔圖層,而且還有 [`background-origin`](https://developer.mozilla.org/en-US/docs/Web/CSS/background-origin) 以及 [`background-clip`](https://developer.mozilla.org/en-US/docs/Web/CSS/background-clip) 這兩個屬性決定背景出現在 box model 裡的哪個區域。
```css
.box {
border-width: 0.5rem;
border-style: solid;
border-color: transparent;
border-radius: 2rem;
background-image: linear-gradient(white, white), linear-gradient(to right, #68e2ff, #4253eb, #d851ff);
background-clip: padding-box, border-box;
background-origin: padding-box, border-box;
}
```
<div class="box box2-1">ABC</div>
<style>
.box2-1 {
border-width: 0.5rem;
border-style: solid;
border-color: transparent;
border-radius: 2rem;
background-image: linear-gradient(white, white), linear-gradient(to right, #68e2ff, #4253eb, #d851ff);
background-clip: padding-box, border-box;
background-origin: padding-box, border-box;
}
</style>
乍看之下有九成像了,只用一個 div 既有漸層邊框也有圓角,應該可以交差了吧?
<div class="wrap">
<div class="box box2-1">ABC</div>
</div>
實際放到網頁上才發現裡面那層背景 `linear-gradient(white, white)` 其實是實心的,如果頁面本身是純色背景還能夠蒙混過去,只好再想想別的辦法了。
### 方案三:遮罩
接下來我查到了一個我很少碰到的屬性 mask,看了文件後發現他跟 background 一樣可以用逗號分隔圖層、疊起來可以選交集或差集,應該能夠在背景那層框出我要的範圍。
```css
.box {
border-width: 0.5rem;
border-style: solid;
border-color: transparent;
border-radius: 2rem;
background-image: linear-gradient(to right, #68e2ff, #4253eb, #d851ff);
background-origin: border-box;
mask-image: linear-gradient(white, white), linear-gradient(white, white);
mask-composite: exclude, add;
mask-clip: padding-box, border-box;
}
```
<div class="wrap">
<div class="box box3-1">ABC</div>
</div>
<style>
.box3-1,
.box3-2::before {
border-width: 0.5rem;
border-style: solid;
border-color: transparent;
border-radius: 2rem;
background-image: linear-gradient(to right, #68e2ff, #4253eb, #d851ff);
background-origin: border-box;
mask-image: linear-gradient(white, white), linear-gradient(white, white);
mask-clip: padding-box, border-box;
mask-composite: exclude, add;
}
</style>
完成!漸層邊框、圓角、內容透明背景全部達成,滿心歡喜地去找設計師炫耀,對方一看問了一個關鍵的問題:「內容呢?」
原來遮罩連內容都隱藏掉了,這下我沒招了 (´~\`)
看來如果要避免這個問題的話只能乖乖多加一個偽元素區分背景與內容了。
```css
.box {
position: relative;
padding: 0.5rem;
}
.box::before {
content: "";
position: absolute;
inset: 0;
/* ... */
}
```
<div class="wrap">
<div class="box box3-2">ABC</div>
</div>
<style>
.box3-2 {
position: relative;
padding: 0.5rem;
color: white;
}
.box3-2::before {
content: "";
position: absolute;
inset: 0;
}
</style>
最後外層用 padding 避免內容壓到邊框上。如此一來大功告成、順利結案。
## 完整程式碼
[Codepen - Linear Gradient Border](https://codepen.io/Elantris/pen/zYgEaaJ)
<div class="wrap">
<div class="box box1-3">Border Image</div>
<div class="box box2-1">Background</div>
<div class="box box3-2">Mask</div>
</div>
### 參考資料
- [Gradient Borders In CSS | CSS-Tricks](https://css-tricks.com/gradient-borders-in-css/)
- [Border with gradient and radius - DEV Community](https://dev.to/afif/border-with-gradient-and-radius-387f)