使用CSS的Mask實現內凹圓角
在網頁設計中,我們偶爾會遇到需要實現內凹圓角的特殊設計,例如像「手機瀏海」或票券上的缺口。傳統上,雖然可以嘗試使用圖層拼接 (Splicing) 搭配 background
達成,但若背景複雜或顏色不固定,拼接處的非透明區域 (non-transparent area) 會因無法與底色融合而輕易被使用者察覺,導致效果破綻。
為了解決透明度的問題,我們能使用CSS Mask來實現。透過Masking的方式,我們能精準地控制元素的哪些區域應該是完全透明 (Transparent) 的,藉此實現複雜的鏤空效果。
CSS mask的原理,是將多個"遮罩層"結果進行交集,並組合成最終能顯示的元素。
保留部分: Mask圖層的不透明區域會保留元素 (#000/alpha=1) 挖空部分: Mask圖層的透明區域會被挖空(transparent/alpha=0) 多層交集: 透過多的mask來處理,並使用交集來達成最終的顯示結果,並且只有在所有圖層上都不透明的部分才會顯示
兩種內凹圓角 (Inner Notch) 實現方式比較
我們來創建兩種實現內凹圓角的方式並做測試
首先我們先建立出兩個寬度為300px和高度100px的長方形灰色長方形作為基底
<div class="inner-notch-bg">
<div class="rect"></div>
</div>
<div class="inner-notch-mask"></div>
body, html {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
body > div {
position: relative;
margin: auto;
width: 300px;
height: 100px;
background: gray;
}
實現一: 透過背景 (Background) 圖層拼接
.inner-notch-bg {
background: radial-gradient(circle at 50% -10px, #fff, #fff 40px, gray calc(40px + 0.5px), gray);
}
使用偽元素,疊加兩個小圓
.inner-notch-bg {
background: radial-gradient(circle at 50% -10px, #fff, #fff 40px, gray calc(40px + 0.5px), gray);
&::before,
&::after {
content: "";
position: absolute;
width: 40px;
height: 40px;
border-radius: 50%;
background: yellow;
top: -15px;
left: 72px;
}
}
添加第二個小圓
.inner-notch-bg {
background: radial-gradient(circle at 50% -10px, #fff, #fff 40px, gray calc(40px + 0.5px), gray);
&::before,
&::after {
content: "";
position: absolute;
width: 40px;
height: 40px;
border-radius: 50%;
background: yellow;
top: -15px;
left: 72px;
}
&::after {
left: unset;
right: 72px;
}
}
接著用rect去蓋住
.inner-notch-bg {
background: radial-gradient(circle at 50% -10px, #fff, #fff 40px, gray calc(40px + 0.5px), gray);
.rect {
position: absolute;
width: 300px;
height: 20px;
left: 0;
top: -15px;
background: linear-gradient(90deg, blue, blue 93px, blue calc(300px - 93px), blue);
z-index: -1;
}
&::before,
&::after {
content: "";
position: absolute;
width: 40px;
height: 40px;
border-radius: 50%;
background: yellow;
top: -15px;
left: 72px;
}
&::after {
left: unset;
right: 72px;
}
}
然後調整一下rect bg
.inner-notch-bg {
background: radial-gradient(circle at 50% -10px, #fff, #fff 40px, gray calc(40px + 0.5px), gray);
.rect {
position: absolute;
width: 300px;
height: 20px;
left: 0;
top: -15px;
background: linear-gradient(90deg, blue, blue 93px, transparent 93px, transparent calc(300px - 93px), blue calc(300px - 93px), blue);
z-index: -1;
}
調整顏色,調成灰色
.inner-notch-bg {
background: radial-gradient(circle at 50% -10px, #fff, #fff 40px, gray calc(40px + 0.5px), gray);
.rect {
position: absolute;
width: 300px;
height: 20px;
left: 0;
top: -15px;
background: linear-gradient(90deg, gray, gray 93px, transparent 93px, transparent calc(300px - 93px), gray calc(300px - 93px), gray);
z-index: -1;
}
&::before,
&::after {
content: "";
position: absolute;
width: 40px;
height: 40px;
border-radius: 50%;
background: gray;
top: -15px;
left: 72px;
}
&::after {
left: unset;
right: 72px;
}
}
實現二:運用 CSS Masking
我們定義三個css custom properties來使用,並計算端點的水平位移變量 (--_d
),用於精確定位左右兩側半圓的中心,確保它們與中央連接弧的切點位置正確銜接。
.inner-notch {
--r: 20px; /* 端點半圓的半徑(控制圓潤度) */
--s: 40px; /* 內凹連接弧的半徑(控制凹口深度尺度) */
--a: 20deg; /* 連接弧的傾角(正值=上抬,負值=下壓) */
/* 2. 計算變量 */
--_d: (var(--s) + var(--r)) * cos(var(--a));
}
接著創建端點水平位移變量(_d
),來決定左右部分離中心的距離,來確保與連接弧的切點位置。
公式:
參數意義:
r
:圓角半徑。s
:凹口尖點到圓角中心在斜線方向的距離。a
:凹口邊緣相對於垂直線的夾角。d
:將長度s + r
投影到 X 軸後的水平分量。
d的求法:
- 從凹口的正中下方(50% 的水平中心)往左/右各有一條「放置圓角中心」的射線,這條射線與垂直方向夾角為
a
。 - 圓角中心到尖點沿著這條射線的實際距離是
s + r
(先離開尖點s
的直線段,再加上圓角半徑r
才會到圓心)。 - 但我們在 CSS
mask
要用的是水平座標,所以要把這段長度投影到 X 軸: - 水平分量 = 斜邊 × cos(角度) =
(s + r) * cos(a)
,這就是d
。
步驟一:使用 linear-gradient
建立主體裁切
在中央切出一個矩形透明區,作為Notch的範圍。
.inner-notch-mask {
/* 1. 核心參數 */
--r: 20px; /* 端點半圓的半徑(控制圓潤度) */
--s: 40px; /* 內凹連接弧的半徑(控制凹口深度尺度) */
--a: 20deg; /* 連接弧的傾角(正值=上抬,負值=下壓) */
/* 2. 計算變量 */
--_d: (var(--s) + var(--r)) * cos(var(--a));
/* 3. Mask 疊加 */
mask:
linear-gradient(
90deg,
#000 calc(50% - var(--_d)),
transparent 0 calc(50% + var(--_d)),
#000 0
);
mask-repeat: no-repeat;
}
步驟二:加入中央連接弧
使用radial-gradient挖出中央的圓弧,實現凹陷的深度與傾角。
.inner-notch-mask {
/* 1. 核心參數 */
--r: 20px;
--s: 40px;
--a: 20deg;
/* 2. 計算變量 */
--_m: 0 / calc(2*var(--r)) var(--r) no-repeat
radial-gradient(50% 100% at bottom, #000 calc(100% - 1px), transparent);
--_d: (var(--s) + var(--r)) * cos(var(--a));
/* 3. 四層 Mask 疊加 */
mask:
radial-gradient(
var(--s) at 50% calc(-1 * sin(var(--a)) * var(--s)),
#000 calc(100% + 1px)
) 0 calc(var(--r) * (1 - sin(var(--a)))),
linear-gradient(
90deg,
#000 calc(50% - var(--_d)),
transparent 0 calc(50% + var(--_d)),
#000 0
);
mask-repeat: no-repeat;
}
稍微調整radial-gradient,讓我們能正確去遮罩,加入透明區塊transparent 100%,
/* Before */
radial-gradient(
var(--s) at 50% calc(-1 * sin(var(--a)) * var(--s)),
#000 calc(100% + 1px)
) 0 calc(var(--r) * (1 - sin(var(--a)))),
/* After */
radial-gradient(
var(--s) at 50% calc(-1 * sin(var(--a)) * var(--s)),
transparent 100%, #000 calc(100% + 1px)
) 0 calc(var(--r) * (1 - sin(var(--a)))),
.inner-notch-mask {
/* 1. 核心參數 */
--r: 20px; /* 端點半圓的半徑(控制圓潤度) */
--s: 40px; /* 內凹連接弧的半徑(控制凹口深度尺度) */
--a: 20deg; /* 連接弧的傾角(正值=上抬,負值=下壓) */
/* 2. 計算變量 */
--_d: (var(--s) + var(--r)) * cos(var(--a));
/* 3. Mask 疊加 */
mask:
radial-gradient(
var(--s) at 50% calc(-1 * sin(var(--a)) * var(--s)),
transparent 100%, #000 calc(100% + 1px)
) 0 calc(var(--r) * (1 - sin(var(--a)))),
linear-gradient(
90deg,
#000 calc(50% - var(--_d)),
transparent 0 calc(50% + var(--_d)),
#000 0
);
mask-repeat: no-repeat;
}
步驟三:添加左右端點半圓 mask
用來遮罩的半圓mask變數,寬 2r, 高 r 的下半圓
在 radial-gradient 中使用
calc(100% - 1px)
是將不透明邊界往內縮 1px,藉此減少邊緣鋸齒 (Aliasing) 造成的白縫。
將這個--_m
變數,添加到右側mask。
.inner-notch-mask {
/* 1. 核心參數 */
--r: 20px; /* 端點半圓的半徑(控制圓潤度) */
--s: 40px; /* 內凹連接弧的半徑(控制凹口深度尺度) */
--a: 20deg; /* 連接弧的傾角(正值=上抬,負值=下壓) */
/* 2. 計算變量 */
--_m: 0 / calc(2*var(--r)) var(--r) no-repeat
radial-gradient(50% 100% at bottom, #000 calc(100% - 1px), transparent);
--_d: (var(--s) + var(--r)) * cos(var(--a));
/* 3. Mask 疊加 */
mask:
calc(50% + var(--_d)) var(--_m),
radial-gradient(
var(--s) at 50% calc(-1 * sin(var(--a)) * var(--s)),
transparent 100%, #000 calc(100% + 1px)
) 0 calc(var(--r) * (1 - sin(var(--a)))),
linear-gradient(
90deg,
#000 calc(50% - var(--_d)),
transparent 0 calc(50% + var(--_d)),
#000 0
);
mask-repeat: no-repeat;
}
接著添加左側mask
.inner-notch-mask {
/* 1. 核心參數 */
--r: 20px; /* 端點半圓的半徑(控制圓潤度) */
--s: 40px; /* 內凹連接弧的半徑(控制凹口深度尺度) */
--a: 20deg; /* 連接弧的傾角(正值=上抬,負值=下壓) */
/* 2. 計算變量 */
--_m: 0 / calc(2*var(--r)) var(--r) no-repeat
radial-gradient(50% 100% at bottom, #000 calc(100% - 1px), transparent);
--_d: (var(--s) + var(--r)) * cos(var(--a));
/* 3. Mask 疊加 */
mask:
calc(50% + var(--_d)) var(--_m),
calc(50% - var(--_d)) var(--_m),
radial-gradient(
var(--s) at 50% calc(-1 * sin(var(--a)) * var(--s)),
transparent 100%, #000 calc(100% + 1px)
) 0 calc(var(--r) * (1 - sin(var(--a)))),
linear-gradient(
90deg,
#000 calc(50% - var(--_d)),
transparent 0 calc(50% + var(--_d)),
#000 0
);
mask-repeat: no-repeat;
}
總結mask步驟:
- 主體裁切 (linear-gradient):先將中段挖空,提供 Notch 的工作區域。
- 中段連接弧 (radial-gradient):在工作區內用一個圓形挖出平滑的弧線,並進行垂直對齊。
- 左右端點半圓 (radial-gradient):將工作區兩側的尖角補成平滑的半圓。
完整程式碼
<div class="inner-notch-bg">
<div class="rect"></div>
</div>
<div class="inner-notch-mask"></div>
body, html {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
body > div {
position: relative;
margin: auto;
width: 300px;
height: 100px;
background: gray;
}
.inner-notch-bg {
background: radial-gradient(circle at 50% -10px, #fff, #fff 40px, gray calc(40px + 0.5px), gray);
.rect {
position: absolute;
width: 300px;
height: 20px;
left: 0;
top: -15px;
background: linear-gradient(90deg, gray, gray 93px, transparent 93px, transparent calc(300px - 93px), gray calc(300px - 93px), gray);
z-index: -1;
}
&::before,
&::after {
content: "";
position: absolute;
width: 40px;
height: 40px;
border-radius: 50%;
background: gray;
top: -15px;
left: 72px;
}
&::after {
left: unset;
right: 72px;
}
}
.inner-notch-mask {
/* 1. 核心參數 */
--r: 20px; /* 端點半圓的半徑(控制圓潤度) */
--s: 40px; /* 內凹連接弧的半徑(控制凹口深度尺度) */
--a: 20deg; /* 連接弧的傾角(正值=上抬,負值=下壓) */
/* 2. 計算變量 */
--_m: 0 / calc(2*var(--r)) var(--r) no-repeat
radial-gradient(50% 100% at bottom, #000 calc(100% - 1px), transparent);
--_d: (var(--s) + var(--r)) * cos(var(--a));
/* 3. Mask 疊加 */
mask:
calc(50% + var(--_d)) var(--_m),
calc(50% - var(--_d)) var(--_m),
radial-gradient(
var(--s) at 50% calc(-1 * sin(var(--a)) * var(--s)),
transparent 100%, #000 calc(100% + 1px)
) 0 calc(var(--r) * (1 - sin(var(--a)))),
linear-gradient(
90deg,
#000 calc(50% - var(--_d)),
transparent 0 calc(50% + var(--_d)),
#000 0
);
mask-repeat: no-repeat;
}
codepen: