使用CSS的Mask實現內凹圓角

Created: 2025/10/11
Updated: 2025/10/11

在網頁設計中,我們偶爾會遇到需要實現內凹圓角的特殊設計,例如像「手機瀏海」或票券上的缺口。傳統上,雖然可以嘗試使用圖層拼接 (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),來決定左右部分離中心的距離,來確保與連接弧的切點位置。

公式: d=(s+r)cos(a)d=(s+r)∗cos(a)

參數意義:

  • 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步驟:

  1. 主體裁切 (linear-gradient):先將中段挖空,提供 Notch 的工作區域。
  2. 中段連接弧 (radial-gradient):在工作區內用一個圓形出平滑的弧線,並進行垂直對齊。
  3. 左右端點半圓 (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: