GODRICH

【コピペOK】CSS ネオン・グロー効果9選

ゲーム配信サイトやポートフォリオで、文字やボタンの周りが夜の看板のようにぼんやり光っているのを見たことはないでしょうか。あれは画像や動画ではなく、CSSのコードだけで光のにじみを描く演出で、css ネオンやグロー効果と呼ばれます。

自動再生 · タイルを押すとその項目へ · すべて1つのzip

9つは人気順ではなく、光る対象が画面のどこにあるかをたどる順で並べています。目立つ見出し文字(01)から始まり、押すボタン(02)・待機中のカード(03)・強調したいカード(04)・通知アイコン(05)・メニューの移動(06)を経て、注意を引き続けたいバッジ(07)・明暗テーマに合わせるバッジ(08)、最後はページに入ってきた瞬間に光るカード(09)で締めくくります。

01ネオン文字点滅

文字の周りに何重にも重なったぼんやりした光がにじみ、古い蛍光灯の管のように明るさが不規則に上下します。これはtext-shadowを5層重ねてopacityだけを@keyframesで揺らす効果で、暗くなる瞬間の深さをわざと4段階に変えて機械的な点滅に見せません。

text-shadow@keyframes flicker다중 그림자
.neon {
  color: #fff;
  text-shadow:
    0 0 4px #ff2e63,
    0 0 8px #ff2e63,
    0 0 16px rgba(255, 46, 99, .8),
    0 0 32px rgba(255, 46, 99, .6),
    0 0 48px rgba(255, 46, 99, .35);
  animation: flicker 2s linear infinite;
}
@keyframes flicker {
  0%, 14%, 20%, 34%, 50%, 64%, 80%, 94%, 100% { opacity: 1; }
  6%  { opacity: .3; }
  26% { opacity: .4; }
  56% { opacity: .25; }
  86% { opacity: .35; }
}

02ボタングローホバー

ボタンの背後にうっすらとした光が普段は控えめに漂い、マウスを乗せるかTabキーでフォーカスすると光が一気に広がって明るくなります。動いているのはbox-shadowの半径と不透明度、それにtransform: scaleだけで、ボタン自体の大きさや位置は変わりません。

box-shadow:hover · :focus-visible@keyframes
.btn {
  box-shadow: 0 0 8px rgba(0, 229, 255, .3),
              0 0 16px rgba(0, 229, 255, .12);
  transition: box-shadow .3s ease-out, transform .3s ease-out;
}
.btn:hover,
.btn:focus-visible {
  box-shadow: 0 0 12px #00e5ff,
              0 0 32px rgba(0, 229, 255, .65),
              0 0 48px rgba(0, 229, 255, .3);
  transform: scale(1.06);
}

03枠グローパルス

カードを囲む枠の光が、呼吸するように広がってはまた縮みます。box-shadowのリングを@keyframesで0%→50%→100%と往復させ、光がいちばん強まる瞬間だけレイヤーを2重から3重に増やして密度そのものを濃く見せています。

box-shadow 펄스border-color@keyframes
.card { animation: pulse 2s ease-in-out infinite; }

@keyframes pulse {
  0%, 100% {
    box-shadow: 0 0 0 rgba(57, 255, 136, 0), 0 0 8px rgba(57, 255, 136, .18);
    border-color: rgba(57, 255, 136, .35);
  }
  50% {
    box-shadow: 0 0 4px rgba(57, 255, 136, .7),
                0 0 32px rgba(57, 255, 136, .55),
                0 0 48px rgba(57, 255, 136, .22);
    border-color: rgba(57, 255, 136, .9);
  }
}

04カードスポットライトグロー

カードの上を丸い光の点がゆっくり巡回し、四隅を順番に照らしていきます。この光はradial-gradientの中心座標を--x``--yという2つの変数で動かしていて、@propertyでパーセント値として登録してあるおかげで@keyframesが滑らかに補間できます。実際にカーソルがカードの上に来ると、8行ほどのJSが同じ--x``--yにマウス座標を代入し、巡回は止まってポインタ追従に切り替わります。

@propertyradial-gradient@keyframes sweep
@property --x { syntax: '<percentage>'; inherits: false; initial-value: 50%; }
@property --y { syntax: '<percentage>'; inherits: false; initial-value: 50%; }

