吹き出し付きレンジスライダー

つまみの位置に金額の吹き出しが追従するレンジ入力。CSSカスタムプロパティで塗りと吹き出し位置を連動させ、価格や予算選択に使えます。

#javascript#form#range

ライブデモ

使用例(お題: SaaS FlowDesk)

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

HTML
<div class="fd-screen">
  <div class="rb-card">
    <div class="fd-brand"><span class="fd-mark">◇</span> FlowDesk</div>
    <h2 class="rb-title">チーム規模を選んで料金を試算</h2>
    <p class="rb-lead">利用人数のスライダーを動かすと、<br>月額のお見積りが自動で更新されます。</p>

    <div class="rb-row">
      <div class="rb-track-wrap">
        <!-- つまみに人数の吹き出しが追従(技法の主役) -->
        <output class="rb-bubble" id="rb-bubble">10 人</output>
        <input id="rb-range" class="rb-range" type="range" min="1" max="100" step="1" value="10">
      </div>
    </div>

    <div class="rb-scale">
      <span>1人</span><span>100人</span>
    </div>

    <div class="rb-quote">
      <span class="rb-quote__label">月額お見積り(Pro / 1人 ¥1,200)</span>
      <span class="rb-quote__price" id="rb-price">¥12,000</span>
    </div>
  </div>
</div>
CSS
* { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 100vh;
  display: grid;
  place-items: center;
  font-family: "Segoe UI", "Hiragino Kaku Gothic ProN", system-ui, sans-serif;
  background:
    radial-gradient(80% 60% at 90% 0%, rgba(79, 124, 255, 0.4), transparent 60%),
    #0f1b34;
  color: #e8edf7;
}

