従来のスクロールとは違って、プレゼンテーションやポートフォリオサイトのような印象を与えたい。セクション単位で画面切り替えをしているようなアニメーションを入れたい。そんな要件ありますか。
何を言ってるんですか。
なんかむかし見たことある気がしますぞ。
スクロールすると次のセクションがアニメーションしながらブラウザ上部でゆっくりと止まります。マウスホイール、キーボード矢印キー、タッチスワイプなどのスクロール操作で発火させます。

止まる?そんなことできるんですか。
検索した感じでは「GSAP」(ジーサップ)を使う方が多いようです。
「GSAP」は派手なアニメーションを仕込みたい時に便利に使えるライブラリ。
有料のプラグインありの少し私には馴染みがなかったのですが、どうやらupdateで無料になったそうです。
以下はデモのリンクです。
4/30のアップデートをもって、有料プラグインも「100%無料」になったとのこと。商用利用も無料です。
https://gsap.com/blog/3-13/
商用利用もOKとは太っ腹ですね。
【共有】スクロールアニメーションのソースコード
サンプルを作成してみましたので共有したいです。
要件は以下です。
- GSAPのScrollTriggerを使ったスクロールアニメーションを使う
- スマホも同じようにする
- おまけでswiperを入れる
【GSAP】ScrollTriggerサンプル
https://astrowave.jp/amnesia_record/scrolltrigger_gasp.php
健忘録リスト
以下にコードを貼り付けます。
html
<!doctype html>
<html lang="ja">
<head>
<title>【GSAP】ScrollTriggerアニメーション</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- Swiper CSS -->
<link rel="stylesheet" href="https://unpkg.com/swiper/swiper-bundle.min.css">
<!-- GSAP CDN -->
<script src="https://unpkg.com/gsap@3.12.2/dist/gsap.min.js"></script>
<script src="https://unpkg.com/gsap@3.12.2/dist/ScrollToPlugin.min.js"></script>
<!-- Swiper JS -->
<script src="https://unpkg.com/swiper/swiper-bundle.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Helvetica Neue', Arial, sans-serif;
background: #000;
height: 100vh;
}
.section {
width: 100%;
height: 100vh;
position: relative;
overflow: hidden;
margin: 0;
padding: 0;
}
.section-content {
position: absolute;
top: 20px;
left: 20px;
text-align: left;
color: white;
z-index: 10;
}
.section-title {
font-size: 3rem;
font-weight: bold;
margin-bottom: 1rem;
}
.section-subtitle {
font-size: 1.2rem;
opacity: 0.8;
}
#footer {
width: 100%;
background: linear-gradient(45deg, #1abc9c, #16a085);
color: white;
padding: 100px 0 50px;
text-align: center;
display: none;
}
.footer-content {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
.footer-title {
font-size: 2.5rem;
font-weight: bold;
margin-bottom: 2rem;
}
.footer-subtitle {
font-size: 1.2rem;
opacity: 0.8;
margin-bottom: 3rem;
}
.footer-links {
border-top: 1px solid rgba(255, 255, 255, 0.2);
padding-top: 50px;
margin-top: 50px;
}
.footer-links ul {
list-style: none;
display: flex;
justify-content: center;
gap: 30px;
flex-wrap: wrap;
margin-bottom: 30px;
}
.footer-links a {
color: white;
text-decoration: none;
font-size: 14px;
opacity: 0.8;
transition: opacity 0.3s ease;
}
.footer-links a:hover {
opacity: 1;
}
.footer-copyright {
font-size: 12px;
opacity: 0.6;
margin-top: 20px;
}
.section-title {
font-size: 2rem;
}
.section-subtitle {
font-size: 1rem;
}
.footer-title {
font-size: 1.8rem;
}
.footer-subtitle {
font-size: 1rem;
}
.swiper-slide {
font-size: 1.5rem;
}
.swiper {
width: 100%;
height: 100%;
}
.main-swiper,
.colla-swiper {
height: 100vh;
margin-top: 0;
}
.swiper-pagination {
position: absolute;
bottom: 20px;
left: auto !important;
right: 20px;
text-align: right;
}
.swiper-pagination-bullet {
background: rgba(255, 255, 255, 0.5);
opacity: 1;
}
.swiper-pagination-bullet-active {
background: #fff;
}
.swiper-slide picture,
.swiper-slide picture img {
display: block;
width: 100%;
height: 100%;
overflow: hidden;
}
.swiper-slide picture img {
object-fit: cover;
}
.section picture img {
transition: transform 8s ease;
transform: scale(1);
transform-origin: center center;
}
.swiper-slide-active picture img,
body picture img {
transform: scale(1.05);
}
.swiper-pagination{
left: auto !important;
right: 20px;
text-align: right;
}
.slide-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 10;
color: #fff;
cursor: pointer;
font-size: 2.27vw;
font-weight: normal;
text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
text-align: center;
}
.slide-text h2 {
margin: -8px 0 20px -2px;
font-size: 2.27vw;
}
.slide-text h3 {
font-size: 2.27vw;
}
.slide-text .sliderLink {
font-size: 14px;
position: relative;
display: inline-block;
}
.slide-text .sliderLink::after {
content: '';
width: 100%;
height: 1px;
background: #fff;
position: absolute;
bottom: 0;
display: block;
transition-duration: 0.5s;
}
.slide-text:hover .sliderLink::after {
width: 0;
transition-duration: 0.5s;
}
</style>
</head>
<body>
<!-- セクション1: メインスライダー -->
<section class="section section1">
<div class="section-content">
<h2 class="section-title">SECTION 1</h2>
<p class="section-subtitle">4枚のスライド</p>
</div>
<div class="swiper main-swiper">
<ul class="swiper-wrapper">
<li class="swiper-slide">
<picture>
<source media="(min-width: 1000px)" srcset="/amnesia_record/img/image_fx_1.jpg" alt="YouTube Test 1">
<img src="/amnesia_record/img/image_fx_1.jpg" alt="YouTube Test 1">
</picture>
<div class="slide-text">
<h2>SECTION 1</h2>
<h3>Slide 1</h3>
<div class="sliderLink">VIEW MORE</div>
</div>
</li>
<li class="swiper-slide">
<picture>
<source media="(min-width: 1000px)" srcset="/amnesia_record/img/image_fx_2.jpg" alt="YouTube Test 2">
<img src="/amnesia_record/img/image_fx_2.jpg" alt="YouTube Test 2">
</picture>
<div class="slide-text">
<h2>SECTION 1</h2>
<h3>Slide 2</h3>
<div class="sliderLink">VIEW MORE</div>
</div>
</li>
<li class="swiper-slide">
<picture>
<source media="(min-width: 1000px)" srcset="/amnesia_record/img/image_fx_3.jpg" alt="YouTube Test 3">
<img src="/amnesia_record/img/image_fx_3.jpg" alt="YouTube Test 3">
</picture>
<div class="slide-text">
<h2>SECTION 1</h2>
<h3>Slide 3</h3>
<div class="sliderLink">VIEW MORE</div>
</div>
</li>
<li class="swiper-slide">
<picture>
<source media="(min-width: 1000px)" srcset="/amnesia_record/img/image_fx_4.jpg" alt="YouTube Test 4">
<img src="/amnesia_record/img/image_fx_4.jpg" alt="YouTube Test 4">
</picture>
<div class="slide-text">
<h2>SECTION 1</h2>
<h3>Slide 4</h3>
<div class="sliderLink">VIEW MORE</div>
</div>
</li>
</ul>
<div class="swiper-pagination"></div>
</div>
</section>
<!-- セクション2: コラボスライダー -->
<section class="section section2">
<div class="section-content">
<h2 class="section-title">SECTION 2</h2>
<p class="section-subtitle">2枚のスライド</p>
</div>
<div class="swiper colla-swiper">
<ul class="swiper-wrapper">
<li class="swiper-slide">
<picture>
<source media="(min-width: 1000px)" srcset="/amnesia_record/img/image_fx_5.jpg" alt="Sort 1">
<img src="/amnesia_record/img/image_fx_5.jpg" alt="Sort 1">
</picture>
<div class="slide-text">
<h2>SECTION 2</h2>
<h3>Slide 1</h3>
<div class="sliderLink">VIEW MORE</div>
</div>
</li>
<li class="swiper-slide">
<picture>
<source media="(min-width: 1000px)" srcset="/amnesia_record/img/image_fx_6.jpg" alt="Sort 2">
<img src="/amnesia_record/img/image_fx_6.jpg" alt="Sort 2">
</picture>
<div class="slide-text">
<h2>SECTION 2</h2>
<h3>Slide 2</h3>
<div class="sliderLink">VIEW MORE</div>
</div>
</li>
</ul>
<div class="swiper-pagination"></div>
</div>
</section>
<!-- フッター(通常の高さ) -->
<div id="footer">
<div class="footer-content">
<h2 class="footer-title">FOOTER</h2>
<p class="footer-subtitle">通常の高さのフッター</p>
<div class="footer-links">
<ul>
<li><a href="#">COMPANY</a></li>
<li><a href="#">RECRUIT</a></li>
<li><a href="#">PRIVACY POLICY</a></li>
<li><a href="#">ABOUT THIS SITE</a></li>
<li><a href="#">CAUTION</a></li>
</ul>
<div class="footer-copyright">
© 2025 SAMPLE COMPANY. ALL RIGHTS RESERVED.
</div>
</div>
</div>
</div>
<script>
// スタイルの実装
document.addEventListener('DOMContentLoaded', function() {
//console.log('=== 初期化開始 ===');
// GSAPプラグインの初期化
gsap.registerPlugin(ScrollToPlugin);
//console.log('GSAP ScrollToPlugin初期化完了');
// Swiperスライダーの初期化(方式で個別初期化)
let mainSwiper = new Swiper(".main-swiper", {
effect: "fade",
loop: true,
speed: 600,
simulateTouch: true,
allowTouchMove: true,
fadeEffect: { crossFade: true },
autoplay: {
delay: 5000,
disableOnInteraction: false
},
pagination: {
el: ".main-swiper .swiper-pagination",
clickable: true,
type: "bullets"
},
on: {
init: function() {
console.log('Main Swiper初期化完了 - スライド数:', this.slides.length);
}
}
});
let collaSwiper = new Swiper(".colla-swiper", {
effect: "fade",
loop: true,
speed: 600,
simulateTouch: true,
allowTouchMove: true,
fadeEffect: { crossFade: true },
autoplay: {
delay: 5000,
disableOnInteraction: false
},
pagination: {
el: ".colla-swiper .swiper-pagination",
clickable: true,
type: "bullets"
},
on: {
init: function() {
console.log('Colla Swiper初期化完了 - スライド数:', this.slides.length);
}
}
});
// スタイルのスクロール制御
const sections = document.querySelectorAll('.section');
let currentIndex = 0;
let isScrolling = false;
let isFooterVisible = false;
let swiperCooldown = false;
// 初期位置を最上部に
window.scrollTo(0, 0);
function scrollToSection(index) {
isScrolling = true;
currentIndex = index;
gsap.to(window, {
scrollTo: { y: sections[index].offsetTop, autoKill: false },
duration: 1.2, // より滑らかに
ease: "power2.out", // イージングを調整
onComplete: function() {
isScrolling = false;
}
});
}
function showFooter() {
isFooterVisible = true;
isScrolling = true; // スクロール中フラグを設定
const footer = document.getElementById('footer');
footer.style.display = 'block';
// セクションと同じスクロールアニメーション
gsap.to(window, {
scrollTo: { y: footer.offsetTop, autoKill: false },
duration: 1.2,
ease: "power2.out",
onComplete: function() {
isScrolling = false;
}
});
}
function hideFooter() {
isFooterVisible = false;
const footer = document.getElementById('footer');
// セクションと同じスクロールアニメーション
gsap.to(window, {
scrollTo: { y: sections[sections.length - 1].offsetTop, autoKill: false },
duration: 1.2,
ease: "power2.out",
onComplete: function() {
footer.style.display = 'none';
isScrolling = false;
}
});
}
function handleNavigation(deltaY) {
if (isScrolling) return;
if (isFooterVisible) {
if (deltaY < 0) {
hideFooter();
currentIndex = sections.length - 1;
scrollToSection(currentIndex);
}
return;
}
if (deltaY > 0) {
if (currentIndex < sections.length - 1) {
currentIndex++;
scrollToSection(currentIndex);
} else {
showFooter();
}
} else {
if (currentIndex > 0) {
currentIndex--;
scrollToSection(currentIndex);
}
}
}
// マウスホイールでのスクロール制御(スムーズ化)
let lastWheel = 0;
window.addEventListener('wheel', function(e) {
let now = Date.now();
if (now - lastWheel < 120) return; // 間隔を調整
lastWheel = now;
if (swiperCooldown) return;
handleNavigation(e.deltaY);
});
// キーボードナビゲーション
document.addEventListener('keydown', function(e) {
if (isScrolling || swiperCooldown) return;
if (isFooterVisible && e.key === "ArrowUp") {
hideFooter();
currentIndex = sections.length - 1;
scrollToSection(currentIndex);
return;
}
if (e.key === "ArrowDown") {
if (currentIndex < sections.length - 1) {
currentIndex++;
scrollToSection(currentIndex);
} else {
showFooter();
}
} else if (e.key === "ArrowUp") {
if (currentIndex > 0) {
currentIndex--;
scrollToSection(currentIndex);
}
}
});
// タッチスクロール
let touchStartY = 0;
let touchCurrentY = 0;
window.addEventListener('touchstart', function(e) {
touchStartY = e.touches[0].clientY;
});
window.addEventListener('touchmove', function(e) {
touchCurrentY = e.touches[0].clientY;
});
window.addEventListener('touchend', function(e) {
if (swiperCooldown) return;
let deltaY = touchStartY - touchCurrentY;
if (Math.abs(deltaY) > 30) {
handleNavigation(deltaY);
}
});
//console.log('初期化完了 - セクション方式で個別初期化');
});
</script>
</body>
</html>以上になります。
詳しくはAIに放り込んで聞いてください。
操作体験として若干、動作が強制的に感じて、あまり好きじゃないです。という個人的感想です。
参考サイト置き場
BRISK
GSAPを使って複雑なオープニングアニメーションを作ってみよう
https://b-risk.jp/blog/2022/05/gsap/
星間旅路のメロディ
「宇宙の静けさに包まれながら、漂流する過去の音楽を捜し求め、銀河の奥底でその旋律に耳を傾ける。」
「この電波はどこの星からきたのだろうか。」
どこかで聞いたことがあるような。



