テクニック

CSSノイズ(グレイン)テクスチャの作り方|SVGと画像の重さ比較

この記事の対象: 背景のビジュアルを自分で実装するWeb制作者・フロントエンド担当
読了時間: 約12分

ノイズ(グレイン)は、ベタ塗りやグラデーションに出てしまう「CSSで塗っただけ」の平板さを消すための手当てです。粒子をわずかに乗せるだけで、同じ配色が紙やフィルムに近い質感に変わり、グラデーションのバンディング(縞)も目立たなくなります。

ただしこの手法は、実装の選び方で表示速度が大きく変わります。SVGフィルターを背景画像として敷く形なら合成は軽く済みますが、filter: url() を生きている要素にかけると、スクロールのたびにフィルターが再計算されて描画が詰まります。この記事では、そのままコピーして動くCSSと、重くなる境界、案件ごとに使うか避けるかの判断までをまとめます。


ノイズ(グレイン)が解決するのは「色面の嘘くささ」

ノイズ(グレイン)が解決するのは「色面の嘘くささ」

ノイズを足す目的は、見た目のトレンドに乗ることではありません。解決している問題は3つに絞れます。

困っていること ノイズで何が変わるか
広い単色面が安っぽく見える 粒子で面が呼吸し、紙や布に近い印象になる
グラデーションに縞(バンディング)が出る 粒子が階調の段差を散らし、縞が視認しづらくなる
写真と地の面が馴染まない 両方に同じ粒を乗せると素材感が揃う

3つ目は実務で効く場面が多いところです。撮影した写真と、CSSで塗ったセクション背景が並ぶと、写真側だけに情報量があるため境目が浮きます。全体に薄くグレインを通すと、この段差が減ります。

逆に言えば、この3つのどれにも当てはまらないなら入れる理由がありません。白い余白が主役のレイアウトや、情報密度の高い管理画面に粒子を乗せても、得るものはほぼありません。配色そのものの設計で解ける話も多いので、ベージュ系Webデザイン配色事例8選|業界別の使いどころのような配色の整理を先に試すほうが早いこともあります。


SVGのfeTurbulenceでノイズを作るCSS

SVGのfeTurbulenceでノイズを作るCSS

いちばん扱いやすいのは、SVGの feTurbulence で生成したノイズをデータURIにして、background-image として敷く方法です。外部ファイルを増やさず、色も粒度もCSS側で調整できます。

そのまま動く基本形

.grain-section {
  position: relative;
  isolation: isolate;
  padding: 120px 24px;
  background: linear-gradient(160deg, #1d2b26 0%, #0e1714 100%);
  color: #f2efe8;
}

.grain-section::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: 0.16;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23g)'/%3E%3C/svg%3E");
  background-repeat: repeat;
  background-size: 180px 180px;
}

このコードの要点を、書いた理由とあわせて並べます。

  • ::after に逃がして z-index: -1 — 背景そのものに重ねると background-image の枠を奪ってしまい、グラデーションと併用できません。擬似要素なら両方を残せます。isolation: isolate を親に付けて、z-index: -1 が祖先の背景まで抜けていくのを止めます。
  • pointer-events: none — 全面を覆う要素なので、これを忘れると下のリンクやボタンがクリックできなくなります。キーボード操作は擬似要素では壊れませんが、マウス操作が無言で死ぬので必須です。
  • stitchTiles='stitch' — タイルの継ぎ目をつなぐ指定です。外すと repeat したときに格子状の筋が見えます。
  • feColorMatrix type='saturate' values='0' — feTurbulence の出力はRGBAすべてにノイズが乗るため、そのままでは色のついた砂になります。彩度を落としてモノクロの粒にします。
  • background-size を明示 — これが抜けると、高DPI端末で粒が意図より細かくなったり、ブラウザ間で粒径が揃わなかったりします。CSSピクセルで固定しておくのが安全です。
  • データURI内の # は %23 — エンコードせずに書くとフラグメント識別子として扱われ、フィルターが当たらず真っ黒い矩形になります。つまずく箇所の筆頭です。

粒の調整で触るのは3つだけ

属性・プロパティ 役割 実用の目安
baseFrequency 粒の細かさ 0.6〜0.9で細かい砂、0.2〜0.4で粗い紙
numOctaves 粒の複雑さ 2〜4。増やすと生成コストが上がる
opacity 乗せる強さ 暗い面で0.12〜0.20、明るい面で0.04〜0.08

numOctaves を6や8にすると、ラスタライズの計算量が増えるだけで見た目はほとんど変わりません。3で足ります。

明るい背景に乗せるとき

