この記事の対象: 実際に手を動かしているWeb制作者
読了時間: 約12分
写真やイラストを置かずに、色がにじみ合う背景を作る方法です。メッシュグラデーションは、複数の radial-gradient を重ねるだけで作れます。画像ファイルを1枚も読まないので、転送量が増えず、画面幅がどう変わっても破綻しません。背景だけで「ちゃんと作ってある」と感じさせたいキービジュアルや、ヒーローの下地に効きます。
ただし、文字を載せた瞬間に読めなくなる作り方が多い。色を足せば足すほど、どこが明るくてどこが暗いか自分で把握できなくなるためです。この記事では、そのまま動くCSSを全文載せたうえで、色の数と位置をどう決めるか、文字のコントラストをどう確保するか、動かす場合の負荷と prefers-reduced-motion の扱いまで書きます。
メッシュグラデーションとは何か、何を解決するのか

線形グラデーションでは出せない「にじみ」
linear-gradient は直線に沿って色が変わります。始点と終点が決まっているので、どうしても「帯」に見える。対してメッシュグラデーションは、キャンバスの上に色のついた光源をいくつか置いて、互いににじませたような見え方になります。Figma や Illustrator のメッシュツールで作る、あのふわっとした背景です。
これを画像で作ると、こうなります。
- フルスクリーン背景だと 1920×1080 以上が必要になり、JPEGでも150KB前後、PNGだと数百KBになりやすい
- Retina対応で2倍を用意すると、さらに倍
- 画面比率が変わると、意図した色の位置がずれる
- 色を少し変えたいだけで、デザインツールに戻って書き出し直し
CSSで作れば、全部なくなります。コードは1KB未満。色の変更はカラーコードを書き換えるだけ。縦長のスマホでも横長のデスクトップでも、% で位置を指定しておけば構図が保たれます。
CSSで作る
- 転送量ほぼゼロ。追加リクエストなし
- 色の修正はカラーコード1行
- 画面比率が変わっても構図が崩れない
- 表現できる形は円と楕円の重なりまで
画像を置く
- 150KB〜数百KB。Retina対応で倍
- 修正はデザインツールへ戻る
- 比率が変わると色の位置がずれる
- 自由な形・テクスチャが入れられる
図の通り、複雑な形やテクスチャが要るなら画像のほうが素直です。円の重なりで足りる表現なら、CSSで組んだほうが運用まで含めて楽になります。
仕組みは「透明に向かって消える円」を重ねること
肝は1つだけです。radial-gradient の終端を transparent にすると、円の外側が透けます。透けた部分から下のレイヤーが見えるので、重ねるほど色が混ざります。
/* 1つだけ置いた状態 */
background-image: radial-gradient(circle at 20% 30%, #ff6b9d 0%, transparent 50%);
これを , で区切って並べると、上に書いたものが前面に来ます。あとは置く位置と広がりを決めるだけです。
そのままコピーして動くメッシュグラデーション

まずは完成形です。HTMLとCSSを別々に載せますが、片方だけでも成立します。
基本形:4色・静止
<section class="mesh-hero">
<div class="mesh-hero__inner">
<h1 class="mesh-hero__title">光の重なりで、<wbr>背景をつくる</h1>
<p class="mesh-hero__lead">画像を1枚も使わずに、<wbr>色がにじみ合う背景を組み立てます。</p>
</div>
</section>
.mesh-hero {
position: relative;
min-height: 70svh;
display: grid;
place-items: center;
padding: 6rem 1.5rem;
overflow: hidden;
/* ベースの地色。グラデーションの隙間はここが見える */
background-color: #1b1033;
/* 奥から手前へ。最後に書いたものが一番手前に来る */
background-image:
radial-gradient(ellipse 70% 60% at 15% 20%, #4b2cc9 0%, transparent 60%),
radial-gradient(ellipse 60% 55% at 85% 15%, #d6246e 0%, transparent 60%),
radial-gradient(ellipse 65% 60% at 75% 85%, #1f6feb 0%, transparent 60%),
radial-gradient(ellipse 55% 50% at 25% 90%, #2f9e8f 0%, transparent 60%);
background-repeat: no-repeat;
}
.mesh-hero__inner {
position: relative;
z-index: 1;
max-width: 44rem;
text-align: center;
color: #ffffff;
word-break: keep-all;
overflow-wrap: anywhere;
}
.mesh-hero__title {
margin: 0 0 1.25rem;
font-size: clamp(1.75rem, 5vw, 3rem);
line-height: 1.35;
letter-spacing: 0.01em;
}
.mesh-hero__lead {
margin: 0;
font-size: clamp(0.95rem, 2.2vw, 1.0625rem);
line-height: 1.9;
color: #e7e2f5;
}
これで、紫を地色に、左上に青紫、右上にピンク、右下に青、左下に青緑が浮かぶ背景になります。min-height: 70svh の svh は、スマホのアドレスバーが出ている状態の高さを基準にする単位です。vh だとスクロール時にアドレスバーが隠れて高さが変わり、背景の構図がわずかに動きます。
明るい地色でやる場合
暗い背景は色が映えますが、案件によっては使えません。明るい版も型は同じで、彩度を落として広がりを狭めます。
.mesh-light {
position: relative;
min-height: 60svh;
display: grid;
place-items: center;
padding: 6rem 1.5rem;
overflow: hidden;
background-color: #f7f4ef;
background-image:
radial-gradient(ellipse 60% 50% at 10% 15%, #cfdcf5 0%, transparent 65%),
radial-gradient(ellipse 55% 45% at 90% 20%, #f6dfe4 0%, transparent 65%),
radial-gradient(ellipse 60% 50% at 80% 90%, #e3eee0 0%, transparent 65%);
background-repeat: no-repeat;
}
.mesh-light__inner {
position: relative;
z-index: 1;
max-width: 42rem;
color: #26211c; /* #f7f4ef に対して十分な差がある */
word-break: keep-all;
overflow-wrap: anywhere;
}
明るい地色のときは、彩度を上げると途端に安っぽくなります。各色のHSL彩度を 20〜35% あたりに収めると、紙に刷ったような落ち着きが出ます。ベージュ基調の配色をどこまで薄くできるかは、こちらの記事で実例ごとに見られます。
色の数と位置をどう決めるか

色は3〜5。6つ目から泥になる
試すと分かりますが、6色以上重ねた瞬間に色が互いを打ち消して、灰色がかった曖昧な面になります。混色の結果が予測できなくなるのも6色目あたりからです。
| 色数 | 見え方 | 向く用途 |
|---|---|---|
| 2色 | 単純な2トーン。メッシュに見えない | ボタンや小さなカード |
| 3色 | 構図が読める。最も扱いやすい | コーポレート、医療、金融 |
| 4〜5色 | 豊かで奥行きが出る | ブランドサイト、イベント、採用 |
| 6色以上 | 混ざって濁る。色が特定できない | 使わない |
3色と4色では難易度がかなり違います。迷ったら3色から始めて、物足りなければ1つ足す。この順番で進めると、どの色を足したら濁ったかが分かります。
位置は四隅+中央のどこかに置く
位置をランダムに散らすと、どこを見ればいいか分からない背景になります。実務では、置く場所をあらかじめ決めてしまったほうが速い。
4つ目の工程が抜けると、だいたい読めない背景になります。最初から文字の位置を決めて、そこを避けるように色を置くのが順番として正しい。
広がりは「はみ出させる」
radial-gradient の円を要素の内側に収めると、丸い玉がぽつんと置かれた見え方になります。意図した「にじみ」にするには、円の中心を枠の外か縁ぎりぎりに置いて、一部だけを見せます。
/* 枠の内側に収まっている。玉に見える */
background-image: radial-gradient(circle 200px at 50% 50%, #d6246e 0%, transparent 100%);
/* 枠の外から差し込む。にじみに見える */
background-image: radial-gradient(ellipse 80% 70% at -10% -20%, #d6246e 0%, transparent 60%);
at -10% -20% のように負の値を使えます。位置の指定は要素の左上を原点にした座標なので、マイナスにすると枠の外へ出ます。
transparent の罠:古いブラウザで灰色の線が出ることがある
transparent は仕様上 rgba(0, 0, 0, 0) です。つまり「透明な黒」。グラデーションの補間を素直にやると、色から透明へ向かう途中で黒が混ざり、境界に灰色のにじみが出ることがあります。
これを避ける書き方は、終端を「同じ色のアルファ0」にすることです。
/* 灰色が混じる可能性がある */
radial-gradient(ellipse 60% 50% at 20% 30%, #d6246e 0%, transparent 60%)
/* 同じ色のまま透明へ。混色の問題が起きない */
radial-gradient(ellipse 60% 50% at 20% 30%, rgb(214 36 110 / 1) 0%, rgb(214 36 110 / 0) 60%)
現行の主要ブラウザは premultiplied alpha で補間するので、transparent でも灰色は出にくくなっています。それでも、確実に意図した色で消したいなら後者の書き方が安全です。実案件では後者で書いておけば、検証の手間が1つ減ります。
文字を載せるときのコントラスト

背景が動く以上、固定の文字色は賭けになる
メッシュグラデーションの厄介なところは、背景の明るさが場所によって違う点です。文字を白にしても、下に明るい色が来ていればコントラストが足りません。WCAG 2.1 の AA は、本文(18px未満)で 4.5:1、大きな文字で 3:1 を要求します。背景のどこか1点で測って通しても、文字の端で割れていたら意味がない。
対策は3つあります。効き方と副作用が違います。
| 手法 | コントラストの確保 | 見た目への影響 | 描画の負荷 |
|---|---|---|---|
| オーバーレイを1枚敷く | 確実。計算できる | グラデーションが少し鈍る | ほぼゼロ |
| 文字側に影を付ける | 不確実。斜めから光る | 文字がぼやける | 小 |
| 文字の背後だけ色を抜く | 確実だが設計が重い | 構図を作り直すことになる | ほぼゼロ |
実務では1つ目を使います。推奨は、文字が乗る領域に半透明の単色を1枚重ねること。
.mesh-hero::before {
content: "";
position: absolute;
inset: 0;
/* 下に向かって濃くなる膜。文字の読みやすさを担保する */
background: linear-gradient(
to bottom,
rgb(12 6 28 / 0.15) 0%,
rgb(12 6 28 / 0.55) 100%
);
pointer-events: none;
}
この膜を敷くと、背景のどこに何色が来ていても、文字が乗る帯の明るさが一定の範囲に収まります。あとはその帯の実測値で文字色を決めればいい。
コントラストの測り方
目で見て判断しないこと。背景がグラデーションなので、ブラウザのスポイトで3点以上拾って、それぞれ計算します。
- Chrome DevTools で該当要素を選び、
Stylesの色見本からスポイトを起動する - 文字の左端・中央・右端の背後の色を拾う
- 拾った値を Chrome の Color Picker に入れると、
Contrast ratioが AA / AAA の判定付きで出る - 最も明るい箇所で 4.5:1(大きな文字は 3:1)を満たすか確認する
DevTools には Rendering パネルの Emulate vision deficiencies もあります。Achromatopsia(全色盲)に切り替えると、色の差だけで作った区別が全部消えます。グラデーション背景は明度差が曖昧になりやすいので、ここで文字が沈んだら膜を濃くします。
配色そのものの判断軸は、黒を基調にした事例をまとめた記事が参考になります。暗い地色に文字を乗せる案件では、どこまで色を効かせるかの感覚がつかめます。
ボタンとリンクは背景に溶かさない
グラデーションの上にそのままボタンを置くと、縁が溶けます。フォーカスリングも背景色と近いと見えません。背景の上に置く操作要素は、不透明の面を持たせるのが確実です。
.mesh-cta {
display: inline-flex;
align-items: center;
gap: 0.5em;
padding: 0.9em 2em;
border-radius: 999px;
background-color: #ffffff; /* 半透明にしない */
color: #1b1033;
font-weight: 700;
text-decoration: none;
}
.mesh-cta:focus-visible {
/* 背景がどんな色でも見えるよう、二重のリングにする */
outline: 3px solid #ffffff;
outline-offset: 3px;
box-shadow: 0 0 0 6px rgb(0 0 0 / 0.55);
}
outline を白、その外側に黒い box-shadow を重ねると、明るい背景でも暗い背景でもリングが残ります。outline: none で消すのは論外ですが、背景色依存の単色リングも実質見えない場面が出ます。
キーボード操作で詰まりやすいのは、むしろグラデーションを乗せたナビゲーション側です。開閉するメニューのフォーカス管理は別記事で手順を追ってまとめています。
アニメーションさせる場合の負荷と prefers-reduced-motion

background-position を動かすと毎フレーム再描画される
色をゆっくり動かしたくなります。ただ、background-position や background-size のアニメーションは、合成だけで済みません。背景のグラデーションを毎フレーム描き直すので、要素が大きいほど重くなります。フルスクリーンのヒーローでやると、スクロールが引っかかる端末が出ます。
負荷を抑える書き方は、背景を持つ要素を別に切り出して transform だけで動かす方法です。transform と opacity は合成だけで処理されるので、再描画が走りません。
<section class="mesh-anim">
<div class="mesh-anim__blobs" aria-hidden="true">
<span class="mesh-anim__blob mesh-anim__blob--a"></span>
<span class="mesh-anim__blob mesh-anim__blob--b"></span>
<span class="mesh-anim__blob mesh-anim__blob--c"></span>
</div>
<div class="mesh-anim__inner">
<h2 class="mesh-anim__title">動かすなら、<wbr>transform だけで動かす</h2>
</div>
</section>
.mesh-anim {
position: relative;
min-height: 70svh;
display: grid;
place-items: center;
padding: 6rem 1.5rem;
overflow: hidden;
background-color: #10132b;
isolation: isolate;
}
.mesh-anim__blobs {
position: absolute;
inset: -20%;
z-index: 0;
filter: blur(80px); /* ここが一番重い。値は控えめに */
pointer-events: none;
}
.mesh-anim__blob {
position: absolute;
width: 55%;
aspect-ratio: 1;
border-radius: 50%;
will-change: transform;
}
.mesh-anim__blob--a {
top: 5%;
left: 0%;
background: #3b2bd4;
animation: mesh-drift-a 18s ease-in-out infinite alternate;
}
.mesh-anim__blob--b {
top: 0%;
right: 0%;
background: #c92a63;
animation: mesh-drift-b 22s ease-in-out infinite alternate;
}
.mesh-anim__blob--c {
bottom: 0%;
left: 25%;
background: #1a7f8c;
animation: mesh-drift-c 26s ease-in-out infinite alternate;
}
@keyframes mesh-drift-a {
from { transform: translate3d(0, 0, 0) scale(1); }
to { transform: translate3d(12%, 8%, 0) scale(1.12); }
}
@keyframes mesh-drift-b {
from { transform: translate3d(0, 0, 0) scale(1.05); }
to { transform: translate3d(-10%, 14%, 0) scale(0.95); }
}
@keyframes mesh-drift-c {
from { transform: translate3d(0, 0, 0) scale(1); }
to { transform: translate3d(8%, -10%, 0) scale(1.1); }
}
/* 文字の読みやすさを担保する膜 */
.mesh-anim::after {
content: "";
position: absolute;
inset: 0;
z-index: 0;
background: linear-gradient(to bottom, rgb(8 6 20 / 0.2), rgb(8 6 20 / 0.6));
pointer-events: none;
}
.mesh-anim__inner {
position: relative;
z-index: 1;
max-width: 44rem;
text-align: center;
color: #ffffff;
word-break: keep-all;
overflow-wrap: anywhere;
}
.mesh-anim__title {
margin: 0;
font-size: clamp(1.5rem, 4.5vw, 2.5rem);
line-height: 1.4;
}
/* 動きを減らす設定の人には止めて見せる */
@media (prefers-reduced-motion: reduce) {
.mesh-anim__blob {
animation: none;
/* 止めた状態でも構図が成立する位置に置き直す */
transform: translate3d(4%, 4%, 0) scale(1.05);
}
}
blur の値がそのまま負荷になる
filter: blur() は、半径に応じて参照する画素が増えます。blur(80px) を画面いっぱいの要素にかけると、GPU メモリと描画時間を食います。実装するときの目安は次のとおりです。
- ぼかす要素は画面の半分程度までに収める。全画面に
blurをかけない blurの半径は 60〜100px に収める。それ以上は見た目がほとんど変わらないのに重くなるblurをかけた要素にwill-change: transformを付ける。合成レイヤーに分離され、再描画を避けられる- 同じ画面内で
backdrop-filterを併用しない。両方かけると低価格帯のスマホで目に見えて落ちる - 要素が画面外にあるセクションでは
content-visibility: autoを併用して、描画自体を遅らせる
blur を使わずに済ませる手もあります。radial-gradient のグラデーション自体が十分なめらかなので、円を描いてぼかすのではなく、グラデーションを transform で動かす構成なら blur は不要です。.mesh-anim__blob の background を radial-gradient(circle, rgb(59 43 212 / 0.9) 0%, rgb(59 43 212 / 0) 70%) に変えて、filter: blur() を外してみてください。見た目はほぼ同じで、負荷は大きく下がります。この構成を推します。
prefers-reduced-motion は「止める」だけでは足りない
animation: none を書いて終わりにすると、キーフレームの開始位置で固まります。開始位置が構図として成立していないと、止めた人だけ変な背景を見ることになります。上のコードで transform を書き直しているのはこのためです。
動きを減らす設定は、前庭障害や片頭痛を持つ人が実際に使っています。省略していい分岐ではありません。確認方法は、Chrome DevTools の Rendering パネルで Emulate CSS prefers-reduced-motion を reduce にすると即座に切り替わります。macOS なら「システム設定 → アクセシビリティ → ディスプレイ → 視差効果を減らす」、Windows なら「設定 → アクセシビリティ → 視覚効果 → アニメーション効果」でも同じです。
ブラウザ対応と確認のしかた

radial-gradient 自体は現行の主要ブラウザで広く使えます。問題になるのは周辺の機能です。
| 使う機能 | 代替が必要になる場面 |
|---|---|
radial-gradient 複数重ね |
現行の主要ブラウザで問題なし |
rgb(R G B / A) のスラッシュ記法 |
古いブラウザ向けには rgba() で書く |
svh / dvh |
対応していない環境向けに vh をフォールバックとして先に書く |
aspect-ratio |
対応していない環境向けに padding-top: 100% |
color-mix() |
案件の対応範囲を確認してから使う |
oklch() での指定 |
にじみは美しくなるが、未対応時は hsl() か16進数に落とす |
対応状況はバージョンで変わるので、記憶や記事内の数字を鵜呑みにしないでください。確認は MDN の各ページ下部にある Browser compatibility 表か、caniuse.com で機能名を引くのが確実です。案件のアクセス解析でブラウザ構成を見て、対応範囲を先に決めてから書き始めると手戻りがありません。
フォールバックは、新しい記法より前に古い記法を書くだけで効きます。
.mesh-hero {
min-height: 70vh; /* 古いブラウザがこれを読む */
min-height: 70svh; /* 対応していればこちらで上書きされる */
}
対応していないブラウザは svh の行を解釈できずに捨てるので、上の値が残ります。@supports を書かなくてもこの順序で成立します。
案件の種類ごとに、使うか使わないか

「どれも良い」では判断に使えないので、言い切ります。
使ってよい
ブランドサイト・イベント・サービスのLP。背景で世界観を出す必要があり、情報量が少ないページ。メッシュグラデーションの曖昧さが邪魔にならず、画像を使わずに雰囲気を作れる利点が効きます。1画面で完結するレイアウトとは相性が良い。
SaaS・ツール系のサービスサイト。「技術的に新しい」という印象を背景だけで出せます。ダッシュボードのスクリーンショットを前面に置くとき、下地に使うと画面が締まります。
採用サイトのキービジュアル。写真素材が揃っていない段階でも、背景だけで印象を作れます。ただし採用サイトは人の顔が効く領域なので、写真が用意できるならそちらを優先します。
条件付きで使う
コーポレートサイト。ヒーロー1箇所に限定し、彩度を落とすこと。全面に広げると業種を問わず軽く見えます。信頼を示す必要がある業種では、背景のにじみよりも情報の整理が効きます。
EC。商品写真の背後では使わない。色が干渉して商品の色が正しく伝わりません。使えるのはキャンペーンのバナーや会員登録の導線まわりに限ります。
使わない
医療・金融・法律・公共。背景の明るさが場所によって変わる作りは、文字の読みやすさの保証が難しい。この領域は、コントラストを単色で確定させるほうが正しい。業種別の実例を見ると、この領域が単色背景に寄っているのが分かります。
文字量の多い記事ページ・資料ページ。長文の背後にグラデーションを置くと、読み進めるうちに文字が沈む箇所が必ず出ます。本文の背景は単色にする。
ブランドカラーが2色以下に厳格に決まっている案件。メッシュグラデーションは混色で中間色が生まれます。ブランドガイドラインで使用色が限定されている場合、生まれた中間色が規定違反になります。レギュレーションを確認せずに提案しないこと。
流行っているから入れる、という判断はしません。背景で世界観を担保する必要があるかどうかで決めます。必要がなければ、単色のほうが速くて読みやすい。
よくある質問

CSSのメッシュグラデーションは重いですか
静止した状態なら、ほぼ負荷はありません。radial-gradient を4つ重ねても描画は1回で終わり、画像リクエストも発生しないので、画像背景より軽くなります。重くなるのは filter: blur() を大きな要素にかけたときと、background-position をアニメーションさせたときです。動かす場合は transform と opacity だけに限定してください。
色は何色まで重ねていいですか
3〜5色に収めます。6色以上重ねると互いに打ち消し合って、灰色がかった曖昧な面になります。3色が最も扱いやすく、構図が読める状態を保てます。迷ったら3色から始めて、必要に応じて1色ずつ足すと、どこで濁ったかが分かります。
グラデーション背景に文字を載せると読みにくくなります
背景の明るさが場所によって違うためです。文字が乗る領域に半透明の単色を1枚重ねて、明るさの幅を狭めてから文字色を決めてください。確認は Chrome DevTools のスポイトで、文字の左端・中央・右端の背後3点を拾い、それぞれコントラスト比を測ります。本文は 4.5:1、大きな文字は 3:1 が基準です。
prefers-reduced-motion はどう書けばいいですか
@media (prefers-reduced-motion: reduce) の中で animation: none にします。ただしそれだけでは、キーフレームの開始位置で固まって構図が崩れることがあります。止めた状態でも成立する位置に transform を書き直してください。動作確認は Chrome DevTools の Rendering パネルにある Emulate CSS prefers-reduced-motion で切り替えられます。
transparent で終わらせると境界が灰色になることがあるのはなぜですか
transparent は仕様上「透明な黒」(rgba(0,0,0,0))だからです。色から透明へ補間する途中で黒が混ざり、灰色のにじみが見える場合があります。終端を rgb(214 36 110 / 0) のように、同じ色のアルファ0で書くと確実に回避できます。現行ブラウザでは起きにくくなっていますが、こちらで書いておけば検証が1つ減ります。
まとめ
メッシュグラデーションは、radial-gradient を3〜5個重ねて終端を透明にするだけで作れます。画像を使わないので転送量が増えず、画面幅が変わっても構図が保てる。これが最大の利点です。
実装で押さえるのは4点です。
- 色は3〜5。位置は四隅と中央から選ぶ。文字が乗る領域を先に決めて、そこを避けて色を置く
- 文字の背後に半透明の膜を1枚敷く。背景の明るさの幅を狭めてから、実測でコントラストを確認する
- 動かすなら
transformとopacityだけ。background-positionのアニメーションと大きなblurは再描画の負荷になる prefers-reduced-motionで止めるときは、止めた状態の構図まで作る
そして、使う案件と使わない案件を分けること。ブランドサイト・SaaS・イベントでは効きます。医療・金融・公共、そして文字量の多いページでは、単色のほうが正しい。背景の表現力が必要かどうかで判断してください。





