zudo-css-wisdom

Type to search...

to open search from anywhere

テキストのアウトラインとストローク効果

作成2026年4月3日更新2026年4月24日Takeshi Takatsudo

問題

テキストのアウトライン効果は一見シンプルに見えますが、レンダリングの詳細はシンプルではありません。

中央揃えのストロークはグリフの内側に食い込みます。これはラテン文字のカウンター(OPR など)で目立ちます。 のような画数の多い CJK 文字ではさらに顕著です。text-shadow で作る太いフェイクアウトラインも品質がすぐに劣化します。角がカクカクになり、曲線が膨らんでしまいます。

アウトラインテキストに求められる要件:

  • CSS ベースの実装
  • 暗い背景でも読みやすいこと
  • 日本語と英語の両方で安定していること
  • ブラウザサポートとアウトラインの太さのトレードオフが明確であること

解決方法

アウトラインの太さとレンダリング要件に応じた手法を使い分けます。

  • 標準的な UI テキストや中程度のアウトラインには、-webkit-text-strokepaint-order: stroke fill を使います。
  • text-shadow は非常に細い(1px)アウトラインのフォールバックとしてのみ使います。
  • ブラウザネイティブで最もクリーンなアウトライン品質を得るには、SVG <text>stroke を使います。
  • 真の外側のみのアウトラインが必要な場合にのみ、SVG feMorphology を使います。

コード例

-webkit-text-stroke + paint-order

-webkit-text-stroke: paint-order なしとあり
/* paint-order なし — ストロークがグリフ内部を侵食する */
.text-outline {
  color: hsl(48 100% 68%);
  -webkit-text-stroke: 6px hsl(336 80% 58%);
}

/* paint-order あり — フィルがストロークの内側半分を覆う */
.text-outline-improved {
  color: hsl(48 100% 68%);
  -webkit-text-stroke: 6px hsl(336 80% 58%);
  paint-order: stroke fill;
}

-webkit-text-stroke は中央揃えのストロークを描画します。半分が内側に、半分が外側に広がります。内側の半分がカウンターや内部の隙間を狭めるため、太いウェイトや CJK テキストで特に目立ちます。

paint-order: stroke fill はストロークを先に描画し、その上にフィルを重ねます。フィルが内側半分を覆い隠すため、ジオメトリは中央揃えのままでも、見た目は外側ストロークに近くなります。

CJK に関する注意: 日本語のグリフは内部構造が密です。太い中央揃えストロークはラテン文字よりも早く細部の隙間を塞いでしまいます。CJK テキストで -webkit-text-stroke を使う場合は、必ず paint-order: stroke fill を追加してください。

ブラウザサポート: -webkit-text-stroke は 2017 年 4 月から Baseline です。CSS の paint-order は 2024 年 3 月に Baseline に到達しました。

text-shadow によるアウトラインハック

4 方向、8 方向、高密度 text-shadow アウトライン
/* 4 方向 — ダイヤモンド形になり不十分 */
.outline-4 {
  text-shadow:
    -1px 0 0 hsl(338 80% 56%),
     1px 0 0 hsl(338 80% 56%),
     0 -1px 0 hsl(338 80% 56%),
     0  1px 0 hsl(338 80% 56%);
}

/* 8 方向 — 1px アウトラインの最低限の形 */
.outline-8 {
  text-shadow:
    -1px -1px 0 hsl(338 80% 56%),
     0   -1px 0 hsl(338 80% 56%),
     1px -1px 0 hsl(338 80% 56%),
    -1px  0   0 hsl(338 80% 56%),
     1px  0   0 hsl(338 80% 56%),
    -1px  1px 0 hsl(338 80% 56%),
     0    1px 0 hsl(338 80% 56%),
     1px  1px 0 hsl(338 80% 56%);
}

太いアウトラインをプログラムで生成する方法:

function createOutlineShadows(radius, color) {
  const shadows = [];
  for (let y = -radius; y <= radius; y += 1) {
    for (let x = -radius; x <= radius; x += 1) {
      if (x === 0 && y === 0) continue;
      if (Math.hypot(x, y) <= radius) {
        shadows.push(`${x}px ${y}px 0 ${color}`);
      }
    }
  }
  return shadows.join(",\n");
}

text-shadow は実際のストロークを描画するわけではありません。テキストのオフセットコピーを重ねているだけです。4 方向ではダイヤモンド形になります。8 方向が 1px のリングとして最低限使える形です。太いアウトラインには多数のシャドウエントリが必要になり、それでもカクカクした膨らんだ描画になります。

CJK に関する注意: 画数の多い漢字ほど弱点が早く現れます。ラテン文字よりも早く内部の細部がつぶれてしまいます。この手法は最大 1px に留めてください。

SVG テキストの stroke + paint-order

