スポットライト マスク

radial-gradient のマスクで暗転した画像をマウス位置だけ明るく照らすスポットライト演出。インタラクティブな探索体験を演出できます。

#css#mask#hover#interactive

ライブデモ

使用例(お題: SaaS FlowDesk)

この技法を「SaaS FlowDesk」というテーマのダミーサイトで実際に使った例です。

HTML
<!-- FlowDesk:機能ツアー(スポットライトで画面を照らす) -->
<section class="fd-spot-wrap">
  <div class="fd-spot-wrap__head">
    <span class="fd-spot-wrap__tag">PRODUCT TOUR</span>
    <h2 class="fd-spot-wrap__title">使う場所だけ、<br>明るく照らす。</h2>
    <p class="fd-spot-wrap__lead">ダッシュボードにカーソルを重ねると、注目の機能だけがくっきり浮かび上がります。</p>
    <a class="fd-spot-wrap__btn" href="#">デモを予約</a>
  </div>

  <!-- 暗転した製品スクショをマウスのスポットだけ照らす -->
  <figure class="fd-spot" tabindex="0" aria-label="スポットライトで製品画面を照らす">
    <img class="fd-spot__img fd-spot__img--dim" src="https://picsum.photos/720/520?random=81&grayscale" alt="製品ダッシュボード(暗)">
    <img class="fd-spot__img fd-spot__img--lit" src="https://picsum.photos/720/520?random=81" alt="製品ダッシュボード(明)">
    <figcaption class="fd-spot__cap">MOVE TO EXPLORE</figcaption>
  </figure>
</section>
CSS
/* FlowDesk:機能ツアー スポットライトマスク */
:root {
  --navy: #0f1b34;
  --blue: #4f7cff;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  height: 400px;
  display: flex;
  align-items: center;
  gap: 30px;
  padding: 0 32px;
  font-family: "Segoe UI", "Hiragino Kaku Gothic ProN", system-ui, sans-serif;
  background: var(--navy);
  color: #fff;
  overflow: hidden;
}

