No.185
MICRO CSSJS

Range Slider Bubble

スライダーのつまみの真上に、今の値を示す吹き出しが付いてくる入力部品。つまみの幅を差し引いて位置を計算するので端でもずれず、押している間と、キーボードで操作した後はフォーカスが外れるまで、吹き出しが少し大きくなります。

MY COLLECTION RANDOM ↗

DEMO

CODE

HTML
<div class="demo-stage rsb-stage">
  <div class="rsb-box">
    <label class="rsb-label" for="rsb-range">明るさ</label>
    <div class="rsb-wrap" id="rsb-wrap">
      <output class="rsb-bubble" id="rsb-bubble" for="rsb-range" aria-hidden="true">60%</output>
      <input class="rsb-range" id="rsb-range" type="range" min="0" max="100" step="1" value="60">
    </div>
    <div class="rsb-scale" aria-hidden="true"><span>0</span><span>100</span></div>
  </div>
</div>
CSS
.rsb-stage { display:flex; align-items:center; justify-content:center; padding:2rem 1.5rem; background:#0B0B10; min-height:260px; }
.rsb-box { width:100%; max-width:320px; }
.rsb-label { display:block; margin-bottom:2.6rem; color:#EDEDED; font-size:.88rem; font-weight:600; }
.rsb-wrap { --p:60; --thumb:22px; position:relative; }
.rsb-range { -webkit-appearance:none; appearance:none; display:block; width:100%; height:44px; margin:0; background:transparent; cursor:pointer; }
.rsb-range:focus { outline:none; }
.rsb-range::-webkit-slider-runnable-track { height:6px; border-radius:3px; background:linear-gradient(90deg, #E91B89 0 calc(var(--thumb) / 2 + (100% - var(--thumb)) * var(--p) / 100), rgba(255,255,255,.16) 0); }
.rsb-range::-webkit-slider-thumb { -webkit-appearance:none; width:var(--thumb); height:var(--thumb); margin-top:-8px; box-sizing:border-box; border-radius:50%; background:#fff; border:3px solid #E91B89; box-shadow:0 2px 8px rgba(0,0,0,.4); transition: transform .15s ease; }
.rsb-range:focus-visible::-webkit-slider-thumb { box-shadow:0 0 0 3px #fff; }
.rsb-range:active::-webkit-slider-thumb { transform:scale(1.15); }
.rsb-range::-moz-range-track { height:6px; border-radius:3px; background:rgba(255,255,255,.16); }
.rsb-range::-moz-range-progress { height:6px; border-radius:3px; background:#E91B89; }
.rsb-range::-moz-range-thumb { width:var(--thumb); height:var(--thumb); box-sizing:border-box; border-radius:50%; background:#fff; border:3px solid #E91B89; box-shadow:0 2px 8px rgba(0,0,0,.4); transition: transform .15s ease; }
.rsb-range:focus-visible::-moz-range-thumb { box-shadow:0 0 0 3px #fff; }
.rsb-range:active::-moz-range-thumb { transform:scale(1.15); }
.rsb-bubble { position:absolute; bottom:100%; left:calc(var(--thumb) / 2 + (100% - var(--thumb)) * var(--p) / 100); margin-bottom:2px; padding:.25rem .55rem; border-radius:8px; background:#D1177A; color:#fff; font-size:.78rem; font-weight:700; font-variant-numeric:tabular-nums; white-space:nowrap; pointer-events:none;
  transform:translateX(-50%); transform-origin:50% 100%; transition: transform .2s cubic-bezier(.34,1.56,.64,1); }
.rsb-bubble::after { content:''; position:absolute; top:100%; left:50%; margin-left:-5px; border:5px solid transparent; border-top-color:#D1177A; }
.rsb-wrap.is-active .rsb-bubble { transform:translateX(-50%) scale(1.12); }
.rsb-scale { display:flex; justify-content:space-between; color:#A1A1AA; font-size:.72rem; font-variant-numeric:tabular-nums; }
@media (prefers-reduced-motion: reduce) {
  .rsb-bubble { transition-duration:.01ms; }
  .rsb-range::-webkit-slider-thumb { transition-duration:.01ms; }
  .rsb-range::-moz-range-thumb { transition-duration:.01ms; }
}
JS
(function(){
  var wrap = document.getElementById('rsb-wrap');
  var range = document.getElementById('rsb-range');
  var bubble = document.getElementById('rsb-bubble');
  if (!wrap || !range || !bubble) return;
  function update(){
    var min = Number(range.min), max = Number(range.max), v = Number(range.value);
    wrap.style.setProperty('--p', (v - min) / (max - min) * 100);
    bubble.textContent = v + '%';
    range.setAttribute('aria-valuetext', v + '%');
  }
  range.addEventListener('input', update);
  function on(){ wrap.classList.add('is-active'); }
  function off(){ wrap.classList.remove('is-active'); }
  var pressed = false;
  function release(){ if (pressed) { pressed = false; off(); } }
  range.addEventListener('pointerdown', function(){ pressed = true; on(); });
  window.addEventListener('pointerup', release);
  window.addEventListener('pointercancel', release);
  range.addEventListener('keydown', on);
  range.addEventListener('blur', off);
  update();
})();

AI PROMPT

Claude Code Cursor v0
PROMPT
スライダー(input type="range")のつまみの真上に、今の値を示す吹き出しが付いてくる入力部品を作って。label と for で名前を付け、input と吹き出し(output 要素・aria-hidden="true")を position:relative の要素で囲む。囲んだ要素にカスタムプロパティ --p(0〜100の割合)と --thumb(つまみの幅22px)を持たせ、吹き出しの left を calc(var(--thumb) / 2 + (100% − var(--thumb)) × var(--p) / 100) にして、transform: translateX(-50%) で中心を合わせる(つまみの中心は、つまみの幅の半分から「全体の幅 − つまみの幅」の範囲を動くので、単純な % だと端でずれる)。吹き出しの下には ::after の三角を付ける。スライダーは appearance:none にして、::-webkit-slider-runnable-track と ::-webkit-slider-thumb、::-moz-range-track・::-moz-range-progress・::-moz-range-thumb をそれぞれ別のルールで書く(1つのセレクタにまとめると、分からない側のブラウザでルールごと無視されるため)。WebKit 系は線の塗りを linear-gradient の区切り位置(吹き出しと同じ計算)で作る。input は高さ44pxにして押しやすくし、フォーカス時はつまみの周りに白い輪を出す(背景とのコントラスト3:1以上)。input イベントで --p と吹き出しの文字(60%)を更新し、aria-valuetext にも「60%」を入れる。押している間とキーボードで操作した後(フォーカスが外れるまで)は吹き出しを scale(1.12) に少し大きくする。スライダーの外でマウスを離しても元に戻るように、pointerdown で押している印を付け、window で受けた pointerup・pointercancel で印が付いている時だけ元の大きさに戻す。@media (prefers-reduced-motion: reduce) では吹き出しとつまみの transition を無効化する。

Acceptance criteria: works with prefers-reduced-motion (animation disabled), zero console errors, zero layout shift

AIエージェントでの実装ガイド → Claude Code でWebアニメーションを実装する

BOOKS · PR · AMAZON

📖 なかしまぁ先生のCSSアニメーション入門 📖 マイクロインタラクション ―UI/UXデザインの神が宿る細部 📖 Fundamentals of Web Animation with GSAP

※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。

学びを止めない · AMAZONサブスク

📚 Kindle Unlimited — 技術書・デザイン書が読み放題。30日無料体験 → 🎧 Audible — 移動中に耳で学ぶオーディオブック。30日無料体験 → 📦 Amazon Prime — Prime Reading・配送特典つき。30日無料体験 → 🎵 Music Unlimited — 作業用BGMに。1億曲が聴き放題・30日無料体験 →

※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。

CREATOR TOOLS · スクール(PR)

🎬 Adobe Creative Cloud — After Effects / Photoshop 業界標準ツール → 💻 SkillHacks — プログラミング・動画編集オンラインスクール → 🚀 デイトラ — Web制作スクール・実案件レベルまで習得 →

※本サイトはアフィリエイトリンクを含みます。リンク経由のご購入・お申し込みで運営者が紹介料を受け取る場合があります。

RELATED EFFECTS

↗ MORE
→ TOGGLE SWITCH → PASSWORD STRENGTH METER → ANCHOR POSITIONING TOOLTIP

Range Slider Bubble(数値の吹き出し付きスライダー)とは

スライダーを動かすと、つまみの真上に今の値を示す吹き出しが付いてくる入力部品です。音量・明るさ・予算の上限のように、細かい数値を目で確かめながら決めたい場面で使われます。

当サイトの Toggle Switch はスライドで切り替えるスイッチ、Password Strength Meter は入力に合わせて4段のメーターが伸びる強度表示、Anchor Positioning Tooltip は CSS の anchor-name で要素に紐づけて配置するツールチップです。本ページはブラウザ標準のスライダーを使ったまま、見た目と値の見せ方だけを整える別テーマです。

標準の input type="range" を使うと、矢印キーでの操作や読み上げへの値の通知はブラウザが用意してくれます。見た目を変える時も input 要素そのものは残し、aria-valuetext で単位付きの値を伝えると、誰にとっても分かりやすいスライダーになります。

よくある質問

吹き出しの位置が端でずれないのはなぜですか?

スライダーのつまみは、線の左端から右端まで動くのではなく、つまみの幅の半分だけ内側を動きます。値が0の時のつまみの中心は左端からつまみの幅の半分の位置、100の時は右端から同じだけ内側です。本実装は「つまみの幅の半分 +(全体の幅 − つまみの幅)× 割合」で中心を求めているため、0でも100でも吹き出しがつまみの真上に来ます。

つまみのCSSを1つのルールにまとめないのはなぜですか?

::-webkit-slider-thumb と ::-moz-range-thumb は、それぞれ対応するブラウザでしか分からない書き方です。CSS では、カンマで並べたセレクタの中に分からないものが1つでもあると、そのルール全体が無視されます。そのため、同じ見た目でも WebKit 系と Firefox 向けを別々のルールに分けて書いています。

コードは無料で使えますか?

はい。Motion LabのコードスニペットとAIプロンプトは、クレジット表記なしで自由に使えます。コピーしてそのままプロジェクトに利用できます。