TECH · グラデーション

濁らせず境界を思い通りに

01 / SAMPLESサンプルとコード

01

境界線がはっきりしたグラデーション

単独で開く ↗

同じ位置に2つの色の停止を重ねると、補間される幅が無くなり境界がくっきり分かれます。縞模様・進捗バー・円グラフを、画像を使わず1枚の背景で描いています。

コードへ ↓

表示領域より広い幅は、見本の枠内で横にスクロールできます。

HTML
選択してコピーすることもできます。
<div class="grid">
  <figure><div class="swatch smooth"></div><figcaption>停止位置が離れている(滑らかに移る)</figcaption></figure>
  <figure><div class="swatch split"></div><figcaption>50% に2色を重ねる(境界がはっきり)</figcaption></figure>
  <figure><div class="swatch stripes"></div><figcaption>repeating で縞模様</figcaption></figure>
  <figure>
    <!-- 見た目だけでなく、値を role="progressbar" と aria-valuenow で伝える -->
    <div class="progress" role="progressbar" aria-valuenow="64" aria-valuemin="0" aria-valuemax="100" aria-label="アップロードの進み具合"></div>
    <figcaption>進捗バー(64%)</figcaption>
  </figure>
  <figure><div class="pie" role="img" aria-label="内訳: 青45%、緑30%、黄25%"></div><figcaption>conic-gradient の円グラフ</figcaption></figure>
</div>