白やベージュの面に黒い粒をそのまま重ねると、灰色のベールがかかって彩度が落ちます。ブレンドモードで粒だけを残します。

.grain-light {
  position: relative;
  isolation: isolate;
  background: #f4efe6;
  color: #22201c;
}

.grain-light::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: 0.35;
  mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g2'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='0.5' intercept='0.5'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23g2)'/%3E%3C/svg%3E");
  background-size: 180px 180px;
}

feComponentTransfer でアルファのコントラストを下げているのは、multiply で潰れすぎるのを防ぐためです。暗い面では mix-blend-mode: overlay や soft-light のほうが馴染みます。ただしブレンドモードは後述のとおり描画コストを持つので、使う範囲はセクション単位に留めます。


画像のノイズテクスチャとSVGの使い分け

画像のノイズテクスチャとSVGの使い分け

もうひとつの定番は、ノイズを描いたPNGを敷く方法です。どちらが良いかは、作るものによって答えが変わります。

SVGデータURIと画像タイルの違い

SVG(feTurbulence)

  • CSSの中で粒度と色を調整できる
  • リクエストが増えない
  • 初回のラスタライズに計算が走る
  • 粒の形は数式任せ

画像タイル(PNG)

  • デザイナーが粒を作り込める
  • 描画は単純な貼り付けだけ
  • 調整に書き出し直しが必要
  • 細かいノイズは圧縮が効きにくい

図の読みどころは、調整の自由度と描画の単純さが逆向きだという点です。制作中に何度も粒を触るならSVG、見た目が確定していて描画を最優先にするなら画像が向きます。

実装判断としては、次のように切り分けると迷いません。

  • コーディング側で詰める案件はSVG — 数値を変えるだけで試せるので、レビューの往復が速くなります。ファイルの受け渡しも発生しません。
  • 紙の繊維や印刷のアミ点など、質感そのものがデザインの主題なら画像 — feTurbulence が作れるのは乱流ノイズだけで、特定の質感は再現できません。
  • ノイズを何種類も使い分けるなら画像に寄せる — SVGのデータURIはCSSの可読性を確実に落とします。3種類を超えたら、ファイルに分けたほうが保守できます。

PNGを選ぶ場合、粒の細かいノイズは隣り合うピクセルの相関が低いため、PNGの圧縮がほとんど効きません。写真より小さくなるとは限らないので、書き出したら必ずバイト数を確認してください。グレースケール+アルファの8bitで書き出す、タイルを128px角まで小さくする、といった手当てで下げられます。


CSSノイズが重くなる場所と、軽くする手当て

CSSノイズが重くなる場所と、軽くする手当て

ここが本題です。ノイズの実装で表示が詰まる原因は、ほぼ次の3つに集約されます。

重くなる3つの型

