CSS 文字外框怎麼做?用多層 text-shadow 畫出圓潤中文描邊
最近開發畫面時剛好需要一圈醒目的文字外框。太久沒寫這類 CSS,第一時間只記得 text-shadow 和 -webkit-text-stroke,實際套到粗體中文字後卻不太滿意:筆畫轉角容易顯得尖,描邊也會往字形內部延伸,讓原本的字面變窄。
最後我採用多層 text-shadow:把沒有模糊的文字副本往四周平移,密集疊成一圈。因為外部陰影會畫在文字下方,原本的文字填色會蓋回中央,看起來就像一圈只長在外側、轉角較圓的外框。
先看最短版本:8 個方向疊一圈
如果外框只有 1px 左右,先從八方向版本開始就夠直覺:
<h1 class="text-outline">文字外框</h1>.text-outline { color: #ffffff; text-shadow: 1px 0 0 #166534, 0.71px 0.71px 0 #166534, 0 1px 0 #166534, -0.71px 0.71px 0 #166534, -1px 0 0 #166534, -0.71px -0.71px 0 #166534, 0 -1px 0 #166534, 0.71px -0.71px 0 #166534;}
每一組值的格式都是:
text-shadow: offset-x offset-y blur-radius color;以 1px 0 0 #166534 為例,瀏覽器會把同一段文字往右移 1px,不加模糊,再用綠色畫一次。把副本放到上、下、左、右與四個斜角,文字外圍就會被同色副本包住。
這裡的 0.71px 約等於 1 × cos(45°)。若斜角也寫成 1px 1px,它與中心的直線距離會變成約 1.41px,外框就不再是同一個半徑。
為什麼多層 text-shadow 不會把原字蓋住?
CSS Text Decoration 規格把外部 text-shadow 的繪製層級放在元素背景與文字之間。簡化後可以理解為:
文字填色────────多層 text-shadow────────元素背景所以這個做法不需要再複製一份 HTML,也不用靠偽元素把原字疊回來。只要文字的 color 是不透明色,位於中央的陰影自然會被文字本身蓋住,畫面上主要看到的就是外圍部分。
text-shadow 也不參與排版計算。外框變粗不會增加元素寬高,因此要自己檢查標題是否貼到容器邊緣、相鄰行是否太近,必要時增加 padding 或 line-height。
外框加粗後,為什麼八方向開始出現缺口?
把八個位移全部從 1px 改成 4px,方向之間的距離也會跟著拉開。中文字的轉角與細小筆畫可能露出缺口,看起來像鋸齒或八角形。
比較穩定的做法是同時增加兩件事:
- 增加圓周上的方向數量,讓相鄰副本更靠近。
- 增加內圈,填補中心到最外圈之間的空白。
這個幾何概念接近把字形向外做形態膨脹(morphological dilation):以不同方向的位移副本掃過字形,外緣便會接近圓形結構元素擴張後的輪廓。方向越密,圓周近似越平滑;但它仍是離散取樣,不是真正連續的向量描邊。
用 JavaScript 產生 24 層圓形外框
手動維護 24 組座標很容易改錯,我把它整理成一個沒有框架依賴的產生器:
function createTextOutline({ color, radius = 3, rings = 2, steps = 16,}) { const shadows = [];
for (let ring = 1; ring <= rings; ring += 1) { const currentRadius = (radius * ring) / rings; const currentSteps = Math.max(8, Math.round((steps * ring) / rings));
for (let index = 0; index < currentSteps; index += 1) { const angle = (Math.PI * 2 * index) / currentSteps; const x = Number((Math.cos(angle) * currentRadius).toFixed(2)); const y = Number((Math.sin(angle) * currentRadius).toFixed(2));
shadows.push(`${x}px ${y}px 0 ${color}`); } }
return shadows.join(", ");}
const title = document.querySelector(".text-outline");
title.style.textShadow = createTextOutline({ color: "#166534", radius: 3, rings: 2, steps: 16,});預設會產生:
- 內圈:半徑
1.5px,8 個方向。 - 外圈:半徑
3px,16 個方向。 - 合計:24 層沒有模糊的
text-shadow。

x = r × cos(θ)、y = r × sin(θ) 會把每個位移點放在同一個圓周上。用 CSS 變數可以把填色、外框色集中管理:
.text-outline { --text-fill: #ffffff; --text-outline: #166534;
color: var(--text-fill);}接著把 getComputedStyle(title).getPropertyValue("--text-outline") 傳給產生器即可。如果樣式在建置時就能確定,也可以先執行一次產生器,把結果貼回 CSS,避免每次載入都用 JavaScript 設定。
text-shadow 與 -webkit-text-stroke 怎麼選?
-webkit-text-stroke 雖然保留了 -webkit- 前綴,但 MDN 已標示為 Baseline Widely available;現在選擇它或 text-shadow,重點不是「能不能用」,而是想要哪一種外觀與維護方式。

| 比較項目 | -webkit-text-stroke | 多層 text-shadow |
|---|---|---|
| 寫法 | 一行即可完成 | 需要多組陰影或產生器 |
| 描邊位置 | 直接沿字形輪廓描繪,粗描邊會占用字面空間 | 陰影在文字下層,中央由原字填色蓋回 |
| 轉角觀感 | 依字型、字重與瀏覽器渲染而定,粗體中文字可能出現尖角 | 以圓周位移取樣,可用方向密度調整圓潤度 |
| 效能成本 | 宣告簡單,適合大量或動態文字 | 陰影層數乘上元素數量,重繪成本較高 |
| 適合情境 | 一般標題描邊、空心字、需要簡單維護 | 少量大標、活動字卡、需要柔和外框的中文字 |

如果 -webkit-text-stroke 的結果在實際字型上已經好看,我會直接用它。只有在「描邊壓縮字面」或「轉角太尖」真的看得出來時,才換成多層陰影。
注意:外框效果高度依賴字型輪廓、字重、裝置像素密度與瀏覽器的反鋸齒結果。不要只在一組英文字型上調完就結束,至少要用實際會出現的繁體中文字、數字與標點一起測試。
三種常用的 CSS 文字特效
同一個 text-shadow 還能做出其他效果,差別主要在位移、模糊半徑與疊加順序。
發光文字:同位置疊加不同模糊半徑
.text-glow { color: #d9f99d; text-shadow: 0 0 4px #84cc16, 0 0 14px #65a30d, 0 0 34px #3f6212;}所有陰影都留在原點,再從小到大疊三層模糊,就能形成由內而外的光暈。這種效果適合短標題,不適合整段內文。

浮雕文字:一亮一暗模擬光源
.text-emboss { color: #cbd5e1; text-shadow: -1px -1px 0 #ffffff, 2px 2px 0 #475569;}左上方放亮色、右下方放暗色,會產生凸起的錯覺;把亮暗方向互換,就會更像壓進背景的凹版文字。背景與文字顏色要接近,效果才不會變成單純的雙色重影。

漸層文字:把背景裁進字形
.text-gradient { color: #38bdf8; background: linear-gradient(90deg, #38bdf8, #a3e635); background-clip: text; -webkit-background-clip: text; -webkit-text-fill-color: transparent;}這個做法不是陰影,而是用文字當成背景的裁切範圍。保留 color 當作降級顏色,避免不支援裁切或高對比模式下完全看不到文字。

如果你也在整理 UI 細節,可以接著參考 Skeleton 與 Shimmer 載入效果、CSS 圖片比例控制:aspect-ratio vs Padding Hack與純 CSS 實作內凹圓角頁籤。
實作時要記得的限制
- 只套在少量大字:24 層陰影乘上大量節點,會增加繪製成本;不要替整篇內文描邊。
- 不要套在包住內文的父層:
text-shadow會繼承。若把效果設在整張卡片,子元素文字也可能一起吃到陰影。 - 粗細增加時同步加密方向:只放大半徑、不增加取樣點,外框會露出缺口。
- 預留外框空間:陰影不影響盒模型,也不會替你撐開容器。
- 檢查可讀性:裝飾不能取代文字與背景的基本對比。WCAG 對一般文字要求至少
4.5:1,大型文字至少3:1;背景複雜時,外框可當作 halo 協助分隔,但仍要用實際相鄰顏色檢查。
這次最後留下的判斷很簡單:細描邊、數量多,先用 -webkit-text-stroke;少量粗體中文字遇到尖角或字面被吃掉,再用多圈、多方向的 text-shadow。未來要調粗細時,不只改半徑,也一起調整圓周取樣數。
常見問題
Q: CSS 文字外框可以只往外長、不壓到文字嗎?
A: 可以用多層、零模糊的 text-shadow,把文字副本往四周位移。外部文字陰影位於文字填色下方,中央區域會被原字蓋住,因此視覺上主要留下字形外側的部分。外框越粗,越需要增加方向數與內圈,避免出現缺口。
Q: 為什麼中文套 text-shadow 外框會有刺或鋸齒?
A: 常見原因是只用了四向或八向陰影,卻把位移半徑拉得太大。圓周上的取樣點距離變遠,中文字複雜轉角間就會露出空隙。可以增加圓周方向、補內圈,並用實際字型在不同裝置像素密度下檢查。
Q: text-shadow 外框會影響元素尺寸嗎?
A: 不會。text-shadow 屬於繪製效果,不會參與版面配置,也不會替外框增加元素的寬高。若外框靠近容器邊界或相鄰文字,需要自行用 padding、margin 或 line-height 預留空間。
參考資料:
W3C CSS Text Decoration Module Level 4:text-shadow
回報錯字、失效連結,或告訴我你想看的延伸主題。