CSS
選択してコピーすることもできます。
/* 滑らかに移る。色の停止位置が離れているので、間が補間される */
.smooth { background: linear-gradient(90deg, #2563eb 0%, #22c55e 100%); }

/* 境界線をはっきりさせる。同じ位置(50%)に2色の停止を重ねると、
   その間の距離が0になり、補間される幅が無くなる */
.split { background: linear-gradient(90deg, #2563eb 50%, #22c55e 50%); }

/* 縞模様。停止位置を0にした2つ目以降は「直前の位置」扱いになるので、
   繰り返しの1周期ぶんだけ書けばよい */
.stripes {
  background: repeating-linear-gradient(
    -45deg,
    #1e293b 0 12px,
    #334155 0 24px
  );
}

/* 進捗バー。値をカスタムプロパティで渡し、境界の位置に使う。
   バーの要素を2つ重ねずに1枚の背景で描ける */
.progress {
  --value: 64%;
  height: 14px;
  border-radius: 99px;
  background: linear-gradient(90deg, #2563eb var(--value), #e2e6ee var(--value));
}

/* 円グラフ。conic-gradient も同じ考え方で、角度を重ねると境界がはっきりする */
.pie {
  width: 110px;
  aspect-ratio: 1;
  border-radius: 50%;
  background: conic-gradient(
    #2563eb 0 45%,
    #22c55e 0 75%,
    #f59e0b 0 100%
  );
}

AIへの指示文
選択してコピーすることもできます。
CSS の linear-gradient と conic-gradient で、次の5つを作ってください。画像は使いません。
要件:
- 青 #2563eb から緑 #22c55e へ滑らかに移る帯。
- 同じ2色を50%の位置でくっきり塗り分けた帯。同じ位置に2つの停止を重ねると
  補間される幅が0になる、と理由をコメントで書く。
- repeating-linear-gradient で斜めの縞模様。2つ目以降の停止位置を0と書くと
  直前の位置と同じ扱いになる、と書く。
- 進捗バー。値をカスタムプロパティ --value で渡し、境界の位置に使う。
  role="progressbar" と aria-valuenow も付ける。
- conic-gradient の円グラフ(45%・30%・25%)。role="img" と内訳を書いた aria-label を付ける。

02

中間が濁らない色空間の指定

単独で開く ↗

青から黄色へのように色相環で反対側にある2色は、sRGB で結ぶと真ん中が灰色にくすみます。補間の色空間を oklch にすると、鮮やかなまま移ります。

コードへ ↓

表示領域より広い幅は、見本の枠内で横にスクロールできます。

HTML
選択してコピーすることもできます。
<div class="row"><p>in srgb: 中間が灰色に濁る</p><div class="bar srgb"></div></div>
<div class="row"><p>in oklch: 色相を回って鮮やかなまま移る</p><div class="bar oklch"></div></div>
<div class="row"><p>in oklab: 暗く沈むのは防ぐが、中間の彩度は落ちる</p><div class="bar oklab"></div></div>
<div class="row"><p>非対応ブラウザにも備えた書き方</p><div class="bar safe"></div></div>

CSS
選択してコピーすることもできます。
/* in srgb を明示してで青から黄色へ。中間がくすんだ灰色になる。
   sRGB は RGB の値をそのまま直線で結ぶので、反対色どうしの間で
   彩度が落ちる */
.srgb { background: linear-gradient(90deg, #2563eb, #facc15); background: linear-gradient(in srgb 90deg, #2563eb, #facc15); }

/* 補間の色空間に oklch を指定する。明るさ・鮮やかさ・色相を分けて
   補間するので、中間でも彩度が保たれる(青→緑→黄と色相を回って移る) */
.oklch { background: linear-gradient(in oklch 90deg, #2563eb, #facc15); }

/* oklab は色相を回らず直線で結ぶ。中間が暗く沈むのは防げるが、
   彩度は落ちる。色相を経由させたくないとき(別の色が混ざって見えると
   困るとき)はこちら */
.oklab { background: linear-gradient(in oklab 90deg, #2563eb, #facc15); }

/* 非対応ブラウザには、中間色を手で1つ足した sRGB の指定が先に効く。
   後から書いた in oklch が読めるブラウザだけ上書きされる */
.safe {
  background: linear-gradient(90deg, #2563eb, #16a34a, #facc15);
  background: linear-gradient(in oklch 90deg, #2563eb, #facc15);
}

AIへの指示文
選択してコピーすることもできます。
青 #2563eb から黄 #facc15 への横向きのグラデーションを4本並べて、違いを比べられるようにしてください。
要件:
- 1本目は in srgb を明示する。中間が灰色に濁る理由を、RGB の値を直線で結ぶため、とコメントで書く。
- 2本目は linear-gradient(in oklch 90deg, ...)。明るさ・鮮やかさ・色相を分けて
  補間するので中間でも彩度が保たれる、と書く。
- 3本目は in oklab。中間が暗く沈むのは防げるが彩度は落ちる。途中で別の色相が
  混ざって見えると困るときに向く、と書く。
- 4本目は非対応ブラウザへの備え。先に中間色を1つ足した sRGB の指定を書き、
  後から in oklch の指定で上書きする。
- 各帯の上に、何を比べているかを1行で書く。

03

画像を使わないメッシュ風の背景

単独で開く ↗

位置と大きさの違う radial-gradient を4つ重ねています。広い面の緩やかなグラデーションは色の段が縞に見えやすいので、SVG で作った薄いノイズを重ねて消しています。

コードへ ↓

表示領域より広い幅は、見本の枠内で横にスクロールできます。

HTML
選択してコピーすることもできます。
<section class="mesh">
  <h2>請求業務を、毎月3日から30分へ。</h2>
  <p>背景は4つの円形のグラデーションを重ねただけです。色と位置を変えるだけで、季節やキャンペーンに合わせた表情を作れます。</p>
</section>

CSS
選択してコピーすることもできます。
.mesh {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding: clamp(2.5rem, 7vw, 4.5rem) clamp(1.5rem, 5vw, 3rem);
  color: #fff;
  border-radius: 20px;
  /* 地の色の上に、位置と大きさの違う radial を重ねる。
     重ねる順は「後に書いたものほど下」。いちばん下に地の色を置く */
  background:
    radial-gradient(40% 55% at 15% 20%, #7c3aed 0%, transparent 70%),
    radial-gradient(45% 60% at 85% 15%, #ec4899 0%, transparent 70%),
    radial-gradient(50% 65% at 70% 90%, #f59e0b 0%, transparent 70%),
    radial-gradient(45% 60% at 20% 85%, #2563eb 0%, transparent 70%),
    #0f172a;
}

/* 縞(バンディング)を消すノイズ。広い面の緩やかなグラデーションは
   8bit の色で段が見えやすい。ごく薄い粒を重ねると段が目立たなくなる。
   ノイズは SVG の feTurbulence を data URI で埋め込み、画像ファイルを増やさない */
.mesh::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  opacity: .18;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  pointer-events: none;
}

.mesh h2 { margin: 0 0 .6rem; font-size: clamp(1.5rem, 4vw, 2.2rem); line-height: 1.4; }
/* 背景が明るい部分に来ても読めるよう、文字の後ろにだけ薄い影を敷く。
   背景全体を暗くするより、色の鮮やかさを保てる */
.mesh h2, .mesh p { text-shadow: 0 1px 12px rgba(15, 23, 42, .45); }
.mesh p { margin: 0; max-width: 34em; line-height: 1.85; }

AIへの指示文
選択してコピーすることもできます。
ヒーロー用の、メッシュ風のグラデーション背景を作ってください。画像ファイルは使いません。
要件:
- 地の色 #0f172a の上に、位置と大きさの違う radial-gradient を4つ重ねる
  (紫 #7c3aed、桃 #ec4899、橙 #f59e0b、青 #2563eb)。各色は transparent 70% で消える。
- 後に書いた背景ほど下に重なる、とコメントで書く。
- ::before に、SVG の feTurbulence で作ったノイズを data URI で敷き、
  不透明度0.18・mix-blend-mode: overlay で重ねる。
  広い面の緩やかなグラデーションは8bitの色で段が縞に見える、と理由を書く。
- 背景は isolation: isolate で閉じ、ノイズを z-index: -1 で文字の下に置く。
- 見出しと本文の後ろにだけ薄い text-shadow を敷き、明るい部分に来ても読めるようにする。
  背景全体を暗くするより色の鮮やかさを保てる、と書く。

04

読めなくならない文字のグラデーション

単独で開く ↗

対応環境でだけ文字を透明にして背景を切り抜き、非対応や強制カラーモードでは単色の文字に戻します。角度は @property で登録して回し、動きを減らす設定では止めます。

コードへ ↓

表示領域より広い幅は、見本の枠内で横にスクロールできます。

HTML
選択してコピーすることもできます。
<div class="hero">
  <h1 class="grad-text">見出しだけに色を集める</h1>
  <p>本文は単色のまま。グラデーションは1画面に1か所だけにします。</p>
</div>

CSS
選択してコピーすることもできます。
/* 角度を動かせるよう、型付きのカスタムプロパティとして登録する。
   登録しないと、カスタムプロパティは文字列として扱われ、アニメーションしない */
@property --angle {
  syntax: "<angle>";
  initial-value: 90deg;
  inherits: false;
}

.grad-text {
  margin: 0;
  font-size: clamp(2rem, 6vw, 3.4rem);
  font-weight: 800;
  line-height: 1.3;
  /* 背景が描けない環境(強制カラーモードなど)で、この色がそのまま出る */
  color: #4338ca;
}
/* 対応している環境でだけ、文字を透明にして背景を文字の形に切り抜く。
   先に color を透明にしてから非対応だと、文字が消えて何も読めなくなる */
@supports (background-clip: text) or (-webkit-background-clip: text) {
  .grad-text {
    /* いちばん明るい色でも地とのコントラストが 4.5:1 を切らない範囲に留める */
    background: linear-gradient(var(--angle), #4338ca, #be185d 55%, #c2410c);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    animation: turn 8s linear infinite;
  }
}
@keyframes turn { to { --angle: 450deg; } }

/* 動きを減らす設定では止める。止めても色の並びはそのまま残る */
@media (prefers-reduced-motion: reduce) {
  .grad-text { animation: none; }
}
/* 強制カラーモードでは背景が消されるので、文字色を元に戻す */
@media (forced-colors: active) {
  .grad-text { color: CanvasText; background: none; }
}

AIへの指示文
選択してコピーすることもできます。
見出しの文字をグラデーションで塗ってください。
要件:
- 既定の color に単色(#4338ca)を指定しておく。
- @supports (background-clip: text) の中でだけ、背景を文字の形に切り抜き、color を透明にする。
  先に透明にしてから非対応だと文字が消えて読めなくなる、と理由を書く。
- いちばん明るい色でも、地とのコントラストが4.5:1を切らない3色を選ぶ。
- 角度を @property で <angle> 型として登録し、8秒で1回転させる。
  登録しないとカスタムプロパティは文字列扱いでアニメーションしない、と書く。
- prefers-reduced-motion では回転を止める。
- forced-colors: active では背景を外し、文字色を CanvasText に戻す。

02 / OVERVIEWこの技術について

CSS のグラデーションは、2010年ごろにベンダープレフィックス付きで使えるようになり、いまではヒーローの背景からボタン、見出しの文字まで、画像を使わずに色の変化を作る標準の手段になりました。1行で書けて画像より軽く、色を変えるのも値を差し替えるだけです。

その手軽さのぶん、思った通りにならない場面も決まっています。真ん中が濁る、縞が出る、文字が読めなくなる。どれも原因がはっきりしていて、書き方で避けられます。

境界をはっきりさせるのも、グラデーションの仕事

グラデーションというと滑らかな色の移り変わりを思い浮かべますが、境界をくっきり分ける使い方のほうが実務では頻繁に出てきます。2色の帯、縞模様、進捗バー、円グラフ。どれも画像や要素を重ねずに、背景1枚で描けます。

停止位置を離すと滑らかに移り、同じ位置に重ねると境界がはっきりする 停止位置を離す 青 0%, 緑 100% 間の全部が補間される 同じ位置に重ねる 青 50%, 緑 50% 補間される幅が0になり、境界ができる 縞模様は repeating-linear-gradient に1周期ぶんだけ書けばよい
確かめるときは、停止位置の差を少しずつ広げてみてください。差が1pxでもあれば、そこに細いぼかしが生まれます。くっきりさせたい境界は、必ず同じ値にします。

中間が濁るのは、色空間のせい

CSS Images Level 4では既定の補間色空間をOklabとしています。一方、従来のHEXやRGBだけの指定をsRGBとして扱う互換動作もあるため、比較するときは in srgb / in oklab / in oklch を明示します。

青から黄色へのグラデーションを sRGB で補間すると、真ん中がくすんだ灰色になります。色の指定が間違っているのではありません。sRGB では、RGB の値をそのまま直線で結ぶので、色相環で反対側にある2色の間では彩度が落ちるのです。

sRGB は反対色を直線で結ぶので中間が灰色になり、oklch は色相を回るので鮮やかさが保たれる sRGB: 中心(灰色)を通る in oklch: 縁を回って鮮やかなまま 円の中心ほど 彩度が低い 反対色を直線で 結ぶと中心を通る
濁りが問題になるのは、青と黄、赤と緑のように色相環の反対側にある組み合わせです。青と紫のような隣り合う色なら、既定のままでもほとんど差は出ません。

反対色どうしを結ぶときは in oklch を付ける。非対応ブラウザには、中間色を1つ手で足した sRGB の指定を先に書いておけば、同じ見た目に近づきます。

広い面の縞は、ノイズで消す

ヒーローの背景のように広い面へ緩やかなグラデーションを敷くと、色が段になって縞模様のように見えることがあります。8bitの画像や表示経路では1チャンネルあたり256段階に限られ、変化が緩やかなほど1段の幅が広がって目に入るからです。

緩やかなグラデーションは色の段が縞に見える。薄いノイズを重ねると段の境目が散る そのまま 段の境目が縦の線に見える 薄いノイズを重ねる 境目が散って見えなくなる opacity: .18 / mix-blend-mode: overlay
ノイズの濃さは、近づいて見て粒が分かるか分からないかが目安です。離れて見て粒が見えるなら濃すぎます。写真の上に重ねる場合は、さらに半分に落とします。

ノイズは SVG の feTurbulence を data URI で埋め込むと、画像ファイルを1つも増やさずに済みます。複数の radial-gradient を重ねて作るメッシュ風の背景と組み合わせるのが定番です。

文字に使うなら、読めることを先に確保する

見出しの文字をグラデーションで塗るのは効果的ですが、壊れ方が2つあります。1つは、いちばん明るい部分で地とのコントラストが足りなくなること。もう1つは、背景を文字の形に切り抜く指定に対応していない環境で、透明にした文字が消えて何も読めなくなることです。

崩れる形いきなり color: transparent にする

切り抜きに対応していない環境や、強制カラーモードで背景が外された環境では、透明な文字だけが残って見出しが消えます。

効く形単色を既定にして、対応環境でだけ切り抜く

既定の color に単色を入れ、@supports の中でだけ透明にします。強制カラーモードでは背景を外して文字色を戻すと、どの環境でも見出しが読めます。

文字のグラデーションは、いちばん明るい色の地点でコントラストを測る 見出しの左端 中ほど いちばん明るい色 ここで 4.5:1 を切らないか測る 平均の色で測ると、 明るい端の読めなさを見落とす
確かめ方は、開発者ツールでグラデーションの最も明るい色だけを文字色にしてコントラストを測ることです。そこで基準を満たせば、どの部分も読めます。

グラデーションは、1画面に1か所だけにとどめると効きます。見出しも背景もボタンも塗ると、どこが主役か分からなくなり、どれも目立たなくなります。

参考:W3C:グラデーションの補間色空間。

03 / NOTES使いどころとつまずきどころ

向いている場面

  • ヒーローやCTAの背景
  • 色の切り替えで区切る帯や見出し
  • 進捗バーや円グラフのような簡単な図
  • 見出しの一部だけを目立たせる文字の塗り

つまずきやすい点

  • 反対色どうしを sRGB で結ぶと、中間が灰色に濁る
  • 広い面の緩やかなグラデーションは、段が縞になって見える
  • 文字の塗りは、いちばん明るい色でコントラストが切れる
  • 非対応環境で文字を透明にすると、何も読めなくなる
  • 画面のあちこちに使うと、どこが主役か分からなくなる

04 / FAQよくある質問

境界線がはっきりしたグラデーションはどう書く?

同じ位置に2つの色の停止を置きます。linear-gradient(90deg, 青 50%, 緑 50%) のように書くと、50%の位置で補間される幅が0になり、くっきり塗り分けられます。縞模様なら repeating-linear-gradient を使います。

グラデーションの真ん中が灰色っぽくなるのはなぜ?

sRGB 補間では、RGB の値を直線で結ぶので、青と黄のような反対色の間で彩度が落ちます。linear-gradient(in oklch 90deg, ...) のように補間の色空間を指定すると、鮮やかなまま移ります。

背景のグラデーションに縞が出る

広い面の緩やかなグラデーションでは、8bit の色の段差が縞として見えます。色の差を大きくするか、ごく薄いノイズを重ねると目立たなくなります。ノイズは SVG の feTurbulence を data URI で埋め込めば、画像ファイルを増やさずに済みます。

文字のグラデーションは検索エンジンや読み上げに影響する?

文字そのものは HTML に残るので、検索エンジンにも読み上げにも普通の文字として渡ります。影響が出るのは画像の文字に置き換えた場合です。気をつけるべきは、いちばん明るい色の部分のコントラストです。

同じ技術を使った完成例を、画面の型ごとにまとめています。作りたい画面が決まっているなら、こちらから探すほうが早いはずです。

06 / MOREほかの技術