アーチ型スライダーとは、スライドが直線ではなく円弧(アーチ)の上に並び、中央が高く左右に向かって下がりながら傾いていくスライダーのことです。Swiperには専用のオプションがないため自前で座標を計算する必要がありますが、三角関数を使えば30行ほどのコードで実装できます。この記事では、Swiper 11で実際に動作を確認したコードと、実装時につまずきやすい「回転が逆になる」「autoplayでカクつく」という2つの問題の対処法を解説します。
アーチ型スライダーとは?通常のスライダーとの違い

通常のスライダーとの違いは、スライドが横一直線ではなく円周上に配置される点だけです。仕組み自体はシンプルで、Swiperの標準機能に「縦方向のズレ」と「回転」を足しているにすぎません。
Swiperは通常、スライドをまとめた親要素(swiper-wrapper)を横方向に動かすことでスライドを送ります。このとき個々のスライド要素のtransformは空いたままなので、そこに自分でtranslateYとrotateを書き込めば、横移動はSwiperに任せたまま円弧状の並びを作れます。制作の現場では、実績紹介やスタッフ紹介など「並べる枚数が多く、単調になりやすいコンテンツ」に動きを付けたいときに選ばれる表現です。
アーチ状に並べる計算の考え方
必要な計算は2つだけです。中央からの位置に応じた「角度」と、その角度から求める「下方向へのズレ」です。
SwiperでwatchSlidesProgress: trueを有効にすると、各スライド要素にprogressというプロパティが付きます。これは中央のスライドが0、隣が1、その隣が2……という「中央から何枚離れているか」を表す値です。1枚あたりの角度をSTEP、円の半径をRADIUSとすると、次の式で座標が決まります。
- 角度 deg = progress × STEP(1枚ずらすごとにSTEP度ぶん傾ける)
- 下方向のズレ y = RADIUS ×(1 − cos(deg))
中央(deg = 0)ではcosが1になるためy = 0で最も高くなり、左右に離れるほどcosが小さくなってyが増え、下に下がります。これがアーチの形をつくる仕組みです。RADIUSを大きくするとカーブは緩やかに、小さくすると急になります。
実装コード(HTML・CSS・JavaScript)
Swiper 11のCDN版で動作を確認したコードです。そのまま貼れば7枚のアーチ型スライダーが動きます。
HTML
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css">
<div class="swiper arc-swiper">
<div class="swiper-wrapper">
<div class="swiper-slide">1</div>
<div class="swiper-slide">2</div>
<div class="swiper-slide">3</div>
<div class="swiper-slide">4</div>
<div class="swiper-slide">5</div>
<div class="swiper-slide">6</div>
<div class="swiper-slide">7</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
CSS
スライドが下に下がるぶん、コンテナには余分な高さが必要です。回転の軸は中央(transform-origin: center center)にしておきます。
.arc-swiper {
width: 100%;
height: 400px; /* 下がるぶんを見込んで高めに取る */
padding-top: 30px;
overflow: hidden;
}
.arc-swiper .swiper-slide {
width: 200px;
height: 240px;
transform-origin: center center;
}
JavaScript
const RADIUS = 900; // 円の半径。大きいほどカーブが緩やか
const STEP = 10; // スライド1枚あたりの角度(度)
function layout(swiper) {
swiper.slides.forEach((slide) => {
const deg = -slide.progress * STEP; // 符号の反転が必要(後述)
const rad = deg * Math.PI / 180;
const y = RADIUS * (1 - Math.cos(rad));
slide.style.transform =
'translateY(' + y.toFixed(2) + 'px) rotate(' + deg.toFixed(2) + 'deg)';
});
}
const swiper = new Swiper('.arc-swiper', {
slidesPerView: 'auto',
centeredSlides: true,
spaceBetween: 16,
initialSlide: 3,
watchSlidesProgress: true, // これがないと slide.progress が取れない
on: {
progress(s) {
layout(s);
},
setTransition(s, duration) {
s.slides.forEach((slide) => {
slide.style.transitionDuration = duration + 'ms';
});
},
},
});
setTransitionの記述は省略しないでください。これを書かないと、スワイプ中は滑らかなのに、指を離した瞬間やautoplayの切り替わりでスライドの角度だけが瞬間移動します。
つまずきポイント:回転の向きが逆になる
最初に必ずぶつかるのが、スライドの傾きが逆になる問題です。原因はslide.progressの符号にあります。
実際にSwiper 11で値を出力して確認したところ、7枚のスライドで中央(4枚目)を表示している状態のprogressは、左から順に「3, 2, 1, 0, −1, −2, −3」でした。つまり中央より右にあるスライドは負の値になります。この値をそのまま角度に使うと、右側のスライドが反時計回りに傾き、アーチではなく右肩下がりの階段のような並びになってしまいます。
正しくアーチにするには、上のコードのように-slide.progressと符号を反転させます。yの計算はcosを使っているため左右対称になり、符号の影響を受けません。傾きだけが逆転する場合は、まずここを疑ってください。
autoplayでカクつく原因と3つの対処法