SVG テキスト: stroke、stroke-width、stroke-linejoin、paint-order
<svg viewBox="0 0 920 260" xmlns="http://www.w3.org/2000/svg">
  <text
    x="460" y="112"
    text-anchor="middle"
    font-size="68" font-weight="900"
    font-family="system-ui, sans-serif"
    fill="hsl(54 100% 70%)"
    stroke="hsl(338 80% 58%)"
    stroke-width="10"
    stroke-linejoin="round"
    paint-order="stroke fill">
    縁取りテキスト
  </text>
</svg>

SVG テキストはブラウザネイティブで最もクリーンなアウトライン品質を実現します。テキスト要素に対して strokestroke-widthstroke-linejoinpaint-order を直接制御できます。特に太いアウトラインでは、text-shadow よりもシャープなレンダリングが得られます。

太いアウトラインには stroke-linejoin="round" を設定してください。ラウンドジョインによって鋭い角のスパイクを防ぎ、密な曲線をより滑らかに保てます。

CJK に関する注意: 太いアウトラインとクリーンな角が必要な太い日本語ディスプレイテキストには、ブラウザネイティブの手法として最も優れた選択肢です。

SVG feMorphology フィルタ

SVG feMorphology による外側のみのアウトライン
<filter id="outside-outline" x="-15%" y="-25%" width="130%" height="150%">
  <feMorphology in="SourceAlpha" operator="dilate" radius="6" result="dilated" />
  <feFlood flood-color="hsl(338 80% 58%)" result="outlineColor" />
  <feComposite in="outlineColor" in2="dilated" operator="in" result="outlineFill" />
  <feComposite in="outlineFill" in2="SourceAlpha" operator="out" result="outsideOnly" />
  <feMerge>
    <feMergeNode in="outsideOnly" />
    <feMergeNode in="SourceGraphic" />
  </feMerge>
</filter>

このフィルタはソースアルファを膨張させ、拡大されたシルエットに色を付け、そこから元のアルファを除去し、元のテキストを上に合成します。結果として、純粋に外側のみのアウトラインが得られます。

この記事で紹介する手法の中で、真の外側のみのストロークを作れるのはこの手法だけです。ストロークの内側への侵食が許容できない場合に有用です。

フィルタ領域は xywidthheight 属性で拡大する必要があります。デフォルトの領域のままだと、アウトラインがクリッピングされます。

CJK に関する注意: 内部のカウンターが完全に保持されます。複雑なレイアウトではフィルタのパフォーマンスとクリッピングをテストしてください。

比較

全手法の横並び比較

クイックリファレンス

手法太いアウトライン(3-5px 以上)エッジ品質CJK 互換性ユースケース
-webkit-text-stroke + paint-order良好良好paint-order 併用で良好一般的な UI テキストや見出し
text-shadow アウトラインハック不良低い細いアウトラインのみ可細いフォールバックアウトライン
SVG <text> + stroke優秀優秀優秀ディスプレイテキストや精密なレンダリング
SVG feMorphology フィルタ優秀優秀優秀真の外側のみアウトライン

AI がよくやるミス

  • -webkit-text-strokepaint-order なしで使う — ストロークがグリフの内部を侵食します。特に CJK テキストでは密なカウンターが塞がってしまいます
  • text-shadow を 4 方向のみで使う — ダイヤモンド形になり、円形のアウトラインにはなりません。1px でも最低 8 方向が必要です
  • 太い text-shadow アウトラインを適用する — 太いアウトラインには 20 以上のシャドウが必要で、それでもカクカクした膨らんだエッジになります
  • SVG テキストで stroke-linejoin="round" を忘れる — 太いアウトラインの曲線や CJK の角で、デフォルトの miter ジョインによる鋭いスパイクが発生します
  • SVG フィルタ領域を拡大しないfeMorphologydilate はデフォルトのフィルタ領域を超えて拡張するため、アウトラインがクリッピングされます

使い分け

-webkit-text-stroke + paint-order

ほとんどの CSS のみのテキストアウトラインに適しています。短く、読みやすく、保守も容易です。

text-shadow

細いフォールバックアウトラインにのみ使います。品質を重視するなら 1px で止めてください。

SVG <text> + stroke

アウトラインの品質がデザインの一部である場合に使います。太い日本語ディスプレイテキストに最適なブラウザネイティブの選択肢です。

SVG feMorphology

アウトラインがグリフの完全に外側に留まる必要がある場合に使います。その要件は一般的ではありませんが、他の CSS/SVG 手法では実現できません。

CSS の範囲を超えて

画像合成やエクスポート品質のレンダリングには、Canvas 2D の <code>strokeText()</code> / <code>fillText()</code>opentype.js によるテキストからパスへの変換、Fabric.jsKonva.js などの canvas ライブラリがより優れたプログラム的制御を提供します。

参考リンク

Revision History