.fd-spot-wrap__head { flex: 0 0 240px; }
.fd-spot-wrap__tag { font-size: 10px; letter-spacing: 0.3em; color: #8aa6ff; }
.fd-spot-wrap__title {
  margin: 10px 0 12px;
  font-size: 25px;
  line-height: 1.4;
  font-weight: 800;
}
.fd-spot-wrap__lead {
  margin: 0 0 20px;
  font-size: 13px;
  line-height: 1.85;
  color: rgba(255,255,255,0.72);
}
.fd-spot-wrap__btn {
  display: inline-block;
  padding: 11px 22px;
  border-radius: 10px;
  background: var(--blue);
  color: #fff;
  font-size: 13px;
  font-weight: 700;
  text-decoration: none;
  box-shadow: 0 10px 24px rgba(79,124,255,0.45);
  transition: transform 0.2s ease;
}
.fd-spot-wrap__btn:hover { transform: translateY(-2px); }

/* スポットライト枠 */
.fd-spot {
  --x: 50%;
  --y: 50%;
  --r: 0px; /* 初期は隠す */
  position: relative;
  flex: 1;
  height: 300px;
  margin: 0;
  border-radius: 14px;
  overflow: hidden;
  cursor: crosshair;
  outline: none;
  box-shadow: 0 18px 45px rgba(0,0,0,0.5);
}
.fd-spot__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.fd-spot__img--dim { filter: brightness(0.3) contrast(1.05); }

/* 明るい層を円形マスクで部分表示 */
.fd-spot__img--lit {
  filter: saturate(1.15) contrast(1.05);
  -webkit-mask-image: radial-gradient(circle var(--r) at var(--x) var(--y), #000 0%, #000 55%, transparent 100%);
  mask-image: radial-gradient(circle var(--r) at var(--x) var(--y), #000 0%, #000 55%, transparent 100%);
}

/* @property で半径を滑らかに補間 */
@property --r {
  syntax: "<length>";
  inherits: true;
  initial-value: 0px;
}
.fd-spot__img--lit { transition: --r 0.25s ease; }

.fd-spot__cap {
  position: absolute;
  left: 50%;
  bottom: 16px;
  transform: translateX(-50%);
  z-index: 2;
  padding: 7px 16px;
  font-size: 11px;
  letter-spacing: 0.2em;
  font-weight: 700;
  color: #fff;
  background: rgba(15,27,52,0.6);
  border-radius: 999px;
  transition: opacity 0.3s ease;
}
.fd-spot:hover .fd-spot__cap,
.fd-spot:focus-visible .fd-spot__cap { opacity: 0; }
.fd-spot:focus-visible { box-shadow: 0 0 0 3px var(--blue), 0 18px 45px rgba(0,0,0,0.5); }

@media (prefers-reduced-motion: reduce) {
  .fd-spot__img--lit { transition: none; }
  .fd-spot-wrap__btn { transition: none; }
}
JavaScript
// マウス位置と半径を CSS 変数に渡し、スポットライトを移動させる
const spot = document.querySelector(".fd-spot");

if (spot) {
  const LIT = 120; // 照らす半径(px)

  const move = (clientX, clientY) => {
    const r = spot.getBoundingClientRect();
    if (!r.width || !r.height) return;
    spot.style.setProperty("--x", `${clientX - r.left}px`);
    spot.style.setProperty("--y", `${clientY - r.top}px`);
  };

  spot.addEventListener("pointerenter", () => spot.style.setProperty("--r", `${LIT}px`));
  spot.addEventListener("pointerleave", () => spot.style.setProperty("--r", "0px"));
  spot.addEventListener("pointermove", (e) => move(e.clientX, e.clientY));

  // キーボードフォーカス時は中央を点灯
  spot.addEventListener("focus", () => {
    spot.style.setProperty("--x", "50%");
    spot.style.setProperty("--y", "50%");
    spot.style.setProperty("--r", `${LIT}px`);
  });
  spot.addEventListener("blur", () => spot.style.setProperty("--r", "0px"));
}

コード

HTML
<!-- 暗転した画像をマウスのスポットライトだけ照らす(mask) -->
<div class="stage">
  <figure class="spot" tabindex="0" aria-label="スポットライトで画像を照らす">
    <!-- 下層: 暗いベース画像 -->
    <img class="spot__img spot__img--dim" src="https://picsum.photos/id/1074/800/600?grayscale" alt="暗い画像">
    <!-- 上層: 明るいカラー画像を radial-gradient マスクで部分表示 -->
    <img class="spot__img spot__img--lit" src="https://picsum.photos/id/1074/800/600" alt="照らされた画像">
    <figcaption class="spot__cap">MOVE TO REVEAL</figcaption>
  </figure>
</div>
CSS
* { box-sizing: border-box; }
body {
  margin: 0;
  min-height: 100vh;
  display: grid;
  place-items: center;
  background: #08080c;
  font-family: "Segoe UI", system-ui, sans-serif;
}
.stage { padding: 24px; }

.spot {
  --x: 50%;
  --y: 50%;
  --r: 0px; /* スポット半径。初期は0で隠す */
  position: relative;
  width: min(64vw, 380px);
  aspect-ratio: 4 / 3;
  margin: 0;
  border-radius: 14px;
  overflow: hidden;
  cursor: crosshair;
  outline: none;
  box-shadow: 0 18px 45px -15px rgba(0, 0, 0, .8);
}
.spot__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* ベースは暗く沈める */
.spot__img--dim { filter: brightness(.32) contrast(1.05); }

/* 明るい層をマウス位置の円だけ表示するマスク */
.spot__img--lit {
  filter: saturate(1.15) contrast(1.05);
  -webkit-mask-image: radial-gradient(
    circle var(--r) at var(--x) var(--y),
    #000 0%, #000 55%, transparent 100%
  );
  mask-image: radial-gradient(
    circle var(--r) at var(--x) var(--y),
    #000 0%, #000 55%, transparent 100%
  );
}

/* @property 登録で半径を滑らかに補間(非対応環境ではスナップ) */
@property --r {
  syntax: "<length>";
  inherits: true;
  initial-value: 0px;
}
.spot__img--lit { transition: --r .25s ease; }

/* キャプション */
.spot__cap {
  position: absolute;
  left: 50%;
  bottom: 16px;
  transform: translateX(-50%);
  z-index: 2;
  padding: 7px 16px;
  font-size: 11px;
  letter-spacing: .25em;
  font-weight: 700;
  color: #fff;
  background: rgba(8, 8, 12, .55);
  border-radius: 999px;
  transition: opacity .3s ease;
}
.spot:hover .spot__cap,
.spot:focus-visible .spot__cap { opacity: 0; }

.spot:focus-visible { box-shadow: 0 0 0 3px #8ab4ff, 0 18px 45px -15px rgba(0, 0, 0, .8); }

@media (prefers-reduced-motion: reduce) {
  .spot__img--lit { transition: none; }
}
JavaScript
// マウス位置と半径を CSS 変数に渡し、スポットライトを移動させる
const spot = document.querySelector(".spot");

if (spot) {
  const LIT = 130; // 照らす半径(px)

  const move = (clientX, clientY) => {
    const r = spot.getBoundingClientRect();
    spot.style.setProperty("--x", `${clientX - r.left}px`);
    spot.style.setProperty("--y", `${clientY - r.top}px`);
  };

  // 入ったら点灯、出たら消灯
  spot.addEventListener("pointerenter", () => spot.style.setProperty("--r", `${LIT}px`));
  spot.addEventListener("pointerleave", () => spot.style.setProperty("--r", "0px"));
  spot.addEventListener("pointermove", (e) => move(e.clientX, e.clientY));

  // キーボードフォーカス時は中央を点灯
  spot.addEventListener("focus", () => {
    spot.style.setProperty("--x", "50%");
    spot.style.setProperty("--y", "50%");
    spot.style.setProperty("--r", `${LIT}px`);
  });
  spot.addEventListener("blur", () => spot.style.setProperty("--r", "0px"));
}

🤖 AIエージェント用プロンプト

このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「スポットライト マスク」の効果を追加してください。

# 追加してほしい効果
スポットライト マスク(画像エフェクト)
radial-gradient のマスクで暗転した画像をマウス位置だけ明るく照らすスポットライト演出。インタラクティブな探索体験を演出できます。

# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- 暗転した画像をマウスのスポットライトだけ照らす(mask) -->
<div class="stage">
  <figure class="spot" tabindex="0" aria-label="スポットライトで画像を照らす">
    <!-- 下層: 暗いベース画像 -->
    <img class="spot__img spot__img--dim" src="https://picsum.photos/id/1074/800/600?grayscale" alt="暗い画像">
    <!-- 上層: 明るいカラー画像を radial-gradient マスクで部分表示 -->
    <img class="spot__img spot__img--lit" src="https://picsum.photos/id/1074/800/600" alt="照らされた画像">
    <figcaption class="spot__cap">MOVE TO REVEAL</figcaption>
  </figure>
</div>

【CSS】
* { box-sizing: border-box; }
body {
  margin: 0;
  min-height: 100vh;
  display: grid;
  place-items: center;
  background: #08080c;
  font-family: "Segoe UI", system-ui, sans-serif;
}
.stage { padding: 24px; }

.spot {
  --x: 50%;
  --y: 50%;
  --r: 0px; /* スポット半径。初期は0で隠す */
  position: relative;
  width: min(64vw, 380px);
  aspect-ratio: 4 / 3;
  margin: 0;
  border-radius: 14px;
  overflow: hidden;
  cursor: crosshair;
  outline: none;
  box-shadow: 0 18px 45px -15px rgba(0, 0, 0, .8);
}
.spot__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* ベースは暗く沈める */
.spot__img--dim { filter: brightness(.32) contrast(1.05); }

/* 明るい層をマウス位置の円だけ表示するマスク */
.spot__img--lit {
  filter: saturate(1.15) contrast(1.05);
  -webkit-mask-image: radial-gradient(
    circle var(--r) at var(--x) var(--y),
    #000 0%, #000 55%, transparent 100%
  );
  mask-image: radial-gradient(
    circle var(--r) at var(--x) var(--y),
    #000 0%, #000 55%, transparent 100%
  );
}

/* @property 登録で半径を滑らかに補間(非対応環境ではスナップ) */
@property --r {
  syntax: "<length>";
  inherits: true;
  initial-value: 0px;
}
.spot__img--lit { transition: --r .25s ease; }

/* キャプション */
.spot__cap {
  position: absolute;
  left: 50%;
  bottom: 16px;
  transform: translateX(-50%);
  z-index: 2;
  padding: 7px 16px;
  font-size: 11px;
  letter-spacing: .25em;
  font-weight: 700;
  color: #fff;
  background: rgba(8, 8, 12, .55);
  border-radius: 999px;
  transition: opacity .3s ease;
}
.spot:hover .spot__cap,
.spot:focus-visible .spot__cap { opacity: 0; }

.spot:focus-visible { box-shadow: 0 0 0 3px #8ab4ff, 0 18px 45px -15px rgba(0, 0, 0, .8); }

@media (prefers-reduced-motion: reduce) {
  .spot__img--lit { transition: none; }
}

【JavaScript】
// マウス位置と半径を CSS 変数に渡し、スポットライトを移動させる
const spot = document.querySelector(".spot");

if (spot) {
  const LIT = 130; // 照らす半径(px)

  const move = (clientX, clientY) => {
    const r = spot.getBoundingClientRect();
    spot.style.setProperty("--x", `${clientX - r.left}px`);
    spot.style.setProperty("--y", `${clientY - r.top}px`);
  };

  // 入ったら点灯、出たら消灯
  spot.addEventListener("pointerenter", () => spot.style.setProperty("--r", `${LIT}px`));
  spot.addEventListener("pointerleave", () => spot.style.setProperty("--r", "0px"));
  spot.addEventListener("pointermove", (e) => move(e.clientX, e.clientY));

  // キーボードフォーカス時は中央を点灯
  spot.addEventListener("focus", () => {
    spot.style.setProperty("--x", "50%");
    spot.style.setProperty("--y", "50%");
    spot.style.setProperty("--r", `${LIT}px`);
  });
  spot.addEventListener("blur", () => spot.style.setProperty("--r", "0px"));
}

# 外部ライブラリ
なし(追加ライブラリ不要)

# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。