.card::before {
  content: '';
  position: absolute; inset: 0;
  background: radial-gradient(circle at var(--x) var(--y),
              rgba(179, 107, 255, .55), transparent 55%);
}

@keyframes sweep {
  0%   { --x: 18%; --y: 20%; }
  25%  { --x: 85%; --y: 22%; }
  50%  { --x: 78%; --y: 80%; }
  75%  { --x: 20%; --y: 76%; }
  100% { --x: 18%; --y: 20%; }
}

05アイコングローリング

アイコンを囲む2つの円が、時間差をつけて交互に膨らみながら薄れていき、通知が波紋のように外へ広がります。動くのはtransform: scaleopacityだけなので、この記事の9個の中でいちばん軽く動きます。

animation-delaybox-shadow 링scale · opacity
.ring {
  position: absolute; inset: 0;
  border-radius: 50%;
  border: 2px solid #ffb020;
  opacity: 0;
  animation: ring 2s ease-out infinite;
}
.ring--b { animation-delay: 1s; }

@keyframes ring {
  0%   { transform: scale(.7); opacity: .8; }
  70%  { transform: scale(1.7); opacity: 0; }
  100% { transform: scale(1.7); opacity: 0; }
}

06下線グロー

タブの下にある光る下線が、選んだタブの真下まで滑って移動します。2つのタブをどちらも96pxの固定幅にそろえてあるため、下線の移動距離は96px+間隔16pxの112pxと計算だけで決まり、レンダリング結果を測り直す必要がありません。デモはこの往復を@keyframesで自動再生していますが、実際のタブ切り替えでは同じanimationをクリックで発火するtransitionに置き換えて使います。

translateXbox-shadowlinear-gradient
.tab { flex: 0 0 auto; width: 96px; }

