カルーセル
横並びスライドを矢印・ドット・スワイプで切り替える。scroll-snap で滑らかに止まり、Pointer Events でタッチスワイプにも対応。
プレビュー
操作ドラッグ/スワイプで操作できます(PC・スマホ対応)。
仕様書
- 向く場面
- 商品写真・キービジュアルの切替
- 限られた枠で複数のお知らせを見せる
- PC とスマホでスワイプ/矢印を揃えたい
- 避ける場面
- 重要な訴求や CTA を2枚目以降だけに置く
- 2〜3枚で全部並べられる内容(グリッドで)
- 自動再生の情報バナー(見落とし・停止不可)
- 構成要素
- トラック
- スライド
- 前へ/次へボタン
- ドット
- 持っている状態
- 通常
- ホバー
- フォーカス
- 無効
- キーボード
Tab で前へ/次へとドット(button)に到達し Enter/Space で移動。カルーセル内で ←→ を押すと前後へ送る。端では対応するボタンが disabled
- 支援技術
ルートは role=region aria-roledescription=carousel。矢印は aria-label、ドットは「スライド N」+現在は aria-current。位置は aria-live=polite の非表示テキストで通知
- 動き
scrollTo smooth+scroll-snap で枠ちょうどに止まる。ドット 200ms・ボタン 150ms の遷移。スワイプ閾値 40px。reduce では即時スクロール
デザインの要点
- 位置の正本はネイティブの scroll 位置。矢印もドットも scrollTo を呼ぶだけにし、指のスワイプと同じ経路に乗せる
- タッチではポインタを捕まえず、ブラウザの慣性スクロールと snap に任せる。捕まえると指に付いてこなくなる
- ドットは 7px の見た目のまま 24px の当たり判定にする。見た目を大きくすると写真の上で主張しすぎる
- 端では矢印を disabled にして「ここで終わり」を見せる。ループさせると何枚あるか分からない
使いどころ
商品写真・キービジュアル・お知らせのように、複数の候補を同じ枠内で切り替えて見せたいときに使う。ドット・矢印・スワイプの3経路を揃えられるので、PCとスマホの両方で自然な操作感になる場面に向く。ただし2枚目以降がほとんど見られないことは計測でも知られており(いわゆる『カルーセル無視』)、重要な訴求やCTAを2枚目以降だけに置くのは避け、最初のスライドで完結させるか、そもそもグリッド表示に切り替えるべき。要素数が2〜3枚程度で全部いっぺんに見せられるなら、横スクロールのカードリストの方が一覧性が高くこの演出は不要。自動再生を足す場合は最低でも4秒は見せ、ホバー/フォーカスで止める設計が要る(今回のデモには自動再生は無い)。
仕組み
土台は overflow-x: auto と scroll-snap-type: x mandatory を掛けた .cr-track で、各スライドは flex: 0 0 100% と scroll-snap-align: start を持つ。矢印・ドットのクリックは track.scrollTo({left: slideW*current, behavior:'smooth'}) でネイティブスクロールを動かすだけで、位置管理は snap に任せている。現在位置は track の scroll イベントで scrollLeft を clientWidth で割った丸め値を current に同期させることで、ネイティブのタッチスワイプで動いた場合もドット・矢印の状態が正しく追従する。ルート要素の pointerdown/pointerup で開始点との差分(dx)を見て前後に送るのはマウス・ペン操作向けの補助で、タッチではブラウザ自身のスクロール操作に任せてポインタをキャプチャしない(キャプチャすると指の動きに対するネイティブの慣性スクロールが使えなくなる)。
実装の注意
ボタンやドットの上で pointerdown を拾ってしまうとクリックが発火しなくなる。この実装は e.target.closest('.cr-btn') と '.cr-dot' で早期リターンし、スワイプ判定に巻き込まないようにしている。これを忘れると『矢印を押しても反応しない』不具合になる。scroll-snap-type: x mandatory は要素の幅が半端だとスナップ位置がずれることがあるため、flex: 0 0 100% のように『必ずコンテナ幅ちょうど』になる指定を崩さないこと。overflow-x: auto に scrollbar-width: none と ::-webkit-scrollbar { display: none } を両方入れないと、OSやブラウザによってスクロールバーが常時表示されて見た目が崩れる。scroll イベントの同期処理を passive にしないと、スクロール中にフレーム落ちが起きやすい。
アクセシビリティ
矢印ボタンには aria-label で『前へ』『次へ』を、ドットには『スライド N』を付けており、スクリーンリーダーでも操作対象が読み上げられる。.cr-root には role="region" aria-roledescription="carousel" を付け、現在のスライド番号を aria-live="polite" の非表示テキスト(.cr-status)で告知しているので、スワイプで動いたときも位置が伝わる。選択中のドットには aria-current="true" を付け、色だけでなく属性でも現在地が分かるようにしてある。自動再生を足す場合は必ず一時停止ボタンを表示し、prefers-reduced-motion: reduce では自動送りそのものを止める(スワイプ・矢印操作は残す)。キーボードはカルーセル内のどの要素にフォーカスがあっても左右矢印キーで前後に送れる(ルートの keydown で受ける)。
調整して使う
調整できるのはスライド数・自動再生の有無・スワイプのしきい値(現在40px)。ページ全体のヒーロー用途なら高さを大きく取り、商品一覧の一部として使うなら複数枚同時表示(flex-basisを50%や33%にして一部を見切れさせる)にすると『まだ続きがある』ことが伝わりやすい。ドットは枚数が多い(8枚以上)と横に長くなりすぎるので、その場合は現在ページ数のテキスト表示(1/8)に切り替えるとよい。矢印を非表示にしてスワイプのみにするミニマルな構成も選べるが、その場合はPCユーザーのために必ずドットかテキスト表示を残し、操作手段をスワイプだけに絞らない。スワイプのしきい値40pxはトラックパッドの誤操作を拾いやすいので、対象がスマホ中心なら60〜80px程度に上げると意図しない送りが減る。
コード(コピーして使えます)
<div class="cr-root" role="region" aria-roledescription="carousel" aria-label="ギャラリー">
<div class="cr-track" id="crTrack">
<!-- <img src="..." alt="..."> を .cr-slide に置き換えて使う -->
<div class="cr-slide" style="background:linear-gradient(135deg,#2b50c7 0%,#5a7bff 100%)">
<span class="cr-label">スライド 1</span>
</div>
<div class="cr-slide" style="background:linear-gradient(135deg,#e86c4d 0%,#f5a623 100%)">
<span class="cr-label">スライド 2</span>
</div>
<div class="cr-slide" style="background:linear-gradient(135deg,#2dc793 0%,#2b50c7 100%)">
<span class="cr-label">スライド 3</span>
</div>
<div class="cr-slide" style="background:linear-gradient(135deg,#9b59b6 0%,#e86c4d 100%)">
<span class="cr-label">スライド 4</span>
</div>
</div>
<button class="cr-btn cr-prev" id="crPrev" type="button" aria-label="前へ">‹</button>
<button class="cr-btn cr-next" id="crNext" type="button" aria-label="次へ">›</button>
<div class="cr-dots" id="crDots"></div>
<p class="cr-status" id="crStatus" aria-live="polite"></p>
</div>