CSSだけで移動ボタン付きカルーセルが作れる時代がくるかもしれない
2025年 8月 8日 Posted 野々瀨(フロントエンドエンジニア)
スクロールによってコンテンツを吸着させるscroll-snap-type
プロパティといったプロパティを使用することで、CSSのみで簡単なカルーセルを作ることができます。しかし、この段階ではまだまだカルーセルとしてはJavaScriptでの実装が多くあります。
最近、前後の移動が可能なナビゲーションボタンやページネーションの実装も、CSSで可能になるらしいと話題になっていましたので、筆者も試してみました。
事前準備
まず最低限として水平方向のカルーセルで、スクロール時に吸着する実装をします。次のようなHTMLを用意します。
<div class="carousel">
<div class="item">Item 1</div>
<div class="item">Item 2</div>
<div class="item">Item 3</div>
<div class="item">Item 4</div>
<div class="item">Item 5</div>
</div>
続いて次のようなCSSを用意します。
.carousel {
overflow-x: scroll;
scroll-snap-type: x mandatory;
display: flex;
.item {
scroll-snap-align: start;
scroll-snap-stop: always;
display: flex;
justify-content: center;
align-items: center;
flex: 1 0 auto;
width: 320px;
aspect-ratio: 16 / 9;
&:nth-child(1) {
background-color: #e9bbbb;
}
&:nth-child(2) {
background-color: #e9e2bb;
}
&:nth-child(3) {
background-color: #c3e9bb;
}
&:nth-child(4) {
background-color: #bbe0e9;
}
&:nth-child(5) {
background-color: #e5bbe9;
}
}
}
ポイントとしては次のような感じです。
display
プロパティをflex
にしてアイテムを横並びにするoverflow-x
プロパティをscroll
にしてX軸方向のスクロールバーを表示し許可するscroll-snap-type
プロパティを使用してスクロール時にX軸方向のみ吸着するようにするscroll-snap-align
プロパティをstart
にしてアイテムの吸着する位置を左端にするscroll-snap-stop
プロパティをalways
にしてアイテムの吸着を通り過ぎないようにする
ページネーションの実装
※ 準備段階でのHTMLとCSSはそのまま生かします。
次のようにCSSを追記します。
.carousel {
scroll-marker-group: after;
&::scroll-marker-group {
display: flex;
justify-content: center;
gap: 20px;
margin-top: 10px;
height: 16px;
}
.item {
&::scroll-marker {
border-radius: 50%;
aspect-ratio: 1 / 1;
background-color: #666;
content: "";
}
&::scroll-marker:target-current {
background-color: #ff008c;
}
}
}
.carousel要素に対してscroll-marker-group
プロパティを指定します。scroll-marker-group
プロパティは、::scroll-marker-group
疑似要素を出力するために指定します。値はどこへ出力するかを指定しますが、ここではafter
を指定して.carousel要素の次の要素として出力するようにします。
::scroll-marker-group
疑似要素は、ページネーションの本体(親)要素を出力します。
::scroll-marker
疑似要素は、ページネーションのアイテム(子)要素を出力します。
:target-current
疑似クラスは、現在のスクロール位置となる::scroll-marker
疑似要素を指します。
なお、2025年5月現在はボタンを押した際のスクロールにアニメーションは付きません。
番号の場合
番号の場合は、次のようにcounter-increment
プロパティとcounter
関数で番号として付けることができます。
.carousel {
scroll-marker-group: after;
&::scroll-marker-group {
display: flex;
justify-content: center;
gap: 20px;
margin-top: 10px;
height: 1em;
}
.item {
counter-increment: markers;
&::scroll-marker {
content: counter(markers);
color: #666;
text-decoration: none;
}
&::scroll-marker:target-current {
color: #ff008c;
font-weight: bold;
}
}
}
ナビゲーションボタンの実装
※ 準備段階でのHTMLとCSSはそのまま生かします。
次のようにCSSを追記します。
.carousel {
anchor-name: --carousel;
&::scroll-button(*) {
position: absolute;
position-anchor: --carousel;
align-self: anchor-center;
margin: 10px 0 0;
padding: 0;
border: none;
width: 20px;
height: 26px;
background-color: #333;
}
&::scroll-button(left) {
left: calc(anchor(left) + 10px);
clip-path: polygon(100% 0, 100% 100%, 0 50%);
content: "";
}
&::scroll-button(right) {
right: calc(anchor(right) + 10px);
clip-path: polygon(0 0, 100% 50%, 0 100%);
content: "";
}
&::scroll-button(*):where(:hover, :focus-visible) {
background-color: #555;
}
&::scroll-button(*):enabled {
cursor: pointer;
}
&::scroll-button(*):disabled {
background-color: #eee;
cursor: default;
}
}
.carousel要素に対して::scroll-button
疑似要素を指定します。::scroll-button
疑似要素は、上下左右の各ボタン要素を出力します。引数には各ボタンの対象を指定します。*
は左右、left
は水平軸の左、right
は水平軸の右として指定しています。
:enabled
疑似クラスで、有効状態のスタイルを指定します。
:disabled
疑似クラスで、無効状態のスタイルを指定します。
またカルーセルの上に中央揃えでのせるため、アンカーポジショニングを用いています。anchor-name
プロパティを基準となるセレクターに指定し、値はアンカー名称を付けます。position-anchor
プロパティで対象となるアンカー名称を指定します。align-self
プロパティを値としてanchor-center
を指定して上下中央揃えにします。左右のボタンに対してanchor
関数を指定し、引数は左端としてleft
、右端としてright
を指定しています。
なお、2025年5月現在はボタンを押した際のスクロールにアニメーションは付きません。
対応ブラウザー
2025年5月現在では、EdgeとChromeのバージョン135以上でのみ対応しています。
https://caniuse.com/mdn-css_properties_scroll-marker-group
https://caniuse.com/?search=scroll-button
【余談】ドラッグやスワイプのスクロールは……
説明の中にある画像でお見せした部分は、全てモバイルのタッチ操作による動作デモです。現状ドラッグやスワイプのスクロールを実装するには、JavaScriptの手を借りる必要があります。しかし残念ながら、現状はscroll-snap-type
プロパティとマウス系のイベントを一緒に動作させることはできません。
【余談】スクロールに関するイベント
スクロールに関して次のようなイベントがあります。
scroll
scrollend
scrollsnapchanging
scrollsnapchange
scrollイベント
scroll
イベントは、これまでずっとあるイベントですが、スクロールしている時に発生するイベントです。
document.querySelector('.carousel').addEventListener('scroll', () => {
console.log('scrolling');
});
scrollendイベント
scrollend
イベントは、スクロールを終えた時に発生するイベントです。
document.querySelector('.carousel').addEventListener('scrollend', () => {
console.log('scrollend');
});
2025年5月現在では、Safari以外のブラウザーで対応しています。
scrollsnapchangingイベント
scrollsnapchanging
イベントは、スクロール中に吸着する要素に達した時に発生するイベントです。scroll-snap-align
プロパティにnone
以外を指定している時に発生します。引数からEvent.snapTargetInline
プロパティで吸着する要素を取得することができます。
document.querySelector('.carousel').addEventListener('scrollsnapchanging', event => {
console.log('changing', event.snapTargetInline);
});
2025年5月現在では、EdgeとChromeのバージョン129以上でのみ対応しています。
scrollsnapchangeイベント
scrollsnapchange
イベントは、スクロールが終了した時に吸着する要素に達している時に発生します。引数からEvent.snapTargetInline
プロパティで吸着する要素を取得することができます。
document.querySelector('.carousel').addEventListener('scrollsnapchange', event => {
console.log('change', event.snapTargetInline);
});
2025年5月現在では、EdgeとChromeのバージョン129以上でのみ対応しています。
参考リンク
scroll-snap-type
: https://developer.mozilla.org/ja/docs/Web/CSS/scroll-snap-typescroll-snap-align
: https://developer.mozilla.org/ja/docs/Web/CSS/scroll-snap-alignscroll-snap-stop
: https://developer.mozilla.org/ja/docs/Web/CSS/scroll-snap-stopscroll-marker-group
: https://developer.mozilla.org/ja/docs/Web/CSS/scroll-marker-group::scroll-marker-group
: https://developer.mozilla.org/ja/docs/Web/CSS/::scroll-marker-group::scroll-marker
: https://developer.mozilla.org/ja/docs/Web/CSS/::scroll-marker:target-current
: https://developer.mozilla.org/ja/docs/Web/CSS/:target-current::scroll-button
: https://developer.mozilla.org/ja/docs/Web/CSS/::scroll-buttonanchor-name
: https://developer.mozilla.org/en-US/docs/Web/CSS/anchor-nameposition-anchor
: https://developer.mozilla.org/en-US/docs/Web/CSS/position-anchoranchor-center
: https://developer.mozilla.org/ja/docs/Web/CSS/CSS_anchor_positioning/Using#anchor-center_を使用してアンカーの中央に配置anchor
: https://developer.mozilla.org/ja/docs/Web/CSS/anchor
最後に
実際に案件で実装するにはまだ少し先の話となるでしょうが、ナビゲーションやページネーション付きのカルーセルをCSSで簡単に実装できる時代が近づいてきています。もちろん高機能なカルーセルを実装する場合はやはりJavaScriptは必要でしょう。HTMLとCSSだけで簡単になんでも実装できるようになってきていますね。