
縦スクロールで横スクロールを実装したい……



GSAPでスムーズな横スクロールを作りたい……
今回はこのようなお悩みをお持ちの方へ向けて
Web制作において人気の高いアニメーション効果
横スクロール・スライドアニメーション
をGSAPを使用して実装する方法をご紹介します。
- 基本的な横スクロール(GSAP ScrollTrigger)
- スナップスクロール(自動スナップ機能)
- カードスライド(カード形式の横スクロール)



特にポートフォリオサイトやランディングページでは、GSAPを使用した横スクロール・スライドアニメーションが非常に効果的です。この記事のコードをご活用いただきWeb制作の効率化に繋がれば何よりです。
なお、今回ご紹介するアニメーションはGSAP ScrollTriggerを使用して実装するので、より高度でスムーズなインタラクションを実現できます。
あわせて読みたい
横スクロール・スライドアニメーションとは
横スクロール・スライドアニメーションは、縦スクロールに連動してコンテンツが横方向にスライドするアニメーション効果です。GSAPを使用することで、よりスムーズで高性能なアニメーションを実現できます。
効果的な使用場面
適している場面
- ポートフォリオサイトの作品ギャラリー
- ランディングページの特徴紹介
- 商品カタログの横スクロール表示
- ストーリーテリングサイト
- 画像ギャラリーの横スライド
避けるべき場面
- 重要な情報の表示
- フォームやナビゲーション
- アクセシビリティを重視する場面
- モバイルでの操作性を重視する場面
- 過度に使用した場合
GSAPの準備
CDNでの読み込み
GSAPを使用するときはライブラリファイルの読み込みが必要です。ここではCDNによる読み込み方法をご紹介します。
<!-- GSAP本体 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<!-- ScrollTriggerプラグイン -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js"></script>
基本的な横スクロール
① デモ
See the Pen 基本的な横スクロール by ケケンタ (@lgshifbg-the-looper) on CodePen.
- GSAP ScrollTriggerを使用したスムーズな横スクロール
- 縦スクロールに連動した自然な動き
- 高いパフォーマンス
- すべてのブラウザで対応
② HTML
<!-- 上部コンテンツ -->
<section class="section">
<div class="container">
<h2>アイテム 1</h2>
<p>上部のコンテンツエリアです。</p>
</div>
</section>
<section class="section">
<div class="container">
<h2>アイテム 2</h2>
<p>横スクロールセクションの前のコンテンツです。</p>
</div>
</section>
<!-- 横スクロールセクション -->
<section class="horizontal-section">
<div class="horizontal-container">
<div class="horizontal-scroll">
<div class="scroll-item">
<h3>アイテム 3</h3>
<p>横スクロールの対象アイテムです。</p>
</div>
<div class="scroll-item">
<h3>アイテム 4</h3>
<p>縦スクロールに連動して横に移動します。</p>
</div>
<div class="scroll-item">
<h3>アイテム 5</h3>
<p>GSAPでスムーズなアニメーションを実現。</p>
</div>
<div class="scroll-item">
<h3>アイテム 6</h3>
<p>ユーザーの操作に自然に反応します。</p>
</div>
</div>
</div>
</section>
<!-- 下部コンテンツ -->
<section class="section">
<div class="container">
<h2>アイテム 7</h2>
<p>横スクロールセクションの後のコンテンツです。</p>
</div>
</section>
<section class="section">
<div class="container">
<h2>アイテム 8</h2>
<p>下部のコンテンツエリアです。</p>
</div>
</section>
③ CSS
/* セクション共通スタイル */
.section {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.container {
max-width: 800px;
text-align: center;
color: white;
}
.container h2 {
font-size: 2.5rem;
margin-bottom: 1rem;
}
.container p {
font-size: 1.2rem;
opacity: 0.9;
}
/* 横スクロールセクション */
.horizontal-section {
height: 100vh;
background: #f5f5f5;
overflow: hidden;
position: relative;
}
.horizontal-container {
height: 100%;
display: flex;
align-items: center;
}
.horizontal-scroll {
display: flex;
gap: 2rem;
padding: 2rem;
width: max-content;
}
.scroll-item {
min-width: 300px;
background: white;
padding: 2rem;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.scroll-item:hover {
transform: translateY(-10px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
}
.scroll-item h3 {
color: #333;
margin-bottom: 1rem;
font-size: 1.5rem;
}
.scroll-item p {
color: #666;
line-height: 1.6;
}
/* レスポンシブ対応 */
@media (max-width: 768px) {
.scroll-item {
min-width: 250px;
padding: 1.5rem;
}
.container h2 {
font-size: 2rem;
}
}
④ JavaScript
// GSAPプラグインの登録
gsap.registerPlugin(ScrollTrigger);
document.addEventListener('DOMContentLoaded', function() {
// 横スクロールの実装
const horizontalScroll = document.querySelector('.horizontal-scroll');
const scrollItems = document.querySelectorAll('.scroll-item');
// 横スクロールのアニメーション
gsap.to(horizontalScroll, {
x: () => -(horizontalScroll.scrollWidth - window.innerWidth),
ease: "none",
scrollTrigger: {
trigger: ".horizontal-section",
start: "top top",
end: () => `+=${horizontalScroll.scrollWidth - window.innerWidth}`,
scrub: 1,
pin: true,
anticipatePin: 1,
invalidateOnRefresh: true,
}
});
// 各アイテムのフェードインアニメーション
scrollItems.forEach((item, index) => {
gsap.fromTo(item,
{
opacity: 0,
y: 50,
scale: 0.8
},
{
opacity: 1,
y: 0,
scale: 1,
duration: 0.6,
delay: index * 0.1,
ease: "back.out(1.7)",
scrollTrigger: {
trigger: item,
start: "top 80%",
end: "bottom 20%",
toggleActions: "play none none reverse"
}
}
);
});
// ホバー効果の強化
scrollItems.forEach(item => {
item.addEventListener('mouseenter', function() {
gsap.to(this, {
scale: 1.05,
duration: 0.3,
ease: "power2.out"
});
});
item.addEventListener('mouseleave', function() {
gsap.to(this, {
scale: 1,
duration: 0.3,
ease: "power2.out"
});
});
});
// スクロール進捗の表示(デバッグ用)
ScrollTrigger.create({
trigger: ".horizontal-section",
start: "top top",
end: () => `+=${horizontalScroll.scrollWidth - window.innerWidth}`,
onUpdate: (self) => {
console.log("Scroll Progress:", self.progress);
}
});
});
⑤ カスタマイズ例
// スクロール速度の調整
gsap.to(horizontalScroll, {
x: () => -(horizontalScroll.scrollWidth - window.innerWidth),
ease: "none",
scrollTrigger: {
trigger: ".horizontal-section",
start: "top top",
end: () => `+=${horizontalScroll.scrollWidth - window.innerWidth}`,
scrub: 0.5, // 速度を調整(0.5 = より速い)
pin: true,
anticipatePin: 1,
}
});
// イージングの変更
gsap.to(horizontalScroll, {
x: () => -(horizontalScroll.scrollWidth - window.innerWidth),
ease: "power2.inOut", // イージングを変更
scrollTrigger: {
trigger: ".horizontal-section",
start: "top top",
end: () => `+=${horizontalScroll.scrollWidth - window.innerWidth}`,
scrub: 1,
pin: true,
}
});
// パララックス効果の追加
scrollItems.forEach((item, index) => {
gsap.to(item, {
y: -50,
ease: "none",
scrollTrigger: {
trigger: ".horizontal-section",
start: "top top",
end: () => `+=${horizontalScroll.scrollWidth - window.innerWidth}`,
scrub: 1,
pin: true,
}
});
});
スナップスクロール
① デモ
See the Pen スナップスクロール by ケケンタ (@lgshifbg-the-looper) on CodePen.
- 自動的にアイテムにスナップ
- スムーズなスナップ動作
- ユーザビリティの向上
- プロフェッショナルな印象
② HTML
<!-- 上部コンテンツ -->
<section class="section">
<div class="container">
<h2>アイテム 1</h2>
<p>上部のコンテンツエリアです。</p>
</div>
</section>
<section class="section">
<div class="container">
<h2>アイテム 2</h2>
<p>スナップスクロールセクションの前のコンテンツです。</p>
</div>
</section>
<!-- スナップスクロールセクション -->
<section class="snap-section">
<div class="snap-container">
<div class="snap-scroll">
<div class="snap-item">
<h3>スナップ 1</h3>
<p>自動的にスナップする横スクロールです。</p>
</div>
<div class="snap-item">
<h3>スナップ 2</h3>
<p>各アイテムに自然にスナップします。</p>
</div>
<div class="snap-item">
<h3>スナップ 3</h3>
<p>スムーズなスナップ動作を実現。</p>
</div>
<div class="snap-item">
<h3>スナップ 4</h3>
<p>ユーザビリティが向上します。</p>
</div>
</div>
</div>
</section>
<!-- 下部コンテンツ -->
<section class="section">
<div class="container">
<h2>アイテム 7</h2>
<p>スナップスクロールセクションの後のコンテンツです。</p>
</div>
</section>
<section class="section">
<div class="container">
<h2>アイテム 8</h2>
<p>下部のコンテンツエリアです。</p>
</div>
</section>
③ CSS
/* セクション共通スタイル */
.section {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.container {
max-width: 800px;
text-align: center;
color: white;
}
.container h2 {
font-size: 2.5rem;
margin-bottom: 1rem;
}
.container p {
font-size: 1.2rem;
opacity: 0.9;
}
/* スナップスクロールセクション */
.snap-section {
height: 100vh;
background: #f5f5f5;
overflow: hidden;
position: relative;
}
.snap-container {
height: 100%;
display: flex;
align-items: center;
}
.snap-scroll {
display: flex;
gap: 0;
padding: 2rem 0;
width: max-content;
}
.snap-item {
min-width: 100vw;
height: 60vh;
background: white;
margin: 0 1rem;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
padding: 2rem;
transition: all 0.3s ease;
}
.snap-item:hover {
transform: scale(1.02);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
}
.snap-item h3 {
color: #333;
margin-bottom: 1rem;
font-size: 2rem;
}
.snap-item p {
color: #666;
line-height: 1.6;
font-size: 1.1rem;
}
/* アクティブアイテムのスタイル */
.snap-item.active {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
transform: scale(1.05);
}
.snap-item.active h3,
.snap-item.active p {
color: white;
}
/* レスポンシブ対応 */
@media (max-width: 768px) {
.snap-item {
min-width: 90vw;
height: 50vh;
padding: 1.5rem;
}
.snap-item h3 {
font-size: 1.5rem;
}
}
④ JavaScript
// GSAPプラグインの登録
gsap.registerPlugin(ScrollTrigger);
document.addEventListener('DOMContentLoaded', function() {
const snapScroll = document.querySelector('.snap-scroll');
const snapItems = document.querySelectorAll('.snap-item');
// スナップスクロールの実装(snapオプションを利用)
gsap.to(snapScroll, {
x: () => -(snapScroll.scrollWidth - window.innerWidth),
ease: "none",
scrollTrigger: {
trigger: ".snap-section",
start: "top top",
end: () => "+=" + (snapScroll.scrollWidth - window.innerWidth),
scrub: 1,
pin: true,
anticipatePin: 1,
invalidateOnRefresh: true,
snap: {
snapTo: 1 / (snapItems.length - 1),
duration: {min: 0.2, max: 0.5},
ease: "power1.inOut"
},
onUpdate: (self) => {
// アクティブクラスの更新
const progress = self.progress;
const itemCount = snapItems.length;
const currentIndex = Math.round(progress * (itemCount - 1));
snapItems.forEach((item, index) => {
if (index === currentIndex) {
item.classList.add('active');
} else {
item.classList.remove('active');
}
});
}
}
});
// 各アイテムのフェードインアニメーション
snapItems.forEach((item, index) => {
gsap.fromTo(item,
{
opacity: 0,
y: 100,
scale: 0.8
},
{
opacity: 1,
y: 0,
scale: 1,
duration: 0.8,
delay: index * 0.2,
ease: "back.out(1.7)",
scrollTrigger: {
trigger: item,
start: "top 80%",
end: "bottom 20%",
toggleActions: "play none none reverse"
}
}
);
});
// ホバー効果の強化
snapItems.forEach(item => {
item.addEventListener('mouseenter', function() {
if (!this.classList.contains('active')) {
gsap.to(this, {
scale: 1.02,
duration: 0.3,
ease: "power2.out"
});
}
});
item.addEventListener('mouseleave', function() {
if (!this.classList.contains('active')) {
gsap.to(this, {
scale: 1,
duration: 0.3,
ease: "power2.out"
});
}
});
});
});
⑤ カスタマイズ例
// スナップ速度の調整
gsap.to(snapScroll, {
x: () => -(snapScroll.scrollWidth - window.innerWidth),
ease: "none",
scrollTrigger: {
trigger: ".snap-section",
start: "top top",
end: () => `+=${snapScroll.scrollWidth - window.innerWidth}`,
scrub: 0.5, // スナップ速度を調整
pin: true,
anticipatePin: 1,
}
});
// スナップ効果の強化
snapItems.forEach((item, index) => {
gsap.to(item, {
rotationY: 360,
ease: "none",
scrollTrigger: {
trigger: ".snap-section",
start: "top top",
end: () => `+=${snapScroll.scrollWidth - window.innerWidth}`,
scrub: 1,
pin: true,
}
});
});
// パララックス効果の追加
snapItems.forEach((item, index) => {
gsap.to(item, {
y: -50,
ease: "none",
scrollTrigger: {
trigger: ".snap-section",
start: "top top",
end: () => `+=${snapScroll.scrollWidth - window.innerWidth}`,
scrub: 1,
pin: true,
}
});
});
カードスライド
① デモ
See the Pen カードスライド by ケケンタ (@lgshifbg-the-looper) on CodePen.
- カード形式の横スクロール
- ホバー効果付き
- レスポンシブ対応
② HTML
<!-- 上部コンテンツ -->
<section class="section">
<div class="container">
<h2>カードスライド開始前のセクション</h2>
<p>ここはカードスライドの前のスペースです。</p>
</div>
</section>
<!-- カードスライドセクション -->
<section class="card-section">
<div class="card-scroll">
<div class="card-item">
<img src="https://picsum.photos/300/200?random=11" alt="カード1">
<div class="card-content">
<h3>カード 1</h3>
<p>横スクロールするカードです。</p>
</div>
</div>
<div class="card-item">
<img src="https://picsum.photos/300/200?random=12" alt="カード2">
<div class="card-content">
<h3>カード 2</h3>
<p>ホバーで拡大します。</p>
</div>
</div>
<div class="card-item">
<img src="https://picsum.photos/300/200?random=13" alt="カード3">
<div class="card-content">
<h3>カード 3</h3>
<p>レスポンシブ対応。</p>
</div>
</div>
<div class="card-item">
<img src="https://picsum.photos/300/200?random=14" alt="カード4">
<div class="card-content">
<h3>カード 4</h3>
<p>モダンなデザイン。</p>
</div>
</div>
<div class="card-item">
<img src="https://picsum.photos/300/200?random=15" alt="カード5">
<div class="card-content">
<h3>カード 5</h3>
<p>横スクロールの最後のカード。</p>
</div>
</div>
</div>
</section>
<!-- 下部コンテンツ -->
<section class="section">
<div class="container">
<h2>カードスライド終了後のセクション</h2>
<p>ここはカードスライドの後のスペースです。</p>
</div>
</section>
③ CSS
.card-section {
height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
overflow: hidden;
position: relative;
display: flex;
align-items: center;
}
.card-scroll {
display: flex;
gap: 2rem;
padding: 2rem;
width: max-content;
}
.card-item {
min-width: 300px;
background: white;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
opacity: 0;
transform: scale(0.8);
}
.card-item img {
width: 100%;
height: 200px;
object-fit: cover;
transition: transform 0.3s ease;
}
.card-item:hover {
transform: translateY(-10px) scale(1.05);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
}
.card-item:hover img {
transform: scale(1.1);
}
.card-content {
padding: 1.5rem;
width: 100%;
text-align: center;
}
.card-content h3 {
color: #333;
margin-bottom: 0.5rem;
font-size: 1.3rem;
}
.card-content p {
color: #666;
line-height: 1.6;
font-size: 0.9rem;
}
@media (max-width: 900px) {
.card-section {
height: 60vh;
align-items: flex-start;
}
.card-scroll {
padding: 1rem;
gap: 1rem;
}
.card-item {
min-width: 220px;
}
}
@media (max-width: 600px) {
.card-section {
height: 40vh;
align-items: flex-start;
}
.card-scroll {
padding: 0.5rem;
gap: 0.5rem;
}
.card-item {
min-width: 160px;
}
.card-content {
padding: 0.7rem;
}
}
④ JavaScript
// GSAPプラグインの登録
gsap.registerPlugin(ScrollTrigger);
document.addEventListener('DOMContentLoaded', function() {
const cardScroll = document.querySelector('.card-scroll');
const cardSection = document.querySelector('.card-section');
const cardItems = document.querySelectorAll('.card-item');
// 横スクロール分だけ高さを確保
function setCardSectionHeight() {
const totalScrollWidth = cardScroll.scrollWidth;
cardSection.style.height = (totalScrollWidth - window.innerWidth + window.innerHeight) + 'px';
}
setCardSectionHeight();
window.addEventListener('resize', setCardSectionHeight);
// 横スクロールの実装
const horizontalTween = gsap.to(cardScroll, {
x: () => -(cardScroll.scrollWidth - window.innerWidth),
ease: "none",
scrollTrigger: {
trigger: ".card-section",
start: "top top",
end: () => "+=" + (cardScroll.scrollWidth - window.innerWidth),
scrub: 1,
pin: true,
anticipatePin: 1,
invalidateOnRefresh: true,
}
});
// 各カードのフェードインアニメーション(横スクロール進行に連動)
cardItems.forEach((item, index) => {
gsap.to(item, {
opacity: 1,
scale: 1,
duration: 0.7,
delay: index * 0.1,
ease: "back.out(1.7)",
scrollTrigger: {
trigger: item,
containerAnimation: horizontalTween,
start: "left 90%",
end: "left 60%",
toggleActions: "play none none reverse"
}
});
});
// ホバー効果の強化
cardItems.forEach(item => {
item.addEventListener('mouseenter', function() {
gsap.to(this, {
scale: 1.08,
y: -15,
duration: 0.3,
ease: "power2.out"
});
const image = this.querySelector('img');
gsap.to(image, {
scale: 1.12,
duration: 0.3,
ease: "power2.out"
});
});
item.addEventListener('mouseleave', function() {
gsap.to(this, {
scale: 1,
y: 0,
duration: 0.3,
ease: "power2.out"
});
const image = this.querySelector('img');
gsap.to(image, {
scale: 1,
duration: 0.3,
ease: "power2.out"
});
});
});
});
まとめ
今回ご紹介した横スクロール・スライドアニメーションは、GSAPを使用することでよりスムーズで高性能なアニメーションを実現できます。
実装のコツ
- GSAP ScrollTriggerの適切な設定
- パフォーマンスを考慮した実装
- スムーズなスクロール動作
- モバイルデバイスでの操作性
- アクセシビリティの配慮
避けるべきポイント
- 過度に複雑なアニメーション
- 重い処理による遅延
- ユーザビリティを損なう実装
- アクセシビリティを無視した設計
- 過度な使用
おすすめの組み合わせ
- シンプルなサイト: 基本的な横スクロール
- モダンなサイト: スナップスクロール
- ポートフォリオサイト: カードスライド



特にポートフォリオサイトやランディングページでは、GSAPを使用した横スクロール・スライドアニメーションがユーザーエクスペリエンスを大きく左右します。この記事のコードをご活用いただき、より魅力的なWebサイトの制作に繋がれば何よりです!
あわせて読みたい
コメント