Open1
ScrollTriggerでpinした要素の子要素に対してScrollTrogger的なアニメーション適用するやつ
前置き
- ①スクロール準拠のアニメーションがウィンドウ内にいくつもある、タイミングがそれぞれ違うなど複雑なスクロールアニメーションがを要求された。
- ②上記に加えて、スクロール固定した親要素の子要素がスクロール準拠のアニメーションする演出がいっぱいあった。
- 単純にアニメーションする要素毎にScrollTriggerつけたら管理しづらい・パフォーマンス的に悪いかもってなった。
- ②に関しては要素固定したタイミングから○割までスクロールしたらアニメーション開始させたい要望などがあり、アニメーション要素のDOM位置に準拠させたScrollTriggerの設定ではタイミングの調整が難しい場合があった。
作ったもの
コード全体
ScrollFixed.js
import { gsap } from "gsap-trial";
import { ScrollTrigger } from "gsap-trial/ScrollTrigger";
/**
* @param { object } param0
* @param {HTMLElement} param0.element
* @param {number} param0.scrollAmount
*/
class ScrollFixed {
constructor({ element, scrollAmount }) {
this._element = element;
this._onEnter = () => {};
this._onEnterBack = () => {};
this._onLeave = () => {};
this._onLeaveBack = () => {};
this._onUpdate = () => {};
this._init({ element, scrollAmount });
}
set onEnter(onEnter) {
this._onEnter = onEnter;
}
set onEnterBack(onEnterBack) {
this._onEnterBack = onEnterBack;
}
set onLeave(onLeave) {
this._onLeave = onLeave;
}
set onLeaveBack(onLeaveBack) {
this._onLeaveBack = onLeaveBack;
}
set onUpdate(onUpdate) {
this._onUpdate = onUpdate;
}
/**
* 初期化
* @param { object } param0
* @param {HTMLElement} param0.element
* @param {number} param0.scrollAmount
*/
_init({ element, scrollAmount }) {
this._element = element;
this._scollAmount = scrollAmount
? scrollAmount
: this._element.clientHeight;
this._setScrollFixed();
}
/**
* スクロール固定にする
*/
_setScrollFixed() {
ScrollTrigger.create({
markers: true,
trigger: this._element,
start: "top top",
end: `top+=${this._scollAmount} top`,
scrub: true,
pin: true,
onEnter: () => this._onEnter(),
onEnterBack: () => this._onEnterBack(),
onLeave: () => this._onLeave(),
onLeaveBack: () => this._onLeaveBack(),
onUpdate: (self) => this._onUpdate(self.progress)
// pinSpacing: false
});
}
}
export { ScrollFixed };
ScrollAnimation.js
import { ScrollFixed } from "../src/ScrollFixed";
import { gsap } from "gsap-trial";
/**
* 値のマッピング
* num : マッピングする値
* toMin : 変換後の最小値
* toMax : 変換後の最大値
* fromMin : 変換前の最小値
* fromMax : 変換前の最大値
*
* @export
* @param {number} num
* @param {number} fromMin
* @param {number} fromMax
* @param {number} toMin
* @param {number} toMax
* @returns {number}
*/
const map = (num, fromMin, fromMax, toMin, toMax) => {
if (num <= fromMin) return toMin;
if (num >= fromMax) return toMax;
const p = (toMax - toMin) / (fromMax - fromMin);
return (num - fromMin) * p + toMin;
};
/**
* @param {Object} param0
* @param {HTMLElement} param0.element [data-message]
*/
class ScrollAnimations {
constructor({ element }) {
this._init({ element });
}
/**
* 初期化
* @param {object} param0.element [data-message]
*/
_init({ element }) {
this._element = element;
this._textElement = this._element.querySelector(
'[data-scroll-animations="text"]'
);
this._imgElement = this._element.querySelector(
'[data-scroll-animations="img"]'
);
this.imgIsVisible = false;
// ScrollFixedインスタンス
this._scrollFixedInstance = new ScrollFixed({
element,
scrollAmount: 3000
});
// ScrollTriggerアップデート時の処理
this._scrollFixedInstance.onUpdate = (progress) => {
// アニメーションする要素のタイムラインを0~1で準備してあげる
const backGroundTimeline = map(progress, 0, 1, 0, 1);
const messageTimeline = map(progress, 0.5, 0.8, 0, 1);
const imgTimeline = map(progress, 0.8, 0.9, 0, 1);
// アニメーションのアップデートを実行
this._setUpdateBackGround(backGroundTimeline);
this._setUpdateMessage(messageTimeline);
this._setUpdateImg(imgTimeline);
};
}
/**
*
* @param {Number} progress //0~1
*/
_setUpdateBackGround(progress) {
const c = [120 + 125 * progress, 20 + 125 * progress, 80 + 125 * progress];
gsap.set(this._element, {
background: `rgba(${c[0]}, ${c[1]}, ${c[2]}, ${progress})`
});
}
/**
*
* @param {Number} progress //0~1
*/
_setUpdateMessage(progress) {
gsap.set(this._textElement, {
autoAlpha: progress
});
}
/**
*
* @param {Number} progress //0~1
*/
_setUpdateImg(progress) {
if (progress > 0 && !this.imgIsVisible) {
// 表示処理
gsap.to(this._imgElement, {
autoAlpha: 1,
duration: 1.0
});
this.imgIsVisible = true;
} else if (progress <= 0 && this.imgIsVisible) {
// 非表示処理
gsap.to(this._imgElement, {
autoAlpha: 0,
duration: 1.0
});
this.imgIsVisible = false;
}
}
}
export { ScrollAnimations };
要点
_setScrollFixed() {
ScrollTrigger.create({
markers: true,
trigger: this._element,
start: "top top",
end: `top+=${this._scollAmount} top`,
scrub: true,
pin: true,
onEnter: () => this._onEnter(),
onEnterBack: () => this._onEnterBack(),
onLeave: () => this._onLeave(),
onLeaveBack: () => this._onLeaveBack(),
onUpdate: (self) => this._onUpdate(self.progress)
// pinSpacing: false
});
}
スクロールを固定するための親要素を作成、ScrollTriggerを設定し、スクロール進捗率はprogressで取得。onUpdate関数の中でアニメーションの定義を行う。
this._scrollFixedInstance.onUpdate = (progress) => {
// アニメーションする要素のタイムラインを0~1で準備してあげる
const backGroundTimeline = map(progress, 0, 1, 0, 1);
const messageTimeline = map(progress, 0.5, 0.8, 0, 1);
const imgTimeline = map(progress, 0.8, 0.9, 0, 1);
// アニメーションのアップデートを実行
this._setUpdateBackGround(backGroundTimeline);
this._setUpdateMessage(messageTimeline);
this._setUpdateImg(imgTimeline);
};
progress(0~1)の値をmap関数でアニメーション開始させたいタイミングと終了タイミングを設定。