CSSで、文字に縁取りを付ける方法

​​CSS​HTML,Playwright

CSS で文字に縁取りを付けるには、-webkit-text-stroke に線の太さと色を指定します。ただ、この線は文字の輪郭の外側だけでなく内側にもかかるので、太くするほど文字の中の色が見えなくなります。paint-order: stroke fill を足すと、線が文字の後ろに描かれ、文字の形はそのままで外側にだけ縁が出ます。

補足

検証環境は Ubuntu 24.04、Node.js 22.23.4、Playwright 1.63.0、Chromium 153 です。フォントは Noto Sans CJK JP を使いました。HTML と CSS のページを Playwright で開いて getComputedStyle() の値を調べ、画面を撮って、文字の中の白い画素と縁取りの濃い色の画素を数えました。Firefox と Safari、スマートフォンの実機、ほかのフォントでの見え方は試していません。

-webkit-text-stroke で縁取りを付ける

CSS は次のとおりです。color が文字の中の色、-webkit-text-stroke が縁取りの太さと色です。

css
/* 文字に縁取りを付ける */
.stroke {
  color: #ffffff;
  -webkit-text-stroke: 4px #1f2a30;
}

-webkit-text-stroke は、-webkit-text-stroke-width(線の太さ)と -webkit-text-stroke-color(線の色)をまとめて書く一括指定です。名前に -webkit- が付いていますが、記事の最後の表のとおり、Chrome、Firefox、Safari のどれでも使えます。

線を太くすると、文字の中が線で埋まる

水色の背景に、太字(700)で64pxの白い「猫の手」を置き、線の太さを変えて比べました。

上から順に4段の比較。水色の背景に白い太字の「猫の手」。「縁取りなし」は白い文字だけ。「-webkit-text-stroke: 2px」は細い黒い縁が付き、白い部分が少し細い。「4px」は縁が太くなり、白い部分がさらに細い線のようになっている。「8px」は文字全体がほぼ黒く塗りつぶされ、白い部分はほとんど見えない
線を太くするほど、文字の中の白い部分が細くなる

文字の中の白い画素を数えると、縁取りなしで4,513個でした。2pxにすると3,164個(約70%)、4pxで1,813個(約40%)に減り、8pxではわずか8個で、白い部分はほぼ消えました。

一方で、線は文字の外にも広がりました。縁取りなしのときは、文字の上端が段落の箱の上端から17px下にありましたが、8pxの線を付けると12pxの位置まで上に広がりました。左側では、段落の箱の左端より6px外にはみ出しました。線は輪郭を中心に、内側と外側の両方に描かれているということです。

細い字ではもっと早く白い部分がなくなります。字の太さを300にすると、縁取りなしで2,648個あった白い画素が、4pxの線を付けただけで43個になりました。

paint-order: stroke fill で、線を文字の後ろに回す

paint-order は、文字の塗りと輪郭の線を、どちらから先に描くかを決めるプロパティです。初期値の normal では塗りが先で、その上に線が描かれます。stroke fill にすると線を先に描き、その上に塗りを重ねるので、輪郭の内側にかかった線は塗りに隠れます。

css
/* 縁取りを文字の後ろに描き、文字の形を細らせない */
.stroke-outside {
  color: #ffffff;
  -webkit-text-stroke: 8px #1f2a30;
  paint-order: stroke fill;
}
上から3段の比較。「8px、paint-order の指定なし」は文字が黒く塗りつぶされている。「8px、paint-order: stroke fill」は白い太字の「猫の手」が元の形のまま残り、外側に黒い縁が付いている。「細い字(300)、8px、paint-order: stroke fill」も、細い白い文字の外側に黒い縁が付き、文字が読める
paint-order: stroke fill を足すと、8pxでも文字の形が残る(2段目と3段目)

8pxの線に paint-order: stroke fill を足すと、白い画素は4,517個で、縁取りなしのとき(4,513個)とほぼ同じでした。字の太さが300でも、白い画素は2,658個で、縁取りなしの2,648個とほぼ同じです。

外側への広がりは、paint-order を足す前と同じでした。縁取りなしと比べて、上に5px(17pxから12pxの位置へ)、左に7px(箱の1px内側から6px外へ)広がりました。輪郭の内側に入る分は塗りに隠れるので、外側に見える縁は、指定した8pxよりも細くなります。縁を太く見せたいときは、そのぶん -webkit-text-stroke の値を大きくします。

線だけで文字を描く(中を塗らない)

文字の中を塗らずに、輪郭の線だけにするときは、color を transparent にします。

css
/* 中を塗らずに、縁取りの線だけで文字を描く */
.outline-only {
  color: transparent;
  -webkit-text-stroke: 2px #067a98;
}
薄い水色の背景に、濃い水色の細い線だけで描かれた「猫の手」。文字の中は背景の色が見えている
中を透明にして、2pxの線だけで描いた文字

-webkit-text-fill-color は、文字の中を塗る色を決めるプロパティで、指定しないときは color の値が使われます。検証では -webkit-text-fill-color を指定していないので、計算値は color と同じ rgba(0, 0, 0, 0) になり、文字の中には背景の色だけが見えました。

この形では、paint-order: stroke fill を付けても見た目は変わりませんでした。線の濃い水色の画素は、付けても付けなくても2,423個です。上に重ねる塗りが透明なので、輪郭の内側にかかった線も隠れません。

text-shadow で代用すると、斜めの線がギザギザになる

-webkit-text-stroke を使わずに、text-shadow の影を上下左右と斜めの8方向にずらして重ね、縁取りに見せる書き方もあります。

css
/* text-shadow を8方向にずらして重ね、縁取りの代わりにする */
.shadow-outline {
  color: #ffffff;
  text-shadow:
    2px 0 0 #1f2a30, -2px 0 0 #1f2a30,
    0 2px 0 #1f2a30, 0 -2px 0 #1f2a30,
    2px 2px 0 #1f2a30, -2px -2px 0 #1f2a30,
    2px -2px 0 #1f2a30, -2px 2px 0 #1f2a30;
}

影は文字の後ろに置かれるので、文字は細りません。白い画素は4,517個で、縁取りなしのときとほぼ同じでした。

ずらす量を4pxにすると、-webkit-text-stroke との違いがはっきり出ました。影は文字の形をそのまま8方向に動かしたものなので、斜めや曲がった線のところで、影と影のすき間が段差になって見えます。

上下2段の比較。104pxの白い「猫の手」。上の「text-shadow を8方向に4pxずらす」では、黒い縁の外側が、「猫」の左のはらいや「の」の曲線、「手」の斜めの線のところで階段のようにギザギザになっている。下の「8px + paint-order: stroke fill」では、縁の外側がなめらかな線になっている
4pxずらした text-shadow は縁がギザギザになる(上)。-webkit-text-stroke ならなめらか(下)

太い縁取りを付けたいときは、-webkit-text-stroke と paint-order を組み合わせるほうが、きれいに仕上がります。text-shadow を使うときは、影の色を必ず書きます。互換性データの注記によると、Safari では色を書かない影は透明になります。

対応しているブラウザ

MDN の互換性データ(@mdn/browser-compat-data 8.1.8)で、この記事で使っている機能を調べました。

機能 Chrome Firefox Safari
-webkit-text-stroke 4 49 3
-webkit-text-fill-color 1 49 3
paint-order(HTML の文字の縁取り) 123 60 11
text-shadow 2 3.5 1.1
注意

paint-order は、Chrome 122 以前と Safari 10.1 以前では、HTML の文字の縁取りには効きません(互換性データの注記より)。その場合は、paint-order を指定していないときと同じ描き方になります。

​Posted by 86