.indicator {
  position: absolute; left: 0; bottom: -8px;
  width: 96px; height: 3px;
  background: linear-gradient(90deg, transparent, #2df5c0, transparent);
  box-shadow: 0 0 12px #2df5c0, 0 0 24px rgba(45, 245, 192, .6);
}

@keyframes slide {
  0%, 40%  { transform: translateX(0); }
  60%, 90% { transform: translateX(112px); } // 96px幅 + 16pxの間隔
  100%     { transform: translateX(0); }
}

07@propertyでグロー色相回転

バッジを囲む光の色が、赤から紫まで虹のようになめらかに一周します。--hueという角度の変数を@propertyで数値として登録し、0から360まで@keyframesで回すとhsl(var(--hue) 90% 62%)の色がとぎれなく変化します。登録していない普通のカスタムプロパティではこの補間ができず、色が途中を飛ばしていきなり切り替わってしまいます。

@propertyhsl(var(--hue))무한 색상 순환
@property --hue { syntax: '<number>'; inherits: false; initial-value: 262; }

.badge {
  width: 80px; height: 80px;
  border-radius: 50%;
  box-shadow: 0 0 16px #8b5cf6, 0 0 40px rgba(139, 92, 246, .5);
}
@supports (background: hsl(var(--hue) 100% 50%)) {
  .badge {
    box-shadow: 0 0 16px hsl(var(--hue) 90% 62%),
                0 0 40px hsl(var(--hue) 90% 55% / .55);
  }
}
@keyframes cycle {
  from { --hue: 0; }
  to   { --hue: 360; }
}

08light-dark()対応グロー

同じバッジでも、明るい背景の上では光が控えめに、暗い背景の上では強く見えます。これはcolor-schemeを要素ごとに宣言したうえで光の色をlight-dark()で書いておくだけの仕組みで、メディアクエリを一行も書かずに背景に合わせて色が切り替わります。

light-dark()color-schemebox-shadow
.pane--light { color-scheme: light; }
.pane--dark  { color-scheme: dark; }

.badge {
  --glow-rest: light-dark(rgba(124, 92, 255, 0), rgba(124, 92, 255, .2));
  --glow-peak: light-dark(rgba(124, 92, 255, .7), #7c5cff);
  --glow-peak-soft: light-dark(rgba(124, 92, 255, .3), rgba(124, 92, 255, .7));
  box-shadow: 0 0 4px var(--glow-rest);
  animation: breathe 2s ease-in-out infinite;
}
@keyframes breathe {
  0%, 15%, 85%, 100% { box-shadow: 0 0 4px var(--glow-rest); }
  35%, 65% {
    box-shadow: 0 0 16px var(--glow-peak), 0 0 40px var(--glow-peak-soft);
  }
}

09スクロールでグロー強度変化

要素が画面の真ん中に近づくほど、カードの光がじわじわ濃くなっていきます。実際のページではIntersectionObserverが要素の交差率をそのまま--intensityという変数に入れ、box-shadowopacitycalc()でその数値に連動させています。--intensity@propertyで数値として登録してあるので、値が途中を飛ばさず滑らかに濃淡が変わります。

IntersectionObserver(JS)@propertybox-shadow
@property --intensity { syntax: '<number>'; inherits: false; initial-value: 0; }

.card {
  opacity: calc(.45 + var(--intensity) * .55);
  box-shadow: 0 0 calc(var(--intensity) * 12px) #3ec9ff,
              0 0 calc(var(--intensity) * 40px) rgba(62, 201, 255, .55);
  transition: box-shadow .3s linear, opacity .3s linear;
}

どこで壊れるか — light-dark() は色の値にしか使えない

08番のバッジを最初に作ったとき、box-shadowのにじみ幅までlight-dark(4px, 8px)のように書いて、背景ごとに太さも変えようとしました。結果はレンダリングそのものが崩れ、実測では強度が0.0まで落ち、フレームごとの変化がまったく検出されませんでした。light-dark()<color>型の値にしか使えず、長さ(px)のような値に混ぜると、そのbox-shadowの宣言全体が無効になって静かに消えます——コンソールにエラーは出ません。直し方は単純で、色だけをlight-dark()に渡し、にじみ幅は普通の数値のまま残せば直ります。MDNのlight-dark()のドキュメントにもこの型の制約が書かれています。ダークモード切り替え自体をアニメーションにする例はCSS ダークモード 切り替え アニメーション9選にまとめました。zipを解凍するときに聞かれるパスワードはazk2eev6で、そのまま入力すれば開きます。もう一つ覚えておきたいのは、box-shadowのアニメーションはtransformopacityのようにGPUの合成では済まず、毎フレーム描き直すペイント処理だということです。にじみ幅が大きくレイヤーが3重を超えると非力な端末でコマ落ちしやすいため、この記事のデモは全部2重以内に抑えています。

アクセシビリティ

9つともprefers-reduced-motion: reduceではanimation: noneに切り替わり、光の点滅や巡回といった「動き」だけが止まります。色やbox-shadowそのものは残るので、強く光る/弱く光るの違いだけでどこが注目点かは変わらず伝わります。04番のスポットライトと09番のスクロール連動は@propertyの初期値に固定されるため、動きを止めた瞬間に光が急に飛んだり消えたりしません。背景にある考え方はMDNのprefers-reduced-motionの解説で確認できます。

FAQ

CSSだけでネオン効果は作れますか?

作れます。9個のうち7個は純粋なCSSだけで完結していて、04番(カードスポットライト)と09番(スクロール連動)だけが実際の座標やスクロール位置を読み取るために8行前後の短いJSを添えています。それ以外のJSがなくても自動ループとして光って見えるようにしてあります。カードそのものを光らせる例はCSS カード ホバー アニメーション9選にも集めています。

なぜ一部のグローだけカクついて見えるのですか?

box-shadowfilterで作る光は毎フレーム描き直す処理なので、にじみ幅が大きくレイヤーが多いほど重くなります。transformopacityだけを動かすアニメーションより負荷が大きいことを踏まえ、レイヤー数を2つ以内に抑えておくと非力な端末でも安定しやすくなります。

@propertyはどのブラウザで使えますか?

ChromeとEdgeは以前から、SafariもSafari 16.4から対応していて、2024年にはFirefoxも追いつきBaseline 2024の「主要ブラウザで広く使える」機能になりました。MDNの@propertyのドキュメントに対応表があり、未対応の古いブラウザではこの記事の04・07・09番のデモがアニメーションなしの固定した光のまま表示されます。レイアウトが崩れる心配はありません。

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。