【GSAP】テキスト固定で背景に画像が流れるスクロールアニメーション
GSAP
posted: 2024/03/06
update: 2024/03/06
![](https://chisaito-portfolio.jp/wp-content/uploads/2024/03/gsap-mv.jpg)
GSAPは基礎的な動きは調べればだいたい出てくるのですが、少し応用的な動きだとあまり出てこなかったのでメモ。あと、仕事で作ったものの色々あってボツになってしまって勿体無いので。
作成した動きはタイトルそのままです。pinでテキスト固定をして、その背景に画像を流す動き。
やりたいこと:テキストをセンタリング固定で切り替えて、その背景にスクロールに準じて画像を流す+文字が切り替わったタイミングで背景色を切り替える。
DEMO はこちら
※文言は伝説のキャラソンから拝借しました。
・HTML
<div class="demo01">
<div class="demo01Flow">
<!-- テキスト文言 -->
<div class="demo01FlowInner">
<h2 class="demo01FlowText">
<span class="demo01FlowText--txt01">
桜咲く、舞い落ちる<br>
何もない僕の手の上
</span>
<span class="demo01FlowText--txt02">
儚くて優しくて、壊れそう<br>
君みたいな花
</span>
</h2>
</div>
<!-- / テキスト文言 -->
<!-- 背景に流れる画像 -->
<div class="demo01FlowImgs">
<div class="demo01FlowImgs--img01">
<figure class="img01 js-trigger01">
<img src="/assets/img/demo/01/img01.jpg" alt="昼桜" width="303" height="292">
</figure>
<figure class="img01 js-trigger01">
<img src="/assets/img/demo/01/img02.jpg" alt="昼桜" width="288" height="432">
</figure>
<figure class="img01 js-trigger01">
<img src="/assets/img/demo/01/img03.jpg" alt="昼桜" width="439" height="267">
</figure>
<figure class="img01 js-trigger01">
<img src="/assets/img/demo/01/img04.jpg" alt="昼桜" width="323" height="299">
</figure>
<figure class="img01 js-trigger01">
<img src="/assets/img/demo/01/img05.jpg" alt="昼桜" width="316" height="273">
</figure>
<figure class="img01 js-trigger01">
<img src="/assets/img/demo/01/img06.jpg" alt="夜桜" width="360" height="360">
</figure>
<figure class="img01 js-trigger01">
<img src="/assets/img/demo/01/img07.jpg" alt="夜桜" width="254" height="215">
</figure>
<figure class="img01 js-trigger01">
<img src="/assets/img/demo/01/img08.jpg" alt="夜桜" width="520" height="340">
</figure>
<figure class="img01 js-trigger01">
<img src="/assets/img/demo/01/img09.jpg" alt="夜桜" width="384" height="478">
</figure>
</div>
</div>
<!-- / 背景に流れる画像 -->
</div>
</div>
・CSS(sass)
.demo01 {
&Flow {
position: relative;
width: 100%;
margin-bottom: -100px;
&Inner {
position: relative;
z-index: 30;
margin: 0 auto;
}
&Text {
color: #000;
font-family: $noto;
position: relative;
font-weight: 500;
line-height: 200%;
letter-spacing: 0.64px;
z-index: 30;
@include media(lg) {
height: 100vh;
@include fontsize(18);
text-align: center;
}
@include media(sm) {
height: 50vh;
@include fontsize(16);
}
&--txt01,
&--txt02 {
position: absolute;
@include media(lg) {
text-align: center;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
@include media(sm) {
display: inline-block;
top: 30%;
left: 5%;
transform: none;
text-align: left;
}
}
}
&Imgs {
&--img01 {
.img01 {
display: inline-block;
position: absolute;
z-index: 10;
@include media(lg) {
height: 15vw;
}
@include media(sm) {
width: 100%;
height: auto;
}
img {
width: 100%;
height: auto;
}
&:nth-of-type(1) {
@media screen and (min-width: 1024px) {
top: 8%;
right: 5%;
width: 250px;
}
@media screen and (min-width: 1440px) {
top: 8%;
right: 5%;
width: 303px;
}
@include media(sm) {
top: 13%;
right: 0;
width: 200px;
}
}
&:nth-of-type(2) {
left: 0;
@media screen and (min-width: 1024px) {
top: 12%;
width: 230px;
}
@media screen and (min-width: 1440px) {
top: 12%;
width: 288px;
}
@include media(sm) {
top: 18%;
width: 140px;
}
}
&:nth-of-type(3) {
right: 0;
@media screen and (min-width: 1024px) {
top: 25%;
width: 300px;
}
@media screen and (min-width: 1440px) {
top: 25%;
width: 400px;
}
@include media(sm) {
top: 30%;
width: 210px;
}
}
&:nth-of-type(4) {
@media screen and (min-width: 1024px) {
top: 30%;
left: 17%;
width: 260px;
}
@media screen and (min-width: 1440px) {
top: 30%;
left: 17%;
width: 320px;
}
@include media(sm) {
top: 40%;
left: 0;
width: 250px;
}
}
&:nth-of-type(5) {
right: 0;
@media screen and (min-width: 1024px) {
top: 37%;
right: 20%;
width: 200px;
}
@media screen and (min-width: 1440px) {
top: 37%;
right: 23%;
width: 200px;
}
@include media(sm) {
top: 65%;
width: 140px;
}
}
&:nth-of-type(6) {
right: 0;
@media screen and (min-width: 1024px) {
top: 50%;
left: 0;
width: 300px;
}
@media screen and (min-width: 1440px) {
top: 50%;
left: 0;
width: 360px;
}
@include media(sm) {
top: 55%;
left: 0;
width: 140px;
}
}
&:nth-of-type(7) {
@media screen and (min-width: 1024px) {
top: 55%;
right: 20%;
width: 250px;
}
@media screen and (min-width: 1440px) {
top: 55%;
right: 20%;
width: 250px;
}
@include media(sm) {
top: 65%;
right: 20%;
width: 140px;
}
}
&:nth-of-type(8) {
@media screen and (min-width: 1024px) {
top: 67%;
right: 5%;
width: 300px;
}
@media screen and (min-width: 1440px) {
top: 67%;
right: 5%;
width: 360px;
}
@include media(sm) {
top: 67%;
right: 5%;
width: 140px;
}
}
&:nth-of-type(9) {
right: 0;
@media screen and (min-width: 1024px) {
top: 70%;
left: 20%;
width: 310px;
}
@media screen and (min-width: 1440px) {
top: 70%;
left: 20%;
width: 350px;
}
@include media(sm) {
top: 70%;
left: 20%;
width: 140px;
}
}
}
}
}
}
}
・GSAP
/* PC/SP出しわけ */
window.matchMedia("(max-width: 959px)").matches;
const getDeviceType = (tb = false) => {
if (tb && window.matchMedia("(min-width: 960px) and (max-width: 1200px)").matches) {
return "tb";
} else if (window.matchMedia("(max-width: 959px)").matches) {
return "sp";
} else {
return "pc";
}
};
/* Demo01 */
const mainFlowanimation = gsap.timeline();
mainFlowanimation.set(".demo01FlowText--txt01", {
y: 0,
});
mainFlowanimation.set(".demo01FlowText--txt02", {
opacity: 0,
y: 0,
color: "#000",
});
mainFlowanimation.set(".demo01Flow", {
background: "#fff"
});
mainFlowanimation.to(".demo01FlowText--txt01", {
opacity: 1,
duration: getDeviceType() === "sp" ? 0.1 : 0.6,
});
mainFlowanimation.to(".demo01FlowText--txt01", {
opacity: 0,
});
mainFlowanimation.to(".demo01Flow", {
background: "#000"
}, '<');
mainFlowanimation.to(".demo01FlowText--txt02", {
opacity: 1,
duration: getDeviceType() === "sp" ? 0.1 : 0.6,
color: "#fff",
});
ScrollTrigger.create({
trigger: ".demo01FlowText",
animation: mainFlowanimation,
start: getDeviceType() === "sp" ? "top top" : "top top",
end: getDeviceType() === "sp" ? "bottom top-=150%" : "bottom top-=200%",
pin: ".demo01FlowInner",
scrub: true,
paused: true,
});
const items01 = gsap.utils.toArray(".js-trigger01");
items01.forEach((item01) => {
gsap.fromTo(
item01.querySelector("img"),
{
y: 100,
},
{
opacity: 1,
y: 0,
ease: "power4.Out",
scrollTrigger: {
trigger: item01,
start: "top top+=30%",
end: "bottom top+=30%",
scrub: true,
},
}
);
});
少し長くなったけど基本的なことしかやっていないのでとても簡単。