商品追従購入バー

商品ページをスクロールすると下から現れる、サムネ・商品名・価格・数量・カート投入をまとめた追従バー。長い商品説明の途中でも、いつでも購入に進めます。ECの定番コンバージョン施策です。

#sticky#cart#ecommerce#buy

ライブデモ

使用例(お題: カフェ MOON BREW)

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

HTML
<!-- MOON BREW:豆の商品ページの追従購入バー -->
<div class="sct-frame">
  <div class="sct-scroll" id="sctScroll">
    <article class="sct-body">
      <div class="sct-hero" aria-hidden="true"><span class="sct-hero__obj"></span></div>
      <p class="sct-cat">SINGLE ORIGIN</p>
      <h1>エチオピア イルガチェフェ 200g</h1>
      <p class="sct-price-lead"><b>¥1,480</b> <s>¥1,680</s> <span class="sct-off">-12%</span></p>
      <p>ベリーのような明るい酸味と華やかな香り。浅煎りで豆本来の個性を引き出しました。少し読み進めると、下から購入バーが現れます。</p>
      <p>焙煎は深夜、注文ごとに少量ずつ。挽き方は5種から選べます。</p>
      <p>このサンプルは自動で下までスクロールし、購入バーの出現を実演します。</p>
      <p>3,000円以上で送料無料。ギフトラッピングも承ります。</p>
    </article>
  </div>

  <div class="sct-bar" id="sctBar">
    <span class="sct-thumb" aria-hidden="true"></span>
    <div class="sct-bar__info">
      <span class="sct-bar__name">イルガチェフェ 200g</span>
      <span class="sct-bar__price">¥1,480</span>
    </div>
    <div class="sct-qty">
      <button type="button" aria-label="減らす">−</button><span>1</span><button type="button" aria-label="増やす">+</button>
    </div>
    <button class="sct-buy" type="button">カートに入れる</button>
  </div>
</div>
CSS
/* MOON BREW(カフェ):商品追従購入バーの再スキン */
* { box-sizing: border-box; }
body { margin: 0; font-family: "Segoe UI", system-ui, -apple-system, sans-serif; }