1. filter: url(#noise) を生きている要素にかけている

これが最悪の形です。SVGフィルターを要素のプロパティとして適用すると、その要素が再描画されるたびにフィルターが走ります。スクロールやホバーで内容が動くたびに計算が入り、フレームが落ちます。背景画像として敷いた場合は一度ラスタライズした結果がタイルとして使い回されるため、負荷の性質がまったく違います。ノイズを filter プロパティで当てない、これを原則にしてください。

2. フィルター対象の面積が大きい

feTurbulence のコストは生成する面積に比例します。180px角のタイルを敷くのと、ビューポート全体を1枚のSVGで覆うのは別物です。特にiOS Safariは大きなフィルター領域でメモリ上限に当たりやすく、ノイズが出ないまま真っ白になる、スクロールで一瞬消える、といった症状が出ます。タイルは200px角までを上限の目安にして、background-repeat: repeat で敷くのが安全側です。

3. 画面全体を覆う固定オーバーレイ + ブレンドモード

position: fixed のグレイン層に mix-blend-mode を付けると、スクロールの各フレームで「下のコンテンツ」と「上の粒」を合成し直す必要が生まれます。コンテンツが動く=合成結果が変わるため、コンポジタがレイヤーを使い回せません。フルスクリーンの固定グレインは、やるなら mix-blend-mode を使わず opacity だけで済ませます。

手当てのチェックリスト

ノイズを軽く入れる手順
小さく作る180px角前後のタイルを repeat で敷く
層を限るセクション単位の擬似要素に留める
固定を避けるfixed とブレンドの併用をやめる
実機で測る中位のAndroidでスクロールを録る

この順で進めると、手戻りがほぼ起きません。最後の実機確認だけは省略できない工程です。

加えて、効いた手当てを並べます。

  • グレイン層は1ページ3枚までを目安に。セクションごとに擬似要素を増やすと、合成レイヤーが積み上がります。同じ粒なら、最上位のコンテナ1枚に敷いて中身を透かすほうが安く済みます。
  • content-visibility: auto を長いページのセクションに付けると、画面外のグレイン層のラスタライズも後回しになります。高さが変わるので contain-intrinsic-size を併記してください。
  • アニメーションさせるなら transform だけで動かす。background-position のアニメーションはペイントが走ります。タイルを一回り大きく敷いて、translate3d で数px動かす形にします。
  • 測り方は、Chrome DevToolsのPerformanceでスクロールを記録し、Paint と Composite Layers の時間を見ます。Renderingパネルの Paint flashing を有効にして、スクロール中に画面全体が緑に光るなら、全面再描画が起きている合図です。Layersパネルでレイヤー数も確認できます。

動くフィルムグレイン(reduced-motion対応つき)

.grain-anim {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: #121317;
}

.grain-anim::after {
  content: "";
  position: absolute;
  top: -50%;
  left: -50%;
  width: 200%;
  height: 200%;
  z-index: -1;
  pointer-events: none;
  opacity: 0.14;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g3'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23g3)'/%3E%3C/svg%3E");
  background-size: 180px 180px;
  animation: grain-drift 0.6s steps(4) infinite;
}

@keyframes grain-drift {
  0%   { transform: translate3d(0, 0, 0); }
  25%  { transform: translate3d(-3%, 2%, 0); }
  50%  { transform: translate3d(2%, -3%, 0); }
  75%  { transform: translate3d(-2%, -2%, 0); }
  100% { transform: translate3d(0, 0, 0); }
}

@media (prefers-reduced-motion: reduce) {
  .grain-anim::after { animation: none; }
}

要素を200%サイズにして中央寄せしているのは、動かしたときに端が覗かないようにするためです。steps(4) を使うのは、フィルムグレインが連続的に流れるのではなく、コマごとに切り替わる見え方になるからです。


読みやすさとアクセシビリティへの影響

読みやすさとアクセシビリティへの影響

ノイズは装飾ですが、可読性に実害を出せる装飾です。確認すべき点を具体的に挙げます。

文字の上に粒を重ねない。 グレイン層を本文より前面に置くと、文字のエッジに粒が乗って線が痩せます。小さい文字と細いウェイトで顕著に出ます。上のコード例がすべて z-index: -1 で背面に置いているのは、この理由です。

コントラスト比は「ノイズを乗せた後の面」で測る。 背景 #1d2b26 に文字 #f2efe8 で比率を満たしていても、黒い粒を乗せた面は実効的に別の色になります。黒い粒なら背景は暗くなるので比率は上がりますが、白い粒を screen で乗せた場合は背景が明るくなり、比率が落ちます。スクリーンショットを撮ってスポイトで実際の背景色を拾い、その値で測り直すのが確実です。WCAG 2.1の本文4.5:1は、装飾を乗せた後の状態で満たす必要があります。

pointer-events: none を必ず書く。 全面を覆う層はクリックを食います。キーボード操作は擬似要素なら影響を受けませんが、<div> でグレイン層を作った場合は aria-hidden="true" とタブ順の除外も要ります。擬似要素で作るほうが事故が少ないのは、この点でもそうです。

動くグレインは prefers-reduced-motion で止める。 画面全体がちらつく動きは、前庭障害や片頭痛の引き金になります。止めるのは必須です。

prefers-contrast: more では外す。 ハイコントラストを選んでいる利用者に対して、面を曇らせる装飾を残す理由がありません。

@media (prefers-contrast: more) {
  .grain-section::after,
  .grain-light::after,
  .grain-anim::after { display: none; }
}

本文エリアの可読性を優先する考え方そのものは、レイアウトの選び方にも通じます。1カラムで読ませる構成との組み合わせは相性が良いので、1カラムWebデザインレイアウトの実例8選|向いているケースと作り方も参考になります。


案件の種類ごとに、使うか使わないか

案件の種類ごとに、使うか使わないか

「好みで決めてください」では使えないので、言い切ります。

案件の種類 判断 理由
アート・展覧会・ギャラリー 使う 質感が内容の一部。強めの粒も成立する
飲食・クラフト・食品 使う 素材感が商品価値と直結する
アパレル・ブランドサイト 使う(弱く) 商品写真の邪魔をしない範囲で地の面だけに
採用サイト 条件つきで使う ビジュアル面は可。募集要項と応募フォームの面には入れない
コーポレートサイト 条件つきで使う KVと下層の見出し帯のみ。本文の背景には入れない
金融・証券・保険 使わない 数値と約款の可読性が優先。曇らせる余地がない
病院・医療・薬品 使わない 高齢の利用者が多く、コントラストを削る装飾は不利
EC商品一覧・商品詳細 使わない 商品の色と質感の判断を妨げる。返品の原因になる
管理画面・SaaSの画面 使わない 長時間の読み取りで疲労が増えるだけ

迷ったときの線引きは「その面で数字や条件を読ませるか」です。読ませる面には入れません。業種ごとの傾向は事例を並べると見えやすいので、下の2本が判断材料になります。


ブラウザ対応の確認のしかた

ブラウザ対応の確認のしかた

feTurbulence はSVG 1.1のフィルタープリミティブで、Chrome・Edge・Firefox・Safariのいずれも現行版で動きます。ただし「対応している」と「意図どおり出る」は別です。実務では次の3点を実機で見てください。

  1. iOS Safari — フィルター領域が大きいときに描画が落ちる、スクロールで一瞬消える、といった挙動が出ます。タイル方式にしていれば起きにくくなりますが、確認は必要です。
  2. 高DPI端末での粒径 — background-size を指定していないと、端末によって粒の細かさが変わります。2x・3xの実機で見比べます。
  3. mix-blend-mode の結果 — ブレンドの結果は色空間の扱いに差が出ることがあり、同じCSSでもブラウザ間で明るさが揃わない場合があります。ブレンドを使うなら全ブラウザで目視します。

対応状況そのものは、Can I use の「SVG filters」「CSS mix-blend-mode」「CSS Containment(content-visibility)」の各項目と、MDNのブラウザ互換性テーブルを、実装する時点で引き直してください。この種の情報は数か月で変わるので、記事に書かれたバージョン番号を鵜呑みにしないほうが安全です。

フォールバックを用意する場合、feTurbulence の有無をCSSから直接判定する方法はありません。確実なのは、ノイズを入れる前の状態(グラデーションや単色)だけで成立するデザインにしておき、グレイン層はあくまで加算として扱う設計です。出なくても壊れない形にする、これがいちばん堅い対応です。


よくある質問

よくある質問

feTurbulenceのノイズとPNGのノイズは、どちらが速いですか

描画そのものはPNGが単純です。ただし背景画像として敷く形であればSVGも一度ラスタライズすれば以降はタイルを使い回すため、スクロール中の差はほとんど出ません。差が出るのは初回描画と、SVGをフィルタープロパティとして当ててしまった場合です。制作中に粒を調整する前提ならSVG、見た目が確定していて初回描画を詰めたいならPNGを選んでください。

ノイズを入れるとコントラスト比の基準を満たせなくなりますか

乗せ方次第です。暗い面に黒い粒を重ねる場合は背景がさらに暗くなるため比率は上がります。明るい粒を乗せる場合は背景が明るくなり、文字色との比率が下がります。判定は推測せず、ノイズを適用した画面のスクリーンショットから実際の背景色を拾い、その値で測り直してください。

スクロールでガタつくときは、どこを疑えばいいですか

最初に疑うのは filter: url() の直接適用と、position: fixed のグレイン層に付けた mix-blend-mode です。この2つのどちらかであることが大半です。Chrome DevToolsのRenderingパネルで Paint flashing を有効にし、スクロール中に画面全体が再描画されていないか見ると切り分けられます。

グラデーションの縞(バンディング)対策としてだけ使えますか

使えます。むしろその用途は副作用が少なく、opacity を0.04〜0.08程度まで落としても縞は十分散ります。質感を足す目的の濃さまで上げる必要はないので、縞対策だけなら可読性への影響はほぼ無視できる範囲に収まります。

粒が細かすぎる・粗すぎるときは何を変えますか

baseFrequency と background-size の2つです。baseFrequency を上げると粒が細かくなり、下げると粗くなります。生成済みのタイルを拡大・縮小したい場合は background-size を変えますが、拡大すると粒がぼやけるので、まず baseFrequency 側で合わせてください。


まとめ

ノイズは「流行っているから入れる」装飾ではなく、広い色面の平板さとグラデーションの縞を解決する具体的な手当てです。実装は feTurbulence を小さなタイルにしてデータURIで敷き、擬似要素の背面に pointer-events: none で置く形が基本になります。filter プロパティとして当てない、フルスクリーン固定とブレンドモードを併用しない、この2つを避ければ描画はほぼ問題になりません。

そして入れる面を選ぶこと。数字と条件を読ませる面には入れません。金融・医療・EC商品ページで見送る判断は、デザインの妥協ではなく、その面が果たす役割からの帰結です。