GSAP ScrollTriggerを使う(HTML/JS)
前書き
GSAPはGreenSock社が開発しているJavaScriptライブラリです。
アニメーションを設定するのにとても便利です。
その使い方を掲載しています。
目次
- 準備
- Tween
- Timeline
- ScrollTrigger
- 一度だけ
- 繰り返し
- スクロール量に応じて動かす
- ピン留め
- クラスを付ける
- activeになった時に何かする
- ScrollTriggerの活用例
- スクロールする度に何かする
- 複数箇所を同じ様に動かす
- 複数箇所を同じ様に動かす__batch
- 横にスクロールさせる
- 順番にスクロールさせる
- 異なる速度でスクロールさせる(パララックス)
- スクロールしたらパッと切り替える
- スクロールしたらパッと切り替える__observe
- 奥にスクロールしている様に見せる
- 参考
準備
こちらのページからダウンロード出来ます。
GSAP Installation
当ページでは、3つのファイルを読み込んでいます。
import { gsap } from './gsap_3.10.4/esm/gsap-core.js';
import { CSSPlugin } from './gsap_3.10.4/esm/CSSPlugin.js';
import { ScrollTrigger } from './gsap_3.10.4/esm/ScrollTrigger.js';
gsap.registerPlugin(ScrollTrigger);
Tween
GSAPの基本の動作です。アニメーションの設定が出来ます。
Tween
動作後の値を指定__to()
gsap.to(".a01", { rotation: 225, x: 100, duration: 1 });
動作前の値を指定__from()
gsap.from(".a02", { rotation: 225, x: 100, duration: 1 });
動作前後の値を指定__fromTo()
gsap.fromTo(
".a03",
{ rotation: 225, x: 0 },
{ rotation: 0, x: 100, duration: 1 },
);
動作無し__set()
gsap.set(".a04", { rotation: 225, x: 100 });
連続して同じ様に動かす__stagger
<div class="b01"></div>
<div class="b01"></div>
<div class="b01"></div>
<div class="b01"></div>
<div class="b01"></div>
gsap.from( ".b01", {
scale: 0.1,
rotation: 180,
duration: 0.5,
stagger: 0.25 //0.25秒おきに
});
ヨーヨーの様に動かす__yoyo
gsap.from( ".b02", {
x: 100,
rotation: 225,
duration: 1,
repeat: -1, //ずっと繰り返す
yoyo: true,
});
Timeline
アニメーションを繋げることが出来ます。動作のタイミングを柔軟に調整出来ます。
Timeline
アニメーションを繋げる
const tl = gsap.timeline();
const left = document.querySelector(".tl01_left");
const right = document.querySelector(".tl01_right");
tl.from(left, { scale: 0.1, duration: 0.5 })
.from(right, { scale: 0.1, duration: 0.5 })
.to(left, { rotation: 180, duration: 0.5 }, "+=0.5") //0.5秒後
.to(right, { rotation: 180, duration: 0.5 }, "<" ); //上のアニメーションと同時
ScrollTrigger
スクロールイベントを制御する為のプラグインです。
markers属性をtrueにすれば、画面右側に目印が表示するので、発火位置の調整がし易いです。
ScrollTrigger
一度だけ
gsap.to(".st01", {
x: 100,
rotation: 225,
duration: 1,
scrollTrigger: {
trigger: '.st01',
start: 'top 75%', //triggerの上端が、画面の高さの75%の位置に来たら
}
});
繰り返し__toggleActions
この箇所でマーカーを利用しています。
gsap.to(".st02", {
x: 100,
rotation: 225,
duration: 1,
scrollTrigger: {
trigger: '.st02',
start: 'top 75%', //triggerの上端が、画面の高さの75%の位置に来たら
end: 'bottom 25%', //triggerの下端が、画面の高さの25%の位置に来たら
toggleActions: "play reverse play reverse", //繰り返し onEnter, onLeave, onEnterBack, onLeaveBack,
markers: true, // マーカー表示
}
});
スクロール量に応じて動かす__scrub
gsap.to(".st03", {
x: 100,
rotation: 225,
scrollTrigger: {
trigger: '.st03',
start: 'top 75%',
end: 'bottom 25%',
scrub: true, //スクロール量に応じて動かす
}
});
ピン留め__pin
gsap.to(".st04", {
x: 100,
rotation: 225,
scrollTrigger: {
trigger: '.st04',
start: 'top 60%',
end: "+=200", //start + 200px
scrub: true, //スクロール量に応じて動かす
pin: true, //ピン留め
}
});
クラスを付ける__toggleClass
.active{
background: #ff0000;
}
ScrollTrigger.create({
trigger: ".st05",
start: 'top 75%',
end: 'bottom 25%',
toggleClass: 'active',
});
activeになった時に何かする__onToggle
.active{
background: #ff0000;
}
ScrollTrigger.create({
trigger: ".st06",
start: 'top 75%',
end: 'bottom 25%',
onToggle: (self) => {
if(self.isActive){
self.trigger.classList.add("active");
}else{
self.trigger.classList.remove("active");
}
},
});
ScrollTriggerの活用例
下記ページにあるサンプルを参考にしています。
ScrollTrigger Demos
スクロールする度に何かする__onUpdate
const tl = gsap.timeline();
ScrollTrigger.create({
trigger: ".st07",
start: 'top 75%',
end: 'bottom 25%',
onUpdate: (self) => {
if(!tl.isActive()){ //タイムラインがアクティブでない時
const skewDeg = self.direction * 20 + "deg"; // direction(スクロール方向)は、1 か -1
tl.to(self.trigger, {skewY : skewDeg, duration: 0.5})
.to(self.trigger, {skewY : "0deg" , duration: 0.5});
}
},
});
複数箇所を同じ様に動かす
document.querySelectorAll(".fdin_up").forEach((item) => {
gsap.from(
item,
{
y: 10,
opacity: 0,
duration: 1,
scrollTrigger: {
trigger: item,
start: 'top 75%',
toggleActions: "play none none reverse", //繰り返し onEnter, onLeave, onEnterBack, onLeaveBack,
},
},
);
});
複数箇所を同じ様に動かす__batch
<div class="wrap60">
<div class="item60"></div>
<!-- 中略 -->
</div>
.wrap60{
display: flex; flex-wrap: wrap; gap: 50px 4%;
}
.item60
width: calc( ( 100 - 8) / 3 * 1% ); height: 2em; background: #346c9c;
@media screen and (min-width: 768px)
width: calc( ( 100 - 12) / 4 * 1% );
}
}
gsap.set(".item60", { y: 10, opacity: 0, });
ScrollTrigger.batch(".item60", {
batchMax: 4,
onEnter: batch => gsap.to( batch, { y: 0, opacity: 1, stagger: 0.15, }),
onLeaveBack: batch => gsap.to( batch, { y: 10, opacity: 0, stagger: 0.15, }),
start: 'top 75%',
end: 'bottom 25%',
});
横にスクロールさせる
1
2
3
<div class="area10">
<div class="wrap10">
<div class="pn10 pn11"><span>1</span></div>
<div class="pn10 pn12"><span>2</span></div>
<div class="pn10 pn13"><span>3</span></div>
</div>
</div>
.area10{
overflow: hidden;
}
.wrap10{
display: flex;
}
.pn10{
height: 100vh;
}
.pn11{ background: #346c9c; }
.pn12{ background: #b1c3cb; }
.pn13{ background: #595757; }
const area = document.querySelector(".area10");
const wrap = document.querySelector(".wrap10");
const panels = document.querySelectorAll(".pn10");
const num = panels.length;
gsap.set(wrap, { width: num * 100 + "%" });
gsap.set(panels, { width: 100 / num + "%" });
gsap.to(panels, {
xPercent: -100 * ( num - 1 ),
ease: "none",
scrollTrigger: {
trigger: area,
start: "top top",
end: "+=100%",
scrub: 0.5, //スクロール量に応じて動かす
pin: true, //ピン留め
snap: { //キリの良い位置へ移動させる
snapTo: 1 / ( num - 1 ),
duration: 0.5,
},
}
});
順番にスクロールさせる
1
2
3
<div class="area20">
<div class="wrap20">
<div class="pn20 pn21"><span>1</span></div>
<div class="pn20 pn22"><span>2</span></div>
<div class="pn20 pn23"><span>3</span></div>
</div>
</div>
.wrap20{
position: relative; height: 50vh;
}
.pn20{
position: absolute; top:0; width: 40%; height: 100%; margin: auto;
}
.pn21{ background: #346c9c; left: 0; }
.pn22{ background: #b1c3cb; left: 0; right: 0; }
.pn23{ background: #595757; right: 0; }
const area = document.querySelector(".area20");
const areaH = area.offsetHeight;
const panels = document.querySelectorAll(".pn20");
const num = panels.length;
panels.forEach((panel, i) => {
gsap.set(panel, {
zIndex : num - i,
});
});
const tl = gsap.timeline({
scrollTrigger: {
trigger: area,
start: "top 25%",
end: "+=100%",
scrub: true,
pin: true,
}
});
tl.to(".pn21", { y: -areaH * 1.5, duration: 1 })
.to(".pn22", { y: -areaH * 1.5, duration: 1 })
異なる速度でスクロールさせる(パララックス)
1
2
3
<div class="area30">
<div class="wrap30">
<div class="pn30 pn31"><span>1</span></div>
<div class="pn30 pn32"><span>2</span></div>
<div class="pn30 pn33"><span>3</span></div>
</div>
</div>
.wrap30{
position: relative; padding: 30em 0 0;
}
.pn30{
position: absolute; top: 0; bottom: 0; width: 30%; height: 4em; margin: auto;
}
.pn31{ background: #346c9c; left: 2%; }
.pn32{ background: #b1c3cb; left: 0; right: 0; }
.pn33{ background: #595757; right: 2%; }
const area = document.querySelector(".area30");
gsap.fromTo(".pn32",
{
yPercent: 200,
},
{
yPercent: -200,
ease: "none",
scrollTrigger: {
trigger: area,
start: "top bottom",
end: "bottom top",
scrub: true, //動作を遅らせない
},
}
);
gsap.fromTo(".pn33",
{
yPercent: 400,
},
{
yPercent: -400,
ease: "none",
scrollTrigger: {
trigger: area,
start: "top bottom",
end: "bottom top",
scrub: 1, //動作を遅らせる
},
}
);
スクロールしたらパッと切り替える
1
2
3
4
5
6
<div class="area40">
<div class="wrap40">
<div class="pn40 pn41"><span>1</span></div>
<div class="pn40 pn42"><span>2</span></div>
<div class="pn40 pn43"><span>3</span></div>
<div class="pn40 pn44"><span>4</span></div>
<div class="pn40 pn45"><span>5</span></div>
<div class="pn40 pn46"><span>6</span></div>
</div>
</div>
.wrap40{
position: relative; height: 50vh;
}
.pn40{
display: none; position: absolute; top: 0; left: 0; width: 100%; height: 100%;
}
.active40{
display: flex;
}
.pn41,.pn44{ background: #346c9c; }
.pn42,.pn45{ background: #b1c3cb; }
.pn43,.pn46{ background: #595757; }
const area = document.querySelector(".area40");
const panels = document.querySelectorAll(".pn40");
const length = panels.length;
const itemSwitch = (progress) => {
const snapVal = gsap.utils.snap(1, progress * length - 0.5); //0 , 1 , 2...length
const clampVal = gsap.utils.clamp(0, length - 1 , snapVal); //0 ~ length -1 の間に収める
panels.forEach((item, index) => {
if( index == clampVal){
item.classList.add("active40");
}else{
item.classList.remove("active40");
}
});
}
itemSwitch(0);
ScrollTrigger.create({
trigger: area,
start: "top 25%",
end: "+=100%",
scrub: true,
pin: true,
onUpdate: (self) => itemSwitch(self.progress),
onLeaveBack: () => itemSwitch(0),
onLeave: () => itemSwitch(1),
});
スクロールしたらパッと切り替える__observe
1
2
3
4
5
6
<div class="area80">
<div class="wrap80">
<div class="pn80 pn81"><span>1</span></div>
<div class="pn80 pn82"><span>2</span></div>
<div class="pn80 pn83"><span>3</span></div>
<div class="pn80 pn84"><span>4</span></div>
<div class="pn80 pn85"><span>5</span></div>
<div class="pn80 pn86"><span>6</span></div>
</div>
</div>
.wrap80{
position: relative; height: 50vh;
}
.pn80{
display: none; position: absolute; top: 0; left: 0; width: 100%; height: 100%;
}
.active80{
display: flex;
}
.pn81,.pn84{ background: #346c9c; }
.pn82,.pn85{ background: #b1c3cb; }
.pn83,.pn86{ background: #595757; }
const area = document.querySelector(".area80");
const panels = document.querySelectorAll(".pn80");
const length = panels.length;
let isObserve = false;
let current = 0;
const itemSwitch = (num) => {
if(num >= 0 && num < length){
panels.forEach((item, index) => {
if( index == num){
item.classList.add("active80");
}else{
item.classList.remove("active80");
}
current = num;
});
}
}
itemSwitch(current);
ScrollTrigger.create({
trigger: area,
start: "top 25%",
end: "+=700px",
pin: true,
onUpdate: (self) => self.isActive ? isObserve = true : isObserve = false,
onLeaveBack: () => itemSwitch(0),
onLeave: () => itemSwitch(length - 1),
});
ScrollTrigger.observe({
type: "wheel,touch,scroll,pointer",
tolerance: 100,
wheelSpeed: -1,
scrollSpeed: -1,
onDown: () => isObserve && itemSwitch(current - 1),
onUp: () => isObserve && itemSwitch(current + 1),
});
奥にスクロールしている様に見せる
1
2
3
<div class="area50">
<div class="wrap50">
<div class="waku50"></div>
<div class="pn50 pn51"><span>1</span></div>
<div class="pn50 pn52"><span>2</span></div>
<div class="pn50 pn53"><span>3</span></div>
</div>
</div>
.area50{
overflow: hidden;
}
.wrap50{
position: relative; height: 100vh;
}
.waku50{
height: 100%; border: 10px solid #333; box-shadow: inset 0 0 5px 5px #aaa;
}
.pn50{
position: absolute;
}
.pn51{ background: #346c9c; }
.pn52{ background: #b1c3cb; }
.pn53{ background: #595757; }
const area = document.querySelector(".area50");
const panels = document.querySelectorAll(".pn50");
const num = panels.length;
const tl = gsap.timeline({
scrollTrigger: {
trigger: area,
start: "top top",
end: "+=150%",
scrub: true,
pin: true,
}
});
panels.forEach((panel, i) => {
gsap.set(panel, {
zIndex : num - i,
});
});
gsap.set(".waku50", { scale: 0.9, });
gsap.set(".pn51", { scale: 0, width: "75%", height: "50%", left: "12.5%", top: "25%", });
gsap.set(".pn52", { scale: 0, width: "75%", height: "50%", left: "12.5%", top: "25%", });
gsap.set(".pn53", { scale: 0, width: "100%", height: "75%", left: 0, top: "12.5%", });
tl.to(".waku50", { scale: 1.2, duration: 0.5})
.to(".pn51", { scale: 1, left: "-37.5%", top: "5%", duration: 1 },"-=0.5")
.to(".pn51", { opacity: 0, duration: 0.2 }, "-=0.2")
.to(".pn52", { scale: 1, left: "62.5%", top: "55%", duration: 1 }, "-=0.5")
.to(".pn52", { opacity: 0, duration: 0.2 }, "-=0.2")
.to(".pn53", { scale: 1, duration: 1 }, "-=0.5")
参考
関連記事
Intersection Observer APIを使う(JavaScript)
固定ヘッダの高さ分、スクロール位置をずらす(HTML/CSS)
この記事は役に立ちましたか?
送信中