.sct-frame { position: relative; width: 100%; height: 380px; overflow: hidden; background: #fbf7f0; }
.sct-scroll { height: 100%; overflow-y: auto; scrollbar-width: thin; }
.sct-body { padding: 0 28px 110px; color: #3c2f22; line-height: 1.85; max-width: 600px; margin: 0 auto; }
.sct-hero { height: 150px; margin: 18px 0; border-radius: 16px; background: linear-gradient(135deg, #c9a877, #8a5a2a); display: grid; place-items: center; }
.sct-hero__obj { width: 56px; height: 78px; border-radius: 8px 8px 12px 12px; background: rgba(40,25,10,.35); }
.sct-cat { margin: 0 0 6px; font-size: 11px; letter-spacing: .22em; font-weight: 700; color: #a67c45; }
.sct-body h1 { margin: 0 0 10px; font-size: 22px; font-weight: 800; }
.sct-price-lead { margin: 0 0 16px; font-size: 14px; color: #8a7560; }
.sct-price-lead b { font-size: 22px; font-weight: 800; color: #3c2f22; }
.sct-off { color: #c2410c; font-weight: 700; margin-left: 4px; }
.sct-body p { margin: 0 0 15px; font-size: 14px; color: #5b4d3c; }

.sct-bar { position: absolute; left: 0; right: 0; bottom: 0; z-index: 10; display: flex; align-items: center; gap: 14px; padding: 12px 18px; background: #fffdf8; border-top: 1px solid #ece0cc; box-shadow: 0 -8px 26px rgba(80,55,25,.1); transform: translateY(120%); transition: transform .4s cubic-bezier(.2,.8,.2,1); }
.sct-bar.is-shown { transform: translateY(0); }
.sct-thumb { width: 42px; height: 42px; border-radius: 9px; background: linear-gradient(135deg, #c9a877, #8a5a2a); flex: 0 0 auto; }
.sct-bar__info { display: flex; flex-direction: column; line-height: 1.3; min-width: 0; }
.sct-bar__name { font-size: 12.5px; font-weight: 600; color: #3c2f22; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 150px; }
.sct-bar__price { font-size: 15px; font-weight: 800; color: #c2410c; }
.sct-qty { display: flex; align-items: center; gap: 4px; margin-left: auto; }
.sct-qty button { width: 30px; height: 30px; border: 1px solid #ddccb2; background: #faf5ec; border-radius: 8px; cursor: pointer; font-size: 15px; color: #5b4d3c; }
.sct-qty span { min-width: 22px; text-align: center; font-size: 14px; font-weight: 700; }
.sct-buy { font: inherit; font-size: 13.5px; font-weight: 700; color: #fff; background: #5a3a1c; border: none; padding: 11px 18px; border-radius: 10px; cursor: pointer; transition: background .2s ease, transform .15s ease; }
.sct-buy:hover { background: #422a13; transform: translateY(-1px); }

@media (prefers-reduced-motion: reduce) { .sct-bar, .sct-buy { transition: none; } }
JavaScript
// (デモと同じフックを流用)スクロールで購入バー出現+数量ステッパー+自動スクロール
(() => {
  const sc = document.getElementById('sctScroll');
  const bar = document.getElementById('sctBar');
  if (!sc || !bar) return;
  const apply = () => bar.classList.toggle('is-shown', sc.scrollTop > 70);
  let ticking = false;
  sc.addEventListener('scroll', () => { if (ticking) return; ticking = true; requestAnimationFrame(() => { apply(); ticking = false; }); }, { passive: true });
  const qty = bar.querySelector('.sct-qty');
  if (qty) {
    const span = qty.querySelector('span'), btns = qty.querySelectorAll('button');
    let n = 1;
    btns[0].addEventListener('click', () => { n = Math.max(1, n - 1); span.textContent = n; });
    btns[1].addEventListener('click', () => { n = Math.min(99, n + 1); span.textContent = n; });
  }
  let auto = !matchMedia('(prefers-reduced-motion: reduce)').matches, dir = 1;
  ['wheel', 'touchstart', 'pointerdown'].forEach(ev => sc.addEventListener(ev, () => { auto = false; }, { passive: true }));
  if (auto) {
    setTimeout(function step() {
      if (!auto) return;
      const max = sc.scrollHeight - sc.clientHeight;
      sc.scrollTop += dir * 1.6;
      if (sc.scrollTop >= Math.min(max, 220)) dir = -1; else if (sc.scrollTop <= 0) dir = 1;
      requestAnimationFrame(step);
    }, 1100);
  }
})();

実装ガイド

使いどころ

ECの商品ページに。スクロールすると下から現れる、サムネ・商品名・価格・数量・カート投入をまとめた追従購入バーです。

実装時の注意点

しきい値超えで translateY による出現。数量ステッパーは増減ボタンで値を更新します。長い商品説明の途中でも購入に進める導線を確保します。

対応ブラウザ

transform トランジション・Scroll イベントは全モダンブラウザ対応。

よくある失敗

出現が早すぎると閲覧を妨げるので、ファーストビューを過ぎてから。iOS Safari の下部バーと重ならないよう safe-area を考慮。在庫切れ・バリエーション未選択時はボタンを無効化する制御を。

応用例

バリエーション(サイズ・色)選択、残数表示、お気に入り追加、価格のキャンペーン表示などに展開できます。

コード

HTML
<!-- 商品ページの追従購入バー -->
<div class="sct-frame">
  <div class="sct-scroll" id="sctScroll">
    <article class="sct-body">
      <div class="sct-hero" aria-hidden="true"><span class="sct-hero__obj"></span></div>
      <p class="sct-cat">FURNITURE</p>
      <h1>オークの一枚板テーブル</h1>
      <p class="sct-price-lead"><b>¥58,000</b> <s>¥72,000</s> <span class="sct-off">-19%</span></p>
      <p>天然オーク無垢の質感をそのままに、職人が一台ずつ仕上げました。少し読み進めると、下から購入バーが現れます。</p>
      <p>オイル仕上げで木の呼吸を妨げず、使い込むほどに味わいが増します。サイズは2種、脚は3色から選べます。</p>
      <p>このデモは自動で下までスクロールし、購入バーの出現を実演します。ホイールやタッチで触れると止まります。</p>
      <p>送料無料・30日間返品保証。組み立て不要でお届け後すぐにお使いいただけます。</p>
    </article>
  </div>

  <div class="sct-bar" id="sctBar">
    <span class="sct-thumb" aria-hidden="true"></span>
    <div class="sct-bar__info">
      <span class="sct-bar__name">オークの一枚板テーブル</span>
      <span class="sct-bar__price">¥58,000</span>
    </div>
    <div class="sct-qty">
      <button type="button" aria-label="減らす">−</button><span>1</span><button type="button" aria-label="増やす">+</button>
    </div>
    <button class="sct-buy" type="button">カートに入れる</button>
  </div>
</div>
CSS
* { box-sizing: border-box; }
body { margin: 0; font-family: "Segoe UI", system-ui, -apple-system, sans-serif; }

.sct-frame { position: relative; width: 100%; height: 380px; overflow: hidden; background: #fbfaf7; }
.sct-scroll { height: 100%; overflow-y: auto; scrollbar-width: thin; }
.sct-body { padding: 0 28px 110px; color: #2a2620; line-height: 1.85; max-width: 600px; margin: 0 auto; }

.sct-hero { height: 150px; margin: 18px 0 18px; border-radius: 16px; background: linear-gradient(135deg, #d9c4a3, #b08c5e); display: grid; place-items: center; }
.sct-hero__obj { width: 70%; height: 16px; border-radius: 6px; background: rgba(60,40,20,.35); box-shadow: 0 20px 0 -6px rgba(60,40,20,.18); }
.sct-cat { margin: 0 0 6px; font-size: 11px; letter-spacing: .22em; font-weight: 700; color: #a67c45; }
.sct-body h1 { margin: 0 0 10px; font-size: 24px; font-weight: 800; }
.sct-price-lead { margin: 0 0 16px; font-size: 14px; color: #8a7a63; }
.sct-price-lead b { font-size: 22px; font-weight: 800; color: #2a2620; }
.sct-off { color: #c2410c; font-weight: 700; margin-left: 4px; }
.sct-body p { margin: 0 0 15px; font-size: 14px; color: #5a5142; }

.sct-bar {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 10;
  display: flex; align-items: center; gap: 14px; padding: 12px 18px;
  background: #fff; border-top: 1px solid #ece4d6; box-shadow: 0 -8px 26px rgba(80,60,30,.1);
  transform: translateY(120%); transition: transform .4s cubic-bezier(.2,.8,.2,1);
}
.sct-bar.is-shown { transform: translateY(0); }
.sct-thumb { width: 42px; height: 42px; border-radius: 9px; background: linear-gradient(135deg, #d9c4a3, #b08c5e); flex: 0 0 auto; }
.sct-bar__info { display: flex; flex-direction: column; line-height: 1.3; min-width: 0; }
.sct-bar__name { font-size: 12.5px; font-weight: 600; color: #2a2620; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 150px; }
.sct-bar__price { font-size: 15px; font-weight: 800; color: #c2410c; }
.sct-qty { display: flex; align-items: center; gap: 4px; margin-left: auto; }
.sct-qty button { width: 30px; height: 30px; border: 1px solid #ddd2bf; background: #faf7f1; border-radius: 8px; cursor: pointer; font-size: 15px; color: #5a5142; }
.sct-qty span { min-width: 22px; text-align: center; font-size: 14px; font-weight: 700; }
.sct-buy { font: inherit; font-size: 13.5px; font-weight: 700; color: #fff; background: #1f2620; border: none; padding: 11px 18px; border-radius: 10px; cursor: pointer; transition: background .2s ease, transform .15s ease; }
.sct-buy:hover { background: #000; transform: translateY(-1px); }

@media (prefers-reduced-motion: reduce) { .sct-bar, .sct-buy { transition: none; } }
JavaScript
// スクロールで購入バーを出現+数量ステッパー。自動で下までスクロールして実演
(() => {
  const sc = document.getElementById('sctScroll');
  const bar = document.getElementById('sctBar');
  if (!sc || !bar) return;

  const apply = () => bar.classList.toggle('is-shown', sc.scrollTop > 70);
  let ticking = false;
  sc.addEventListener('scroll', () => { if (ticking) return; ticking = true; requestAnimationFrame(() => { apply(); ticking = false; }); }, { passive: true });

  // 数量ステッパー
  const qty = bar.querySelector('.sct-qty');
  if (qty) {
    const span = qty.querySelector('span');
    const btns = qty.querySelectorAll('button');
    let n = 1;
    btns[0].addEventListener('click', () => { n = Math.max(1, n - 1); span.textContent = n; });
    btns[1].addEventListener('click', () => { n = Math.min(99, n + 1); span.textContent = n; });
  }

  let auto = !matchMedia('(prefers-reduced-motion: reduce)').matches, dir = 1;
  ['wheel', 'touchstart', 'pointerdown'].forEach(ev => sc.addEventListener(ev, () => { auto = false; }, { passive: true }));
  if (auto) {
    setTimeout(function step() {
      if (!auto) return;
      const max = sc.scrollHeight - sc.clientHeight;
      sc.scrollTop += dir * 1.6;
      if (sc.scrollTop >= Math.min(max, 220)) dir = -1; else if (sc.scrollTop <= 0) dir = 1;
      requestAnimationFrame(step);
    }, 1100);
  }
})();

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

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

# 追加してほしい効果
商品追従購入バー(追従ウィジェット)
商品ページをスクロールすると下から現れる、サムネ・商品名・価格・数量・カート投入をまとめた追従バー。長い商品説明の途中でも、いつでも購入に進めます。ECの定番コンバージョン施策です。

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

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- 商品ページの追従購入バー -->
<div class="sct-frame">
  <div class="sct-scroll" id="sctScroll">
    <article class="sct-body">
      <div class="sct-hero" aria-hidden="true"><span class="sct-hero__obj"></span></div>
      <p class="sct-cat">FURNITURE</p>
      <h1>オークの一枚板テーブル</h1>
      <p class="sct-price-lead"><b>¥58,000</b> <s>¥72,000</s> <span class="sct-off">-19%</span></p>
      <p>天然オーク無垢の質感をそのままに、職人が一台ずつ仕上げました。少し読み進めると、下から購入バーが現れます。</p>
      <p>オイル仕上げで木の呼吸を妨げず、使い込むほどに味わいが増します。サイズは2種、脚は3色から選べます。</p>
      <p>このデモは自動で下までスクロールし、購入バーの出現を実演します。ホイールやタッチで触れると止まります。</p>
      <p>送料無料・30日間返品保証。組み立て不要でお届け後すぐにお使いいただけます。</p>
    </article>
  </div>

  <div class="sct-bar" id="sctBar">
    <span class="sct-thumb" aria-hidden="true"></span>
    <div class="sct-bar__info">
      <span class="sct-bar__name">オークの一枚板テーブル</span>
      <span class="sct-bar__price">¥58,000</span>
    </div>
    <div class="sct-qty">
      <button type="button" aria-label="減らす">−</button><span>1</span><button type="button" aria-label="増やす">+</button>
    </div>
    <button class="sct-buy" type="button">カートに入れる</button>
  </div>
</div>

【CSS】
* { box-sizing: border-box; }
body { margin: 0; font-family: "Segoe UI", system-ui, -apple-system, sans-serif; }

.sct-frame { position: relative; width: 100%; height: 380px; overflow: hidden; background: #fbfaf7; }
.sct-scroll { height: 100%; overflow-y: auto; scrollbar-width: thin; }
.sct-body { padding: 0 28px 110px; color: #2a2620; line-height: 1.85; max-width: 600px; margin: 0 auto; }

.sct-hero { height: 150px; margin: 18px 0 18px; border-radius: 16px; background: linear-gradient(135deg, #d9c4a3, #b08c5e); display: grid; place-items: center; }
.sct-hero__obj { width: 70%; height: 16px; border-radius: 6px; background: rgba(60,40,20,.35); box-shadow: 0 20px 0 -6px rgba(60,40,20,.18); }
.sct-cat { margin: 0 0 6px; font-size: 11px; letter-spacing: .22em; font-weight: 700; color: #a67c45; }
.sct-body h1 { margin: 0 0 10px; font-size: 24px; font-weight: 800; }
.sct-price-lead { margin: 0 0 16px; font-size: 14px; color: #8a7a63; }
.sct-price-lead b { font-size: 22px; font-weight: 800; color: #2a2620; }
.sct-off { color: #c2410c; font-weight: 700; margin-left: 4px; }
.sct-body p { margin: 0 0 15px; font-size: 14px; color: #5a5142; }

.sct-bar {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 10;
  display: flex; align-items: center; gap: 14px; padding: 12px 18px;
  background: #fff; border-top: 1px solid #ece4d6; box-shadow: 0 -8px 26px rgba(80,60,30,.1);
  transform: translateY(120%); transition: transform .4s cubic-bezier(.2,.8,.2,1);
}
.sct-bar.is-shown { transform: translateY(0); }
.sct-thumb { width: 42px; height: 42px; border-radius: 9px; background: linear-gradient(135deg, #d9c4a3, #b08c5e); flex: 0 0 auto; }
.sct-bar__info { display: flex; flex-direction: column; line-height: 1.3; min-width: 0; }
.sct-bar__name { font-size: 12.5px; font-weight: 600; color: #2a2620; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 150px; }
.sct-bar__price { font-size: 15px; font-weight: 800; color: #c2410c; }
.sct-qty { display: flex; align-items: center; gap: 4px; margin-left: auto; }
.sct-qty button { width: 30px; height: 30px; border: 1px solid #ddd2bf; background: #faf7f1; border-radius: 8px; cursor: pointer; font-size: 15px; color: #5a5142; }
.sct-qty span { min-width: 22px; text-align: center; font-size: 14px; font-weight: 700; }
.sct-buy { font: inherit; font-size: 13.5px; font-weight: 700; color: #fff; background: #1f2620; border: none; padding: 11px 18px; border-radius: 10px; cursor: pointer; transition: background .2s ease, transform .15s ease; }
.sct-buy:hover { background: #000; transform: translateY(-1px); }

@media (prefers-reduced-motion: reduce) { .sct-bar, .sct-buy { transition: none; } }

【JavaScript】
// スクロールで購入バーを出現+数量ステッパー。自動で下までスクロールして実演
(() => {
  const sc = document.getElementById('sctScroll');
  const bar = document.getElementById('sctBar');
  if (!sc || !bar) return;

  const apply = () => bar.classList.toggle('is-shown', sc.scrollTop > 70);
  let ticking = false;
  sc.addEventListener('scroll', () => { if (ticking) return; ticking = true; requestAnimationFrame(() => { apply(); ticking = false; }); }, { passive: true });

  // 数量ステッパー
  const qty = bar.querySelector('.sct-qty');
  if (qty) {
    const span = qty.querySelector('span');
    const btns = qty.querySelectorAll('button');
    let n = 1;
    btns[0].addEventListener('click', () => { n = Math.max(1, n - 1); span.textContent = n; });
    btns[1].addEventListener('click', () => { n = Math.min(99, n + 1); span.textContent = n; });
  }

  let auto = !matchMedia('(prefers-reduced-motion: reduce)').matches, dir = 1;
  ['wheel', 'touchstart', 'pointerdown'].forEach(ev => sc.addEventListener(ev, () => { auto = false; }, { passive: true }));
  if (auto) {
    setTimeout(function step() {
      if (!auto) return;
      const max = sc.scrollHeight - sc.clientHeight;
      sc.scrollTop += dir * 1.6;
      if (sc.scrollTop >= Math.min(max, 220)) dir = -1; else if (sc.scrollTop <= 0) dir = 1;
      requestAnimationFrame(step);
    }, 1100);
  }
})();

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

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