.rb-card {
  width: min(440px, 94vw);
  padding: 24px 28px 24px;
  background: #16244a;
  border: 1px solid rgba(120, 150, 220, 0.22);
  border-radius: 16px;
  box-shadow: 0 26px 60px -24px rgba(0, 0, 0, 0.8);
}
.fd-brand {
  display: flex; align-items: center; gap: 7px; margin-bottom: 12px;
  font-size: 0.8rem; font-weight: 700; letter-spacing: 0.06em; color: #9fb6f0;
}
.fd-mark { color: #4f7cff; font-size: 1rem; }

.rb-title { margin: 0 0 8px; font-size: 1.12rem; font-weight: 800; }
.rb-lead { margin: 0 0 30px; font-size: 0.8rem; line-height: 1.6; color: #8ea0c4; }

.rb-row { padding-top: 16px; }
.rb-track-wrap { position: relative; }

/* 吹き出し。--posでつまみ位置に追従 */
.rb-bubble {
  position: absolute; top: -38px;
  left: var(--pos, 50%);
  transform: translateX(-50%);
  padding: 5px 12px;
  font-size: 0.82rem; font-weight: 700; color: #fff;
  white-space: nowrap;
  background: linear-gradient(135deg, #4f7cff, #2f5fe0);
  border-radius: 8px;
  box-shadow: 0 8px 16px -6px rgba(79, 124, 255, 0.7);
  transition: left 0.05s linear;
}
.rb-bubble::after {
  content: ""; position: absolute; left: 50%; bottom: -5px;
  transform: translateX(-50%);
  border: 6px solid transparent; border-top-color: #3060e3; border-bottom: 0;
}

/* レンジ本体 */
.rb-range {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: 8px; border-radius: 999px;
  background: linear-gradient(90deg, #4f7cff 0 var(--pos, 50%), rgba(120,150,220,0.25) var(--pos, 50%) 100%);
  outline: none; cursor: pointer;
}
.rb-range::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 22px; height: 22px; border-radius: 50%;
  background: #fff; border: 3px solid #4f7cff;
  box-shadow: 0 3px 8px rgba(0,0,0,0.4); cursor: pointer;
  transition: transform 0.12s ease;
}
.rb-range::-webkit-slider-thumb:hover { transform: scale(1.15); }
.rb-range::-moz-range-thumb {
  width: 20px; height: 20px; border-radius: 50%;
  background: #fff; border: 3px solid #4f7cff; cursor: pointer;
}

.rb-scale {
  display: flex; justify-content: space-between;
  margin-top: 11px; font-size: 0.72rem; color: #6f80a6;
}

.rb-quote {
  display: flex; align-items: center; justify-content: space-between;
  margin-top: 18px; padding: 13px 16px;
  background: rgba(79, 124, 255, 0.12);
  border: 1px solid rgba(79, 124, 255, 0.3);
  border-radius: 12px;
}
.rb-quote__label { font-size: 0.72rem; color: #9fb6f0; }
.rb-quote__price { font-size: 1.25rem; font-weight: 800; color: #fff; }

@media (prefers-reduced-motion: reduce) {
  .rb-bubble, .rb-range::-webkit-slider-thumb { transition: none; }
}
JavaScript
const range = document.getElementById("rb-range");
const bubble = document.getElementById("rb-bubble");
const price = document.getElementById("rb-price");
const PER_SEAT = 1200; // 1人あたり月額

// つまみ位置を割合(%)で計算し、吹き出し・塗り・見積りに反映
function update() {
  if (!range) return;
  const min = Number(range.min) || 0;
  const max = Number(range.max) || 100;
  const val = Number(range.value);
  const ratio = (val - min) / (max - min); // 0〜1

  // つまみ幅ぶんの補正で端でもはみ出さないように
  const pos = `calc(${ratio * 100}% + ${(0.5 - ratio) * 22}px)`;
  range.style.setProperty("--pos", `${ratio * 100}%`);

  if (bubble) {
    bubble.style.setProperty("--pos", pos);
    bubble.textContent = `${val} 人`;
  }
  // 月額見積りを再計算
  if (price) price.textContent = "¥" + (val * PER_SEAT).toLocaleString("ja-JP");
}

if (range) {
  range.addEventListener("input", update);
  update();
}

コード

HTML
<div class="stage">
  <div class="rb-card">
    <h2 class="rb-title">予算を選択</h2>

    <div class="rb-row">
      <div class="rb-track-wrap">
        <!-- 値を吹き出しで追従表示 -->
        <output class="rb-bubble" id="rb-bubble">¥50,000</output>
        <input id="rb-range" class="rb-range" type="range" min="0" max="100000" step="1000" value="50000">
      </div>
    </div>

    <div class="rb-scale">
      <span>¥0</span><span>¥100,000</span>
    </div>

    <p class="rb-note">スライダーを動かすと吹き出しが追従します</p>
  </div>
</div>
CSS
* { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 100vh;
  display: grid;
  place-items: center;
  font-family: "Segoe UI", "Hiragino Kaku Gothic ProN", system-ui, sans-serif;
  background: linear-gradient(135deg, #2b1055, #7597de);
  color: #1f2937;
}

.stage { width: 100%; padding: 22px; display: grid; place-items: center; }

.rb-card {
  width: min(420px, 94vw);
  padding: 30px 28px 24px;
  background: #fff;
  border-radius: 18px;
  box-shadow: 0 24px 56px -24px rgba(20, 10, 50, 0.6);
}

.rb-title { margin: 0 0 26px; font-size: 1.1rem; color: #1e293b; }

.rb-row { padding-top: 18px; }
.rb-track-wrap { position: relative; }

/* 吹き出し。--posでつまみ位置に追従 */
.rb-bubble {
  position: absolute;
  top: -38px;
  left: var(--pos, 50%);
  transform: translateX(-50%);
  padding: 5px 11px;
  font-size: 0.82rem;
  font-weight: 700;
  color: #fff;
  white-space: nowrap;
  background: linear-gradient(135deg, #7c3aed, #6366f1);
  border-radius: 8px;
  box-shadow: 0 8px 16px -6px rgba(124, 58, 237, 0.6);
  transition: left 0.05s linear;
}
.rb-bubble::after {
  content: "";
  position: absolute;
  left: 50%; bottom: -5px;
  transform: translateX(-50%);
  border: 6px solid transparent;
  border-top-color: #6b4ce0;
  border-bottom: 0;
}

/* レンジ本体。塗りは--posのグラデで表現 */
.rb-range {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 8px;
  border-radius: 999px;
  background: linear-gradient(90deg, #7c3aed 0 var(--pos, 50%), #e5e7eb var(--pos, 50%) 100%);
  outline: none;
  cursor: pointer;
}
.rb-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 22px; height: 22px;
  border-radius: 50%;
  background: #fff;
  border: 3px solid #7c3aed;
  box-shadow: 0 3px 8px rgba(0,0,0,0.25);
  cursor: pointer;
  transition: transform 0.12s ease;
}
.rb-range::-webkit-slider-thumb:hover { transform: scale(1.15); }
.rb-range::-moz-range-thumb {
  width: 20px; height: 20px;
  border-radius: 50%;
  background: #fff;
  border: 3px solid #7c3aed;
  cursor: pointer;
}

.rb-scale {
  display: flex; justify-content: space-between;
  margin-top: 12px;
  font-size: 0.74rem;
  color: #94a3b8;
}

.rb-note { margin: 18px 0 0; font-size: 0.78rem; color: #94a3b8; text-align: center; }

@media (prefers-reduced-motion: reduce) {
  .rb-bubble, .rb-range::-webkit-slider-thumb { transition: none; }
}
JavaScript
const range = document.getElementById("rb-range");
const bubble = document.getElementById("rb-bubble");

// 金額を ¥ 区切りで整形
const yen = (n) => "¥" + Number(n).toLocaleString("ja-JP");

// つまみ位置を割合(%)で計算し、吹き出しと塗りに反映
function update() {
  if (!range) return;
  const min = Number(range.min) || 0;
  const max = Number(range.max) || 100;
  const val = Number(range.value);
  const ratio = (val - min) / (max - min); // 0〜1

  // つまみ幅ぶんの補正で端でもはみ出さないように
  const pos = `calc(${ratio * 100}% + ${(0.5 - ratio) * 22}px)`;
  range.style.setProperty("--pos", `${ratio * 100}%`);
  if (bubble) {
    bubble.style.setProperty("--pos", pos);
    bubble.textContent = yen(val);
  }
}

if (range) {
  range.addEventListener("input", update);
  update();
}

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

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

# 追加してほしい効果
吹き出し付きレンジスライダー(フォーム & 入力)
つまみの位置に金額の吹き出しが追従するレンジ入力。CSSカスタムプロパティで塗りと吹き出し位置を連動させ、価格や予算選択に使えます。

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

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<div class="stage">
  <div class="rb-card">
    <h2 class="rb-title">予算を選択</h2>

    <div class="rb-row">
      <div class="rb-track-wrap">
        <!-- 値を吹き出しで追従表示 -->
        <output class="rb-bubble" id="rb-bubble">¥50,000</output>
        <input id="rb-range" class="rb-range" type="range" min="0" max="100000" step="1000" value="50000">
      </div>
    </div>

    <div class="rb-scale">
      <span>¥0</span><span>¥100,000</span>
    </div>

    <p class="rb-note">スライダーを動かすと吹き出しが追従します</p>
  </div>
</div>

【CSS】
* { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 100vh;
  display: grid;
  place-items: center;
  font-family: "Segoe UI", "Hiragino Kaku Gothic ProN", system-ui, sans-serif;
  background: linear-gradient(135deg, #2b1055, #7597de);
  color: #1f2937;
}

.stage { width: 100%; padding: 22px; display: grid; place-items: center; }

.rb-card {
  width: min(420px, 94vw);
  padding: 30px 28px 24px;
  background: #fff;
  border-radius: 18px;
  box-shadow: 0 24px 56px -24px rgba(20, 10, 50, 0.6);
}

.rb-title { margin: 0 0 26px; font-size: 1.1rem; color: #1e293b; }

.rb-row { padding-top: 18px; }
.rb-track-wrap { position: relative; }

/* 吹き出し。--posでつまみ位置に追従 */
.rb-bubble {
  position: absolute;
  top: -38px;
  left: var(--pos, 50%);
  transform: translateX(-50%);
  padding: 5px 11px;
  font-size: 0.82rem;
  font-weight: 700;
  color: #fff;
  white-space: nowrap;
  background: linear-gradient(135deg, #7c3aed, #6366f1);
  border-radius: 8px;
  box-shadow: 0 8px 16px -6px rgba(124, 58, 237, 0.6);
  transition: left 0.05s linear;
}
.rb-bubble::after {
  content: "";
  position: absolute;
  left: 50%; bottom: -5px;
  transform: translateX(-50%);
  border: 6px solid transparent;
  border-top-color: #6b4ce0;
  border-bottom: 0;
}

/* レンジ本体。塗りは--posのグラデで表現 */
.rb-range {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 8px;
  border-radius: 999px;
  background: linear-gradient(90deg, #7c3aed 0 var(--pos, 50%), #e5e7eb var(--pos, 50%) 100%);
  outline: none;
  cursor: pointer;
}
.rb-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 22px; height: 22px;
  border-radius: 50%;
  background: #fff;
  border: 3px solid #7c3aed;
  box-shadow: 0 3px 8px rgba(0,0,0,0.25);
  cursor: pointer;
  transition: transform 0.12s ease;
}
.rb-range::-webkit-slider-thumb:hover { transform: scale(1.15); }
.rb-range::-moz-range-thumb {
  width: 20px; height: 20px;
  border-radius: 50%;
  background: #fff;
  border: 3px solid #7c3aed;
  cursor: pointer;
}

.rb-scale {
  display: flex; justify-content: space-between;
  margin-top: 12px;
  font-size: 0.74rem;
  color: #94a3b8;
}

.rb-note { margin: 18px 0 0; font-size: 0.78rem; color: #94a3b8; text-align: center; }

@media (prefers-reduced-motion: reduce) {
  .rb-bubble, .rb-range::-webkit-slider-thumb { transition: none; }
}

【JavaScript】
const range = document.getElementById("rb-range");
const bubble = document.getElementById("rb-bubble");

// 金額を ¥ 区切りで整形
const yen = (n) => "¥" + Number(n).toLocaleString("ja-JP");

// つまみ位置を割合(%)で計算し、吹き出しと塗りに反映
function update() {
  if (!range) return;
  const min = Number(range.min) || 0;
  const max = Number(range.max) || 100;
  const val = Number(range.value);
  const ratio = (val - min) / (max - min); // 0〜1

  // つまみ幅ぶんの補正で端でもはみ出さないように
  const pos = `calc(${ratio * 100}% + ${(0.5 - ratio) * 22}px)`;
  range.style.setProperty("--pos", `${ratio * 100}%`);
  if (bubble) {
    bubble.style.setProperty("--pos", pos);
    bubble.textContent = yen(val);
  }
}

if (range) {
  range.addEventListener("input", update);
  update();
}

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

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