カクつきの原因は、JavaScriptによる座標計算とCSSトランジションという2種類の制御が混ざることにあります。
ドラッグ中はSwiperがprogressイベントを連続で発火するため、JavaScriptが毎フレーム座標を計算し直し、スライドは正確に円弧をなぞります。ところがautoplayやスワイプを離した後の慣性移動では、Swiperはトランジションを1回設定するだけで、あとはブラウザのCSSアニメーションに任せます。その間JavaScriptは呼ばれないため、スライドは「移動前の角度」から「移動後の角度」へ直線的に補間されます。本来は弧を描くはずの動きが直線でショートカットされるため、これが不自然な動きとして見えます。対処法は3つあります。
- setTransitionで同じdurationを配る:最低限これは必須です。角度が瞬間移動する現象はこれで解消します。
- STEPを小さくし、表示枚数を増やす:1枚あたりの角度が小さいほど弧と直線の差が縮まり、補間のズレが目立たなくなります。STEPを10度から6度前後に下げるだけでもかなり改善します。
- requestAnimationFrameで毎フレーム計算し直す:完全に滑らかにしたい場合の方法です。
3つ目は、CSSトランジション任せにせず、実際の移動量を毎フレーム読み取って角度を計算し直すアプローチです。スライド側のトランジションは0msにして二重制御をやめる点がポイントになります。
// スライド側のCSSトランジションは切っておく
setTransition(s) {
s.slides.forEach((slide) => {
slide.style.transitionDuration = '0ms';
});
}
// 毎フレーム、実際の移動量から角度を計算し直す
(function follow() {
const m = new DOMMatrixReadOnly(
getComputedStyle(swiper.wrapperEl).transform
);
swiper.updateSlidesProgress(m.m41); // 現在位置でprogressを再計算
layout(swiper);
requestAnimationFrame(follow);
})();
サイト制作の観点では、まず1と2で調整し、それでも気になる場合だけ3を検討するのが現実的です。3は常時ループが回り続けるため、スライドの枚数が多いページでは負荷とのバランスを確認したうえで採用してください。
よくある質問
Q. Swiperにアーチ型のオプションは用意されていますか?
A. ありません。SwiperにはCoverflow、Cube、Flipなどのエフェクトがありますが、円弧上に配置するものは含まれていないため、この記事のようにslide.progressを使って自分で座標を計算する必要があります。
Q. スライドが下に切れてしまいます。
A. コンテナの高さ不足が原因です。スライドは最大で「RADIUS ×(1 − cos(最大角度))」ピクセルぶん下に下がります。RADIUS 900・両端30度なら約121pxです。この値を見込んだ高さを指定してください。
Q. loopオプションと併用できますか?
A. 併用できます。ただしloopを有効にするとSwiperがスライドを複製するため、複製分にも同じ計算が適用されているかを実機で確認してください。表示枚数より十分に多い枚数を用意しておくと破綻しにくくなります。
Q. スマートフォンでも同じ角度でよいですか?
A. 画面幅が狭いと同じ角度でも急なカーブに見えるため、STEPとRADIUSは画面幅で切り替えることをおすすめします。breakpointsでは独自の変数を切り替えられないので、window.matchMediaで値を出し分けるのが簡単です。
まとめ
Swiperでのアーチ型スライダーは、横移動をSwiperに任せ、スライド側にtranslateYとrotateを足すだけで実装できます。押さえるべきポイントは、watchSlidesProgressを有効にすること、progressの符号を反転させること、そしてsetTransitionを省略しないことの3つです。カクつきが気になる場合は、角度を小さくするところから調整してみてください。THREE D PLUSでは、こうした動きのあるコーディングを含めたWebサイトの制作・改修を行っています。実装でお困りの際は、お気軽にご相談ください。