スクロール中に横スクロールしたいです。
そんな要件はありますでしょうか。
横ということはスライダーですか?
いえ、違います。
マウスなどのスクロールで下にスクロールしていってる時に、ある部分で横スクロールになってほしいそうです。

え、どうやるの。
JavaScriptライブラリの「GSAP」(ジーサップ)を使用します。
「GSAP」は派手なアニメーションを仕込みたい時に便利に使えるライブラリで、とても有名ということでした。
できること、その種類はとても沢山あります。以下はデモのリンクです。
有料のプラグインもあり、ちょっと馴染みがなかったのですが、どうやらupdateで無料になるそうです。
4/30のアップデートをもって、有料プラグインも「100%無料」になったとのこと。商用利用も無料です。
https://gsap.com/blog/3-13/
無料ならastrowaveでも使わせてもらいたいカモ。
商用利用もOKとは太っ腹ですね。
今回は横スクロールのみで使用します。
サンプルを作成しましたので以下に共有したいです。
【共有】ソースコード
作成しましたサンプルは「GSAP」を利用します。
要件は以下です。
- 下へスクロール中に横スクロールしたい
- スマホの時は横スクロールを動作させない
gsap横スクロール(サンプルページ)
https://astrowave.jp/amnesia_record/gsap_scroll.php
position:stickyを使いますので、表示が変なときはoverflow:hiddenが悪さをしていないかご注意ください。
↓1行目から、cdnのリンクで2種ライブラリを読み込みさせてもらいました。
html
<!-- gsap -->
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/ScrollTrigger.min.js"></script>
<div class="main">
<h1>「GSAP」(ジーサップ)の横スクロール</h1>
<section class="contents">
top contents
</section>
<section id="gsap_cont">
<div class="scroll-inner">
<div id="wrapper" class="l-hero-wrapper">
<div class="scroll-set">
<div class="movie_set l-hero-panel">
<h2 class="h2_ttl sp_only">Ojisan Style</h2>
<div class="txt_set">
<h2 class="h2_ttl pc_only">Ojisan Style 1</h2>
<p class="messe_ttl">「おじさんと遊ぼう」</p>
<p class="messe_sub_ttl">OLD FASHION</p>
<p class="messe_txt">サンプルムービーにてご紹介。サンプルムービーにてご紹介。サンプルムービーにてご紹介。サンプルムービーにてご紹介。</p>
</div>
<div class="poster">
<img src="https://astrowave.jp/amnesia_record/img/portrait_movie_cover.webp" alt="ojisan style">
</div>
</div>
<div class="img_set l-hero-panel">
<div class="photo01">
<img src="https://astrowave.jp/amnesia_record/img/portrait_movie_cover.webp" alt="ojisan style">
</div>
</div>
<div class="movie_set borders l-hero-panel">
<div class="txt_set">
<h2 class="h2_ttl">Ojisan Style 2</h2>
<p class="messe_ttl">「おじさんが悲しむ」</p>
<p class="messe_sub_ttl">ピュア ハート</p>
<p class="messe_txt">サンプルムービーにてご紹介。サンプルムービーにてご紹介。サンプルムービーにてご紹介。サンプルムービーにてご紹介。</p>
</div>
<div class="poster">
<img src="https://astrowave.jp/amnesia_record/img/portrait_movie_cover.webp" alt="ojisan style">
</div>
</div>
<div class="img_set l-hero-panel">
<div class="photo02">
<img src="https://astrowave.jp/amnesia_record/img/portrait_movie_cover.webp" alt="ojisan style">
</div>
</div>
<div class="movie_set borders l-hero-panel">
<div class="txt_set">
<h2 class="h2_ttl">Ojisan Style 3</h2>
<p class="messe_ttl">「おじさんが喜ぶ」</p>
<p class="messe_sub_ttl">ハッピー ハート</p>
<p class="messe_txt">サンプルムービーにてご紹介。サンプルムービーにてご紹介。サンプルムービーにてご紹介。サンプルムービーにてご紹介。</p>
</div>
<div class="poster">
<img src="https://astrowave.jp/amnesia_record/img/portrait_movie_cover.webp" alt="ojisan style">
</div>
</div>
<div class="img_set l-hero-panel">
<div class="photo03">
<img src="https://astrowave.jp/amnesia_record/img/portrait_movie_cover.webp" alt="ojisan style">
</div>
</div>
</div>
</div>
</div>
</section>
<section class="contents">
bottom contents
</section>
</div>css
<style>
/*------------------------------------
画面サイズ 768px 未満 (SP)
-------------------------------------*/
@media (width < 768px) {
.contents {
background: lightcoral;
height: 200px;
}
#gsap_cont {
background:rgb(255, 170, 0);
overflow: unset;
-ms-overflow-style: none;
position: relative;
}
#gsap_cont .poster {
width: 100%;
}
#gsap_cont .poster img {
width: 100%;
height: 100%;
object-fit: cover;
}
#gsap_cont::-webkit-scrollbar{
display: none;
}
#gsap_cont .scroll-set {
display: block;
flex-wrap: wrap;
}
#gsap_cont .l-hero-wrapper{
width: 100%;
height: auto;
margin: 0;
padding: 0;
display: block;
flex-wrap: wrap;
}
#gsap_cont .l-hero-panel {
width: 100%;
height: 100%;
}
#gsap_cont .h2_ttl{
font-weight: 600;
font-size: 15px;
letter-spacing: -0.02em;
line-height: 1.4;
color:rgb(0, 0, 0);
text-align: center;
width: 100%;
padding: 77px 0 36px;
}
#gsap_cont .movie_set {
width: auto;
display: flex;
flex-wrap: wrap;
}
#gsap_cont .movie_set.borders {
padding: 96px 0 40px;
}
#gsap_cont .movie_set.borders .h2_ttl {
display: none;
}
#gsap_cont .txt_set {
width: 100%;
padding: 0 0;
order: 2;
}
#gsap_cont .txt_set .messe_ttl {
font-weight: 500;
font-size: 23px;
letter-spacing: 0.05em;
line-height: 30px;
text-align: center;
color:rgb(0, 0, 0);
margin: 32px 0 0;
}
#gsap_cont .txt_set .messe_sub_ttl {
font-weight: 500;
font-size: 11px;
letter-spacing: 0.05em;
line-height: 1.4;
text-align: center;
color:rgb(0, 0, 0);
margin: 22px 0 0;
}
#gsap_cont .txt_set .messe_txt {
font-weight: 500;
font-size: 13px;
letter-spacing: -0.01em;
line-height: 26px;
text-align: left;
color:rgb(0, 0, 0);
width: auto;
margin: 30px 26px 0;
}
#gsap_cont .img_set {
display: block;
padding: 0;
flex: 1;
}
#gsap_cont .img_set > div{
width: auto;
}
#gsap_cont .img_set .photo01 {
padding: 55px 52px 0;
}
#gsap_cont .img_set .photo02 {
padding: 26px 52px 40px;
}
#gsap_cont .img_set .photo03 {
padding: 16px 52px 76px;
}
#gsap_cont .img_set img {
width: 100%;
height: auto;
}
}
/*------------------------------------
画面サイズ 768px 以上 (PC)
-------------------------------------*/
@media (786px <= width) {
.contents {
width: 100%;
height: 100svh;
background: lightcoral;
position: relative;
}
#gsap_cont {
background:rgb(255, 170, 0);
position: relative;
height: 250vh;
}
#gsap_cont::-webkit-scrollbar{
display: none;
}
#gsap_cont#wrapper{
overflow: hidden;
position: relative;
}
#gsap_cont .scroll-inner{
overflow: hidden;
position: sticky;
top: 0;
left: 0;
display: flex;
justify-content: flex-start;
align-items: flex-start;
}
#gsap_cont .poster img {
width: 100%;
height: 100%;
object-fit: cover;
}
#gsap_cont .scroll-set {
display: flex;
flex-wrap: nowrap;
}
#gsap_cont .l-hero-wrapper{
height: 100svh;
margin: 0;
padding: 0;
display: flex;
flex-wrap: nowrap;
flex-shrink: 0;
}
#gsap_cont .l-hero-panel {
width: 100%;
height: auto;
}
#gsap_cont .h2_ttl{
font-weight: 600;
font-size: 17px;
line-height: 1.4;
color:rgb(0, 0, 0);
text-align: center;
padding: 80px 0 0;
}
#gsap_cont .movie_set {
width: auto;
display: flex;
}
#gsap_cont .movie_set.borders {
}
#gsap_cont .txt_set {
width: auto;
padding: 0 80px;
}
#gsap_cont .txt_set .messe_ttl {
font-weight: 500;
font-size: 26px;
letter-spacing: 0.05em;
line-height: 30px;
text-align: center;
color:rgb(0, 0, 0);
margin: 30px 0 0;
}
#gsap_cont .txt_set .messe_sub_ttl {
font-weight: 500;
font-size: 12px;
letter-spacing: 0.05em;
line-height: 1.4;
text-align: center;
color:rgb(0, 0, 0);
margin: 26px 0 0;
}
#gsap_cont .txt_set .messe_txt {
font-weight: 500;
font-size: 13px;
letter-spacing: 0.05em;
line-height: 25px;
text-align: left;
color:rgb(0, 0, 0);
width: 360px;
margin: 40px auto 0;
}
#gsap_cont .img_set {
display: flex;
padding: 150px 60px;
flex: 1;
}
#gsap_cont .img_set > div{
width: auto;
}
#gsap_cont .img_set .photo02 {
padding: 0 0 0 60px;
}
#gsap_cont .img_set .photo03 {
padding: 0 50px 0 0;
}
#gsap_cont .img_set img {
width: auto;
height: 100%;
}
}
</style>JavaScript
<script>
class HorizontalScroll {
constructor() {
// #gsap_cont が存在しなければ処理を終了
if (!document.querySelector('#gsap_cont')) return;
// GSAP と ScrollTrigger プラグインを登録
gsap.registerPlugin(ScrollTrigger);
// ページの全コンテンツが読み込まれた後に初期化
window.addEventListener('load', () => {
this.scrollInit();
});
// リサイズイベントを処理
this.resizeHandler();
}
scrollInit() {
// 対象となる要素を取得
this.section = document.querySelector('#gsap_cont');
this.wrap = document.querySelector('#gsap_cont #wrapper');
this.inner = document.querySelector('#gsap_cont');
// 画面サイズがスマホかどうかを判定(768px 以下の場合はスマホ)
this.isMobile = window.matchMedia('screen and (max-width: 768px)').matches;
// スマホの場合はスクロールアニメーションを適用しない
if (!this.isMobile) {
// 初期化時にサイズを更新してスクロールアニメーションを適用
this.updateDimensions();
this.applyScrollAnimation();
}
}
resizeHandler() {
let resizeTimeout;
window.addEventListener('resize', () => {
if (resizeTimeout) clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
this.isMobile = window.matchMedia('screen and (max-width: 768px)').matches;
if (!this.isMobile) {
this.updateDimensions();
ScrollTrigger.refresh();
this.applyScrollAnimation();
} else {
ScrollTrigger.getAll().forEach(trigger => trigger.kill());
gsap.set(this.wrap, { x: 0 });
}
}, 200); // 200ms程度の遅延を設定
});
}
updateDimensions() {
// 横幅を取得して必要な移動量を計算
this.innerWidth = this.inner.offsetWidth;
this.wrapWidth = this.wrap.offsetWidth;
this.xMove = Math.min(0, this.innerWidth - this.wrapWidth); // 負の値にならないようにする
}
applyScrollAnimation() {
// 既存のアニメーションを停止
gsap.killTweensOf(this.wrap);
// 新たにスクロールアニメーションを設定
gsap.fromTo(
this.wrap,
{ x: 0 }, // 初期状態
{
x: this.xMove, // 計算した移動量
scrollTrigger: {
trigger: this.section, // トリガーとなる要素
start: 'top -300px', // アニメーション開始位置
end: () => `+=${this.section.offsetHeight * 0.5}`, // アニメーション終了位置
scrub: 1, // スクロールとアニメーションを同期
markers: false, // デバッグ用のマーカーを表示しない
invalidateOnRefresh: true, // リフレッシュ時に再計算
onLeave: () => {
document.querySelector('#gsap_cont')?.classList.add('is_moved');
},
onEnterBack: () => {
document.querySelector('#gsap_cont')?.classList.remove('is_moved');
}
},
}
);
}
}
// DOM が読み込まれたらクラスをインスタンス化
window.addEventListener('DOMContentLoaded', () => {
new HorizontalScroll();
});
</script>以上になります。
参考サイト置き場
BRISK
GSAPを使って複雑なオープニングアニメーションを作ってみよう
https://b-risk.jp/blog/2022/05/gsap/
星間旅路のメロディ
「宇宙の静けさに包まれながら、漂流する過去の音楽を捜し求め、銀河の奥底でその旋律に耳を傾ける。」
この操作盤は、ハイテク技術の塊のようです。



