📑
慣性が効いてるみたいなワンスクロールつくってみた
慣性がきいてるみたいなワンスクロール
ワンスクロールみたいなスクロールを制限する挙動ってUXの観点から結構嫌われがちだけれど、サイトの目的とかデザインによっては、リッチな表現になったりするかなと思ってます。特にデザイン会社である弊社(https://twitter.com/funtech_inc) 案件だと結構使ったりします。
ホイールイベントメソッドを登録できるので、慣性的にしないこともできます〜
結論:できたのはこれ
要件はこんな感じ
- ページの途中から画面固定を始めることができる
- 最初から画面固定にすることもできる
- その際に無限スクロールにすることができる
- トランジションアニメーションメソッドを追加可能にする
- ホイール中のアニメーションメソッドを追加可能にする
- ボタンで特定のシーンにジャンプすることができる
- スクロールバーの操作や↓↑ボタンの操作があった場合にも状態が変わる
- resizeに対応
- スマホ対応
- falseにもできる
- マウスホイールなどで無茶苦茶スクロールして位置がズレた場合、それを検出して位置を修正させる
- ので、スクロールバーを
直接操作しやがった場合にも対応できる
- ので、スクロールバーを
-
scrollとwheel eventをrAFで間引くあんま意味ないかも - クラスにする
使い方
あんまり使い所はないかもですが、、、
もし使ったよ!って方いたら改善点とか教えていただけると助かります!
(ライブラリつくったことないので、時間みつけて作ってみようかな)
初期化
const fixedView = new FixedView(SCENES, {
buttonsTarget: BUTTONS,
isInfinitScroll: false,
isMobile: true,
});
fixedView.mount();
第一引数にシーンのターゲット、第二引数にoptionが入ります。
ホイールイベントの登録
let wheelTimeOutID = 0;
/*===============================================
wheel
===============================================*/
fixedView.on("wheel", (obj) => {
//デフォルトの挙動
const moveY = obj.scrollVol * -0.1;
//慣性アニメーション
gsap.context(() => {
gsap.to(".js_animTarget", {
y: `+=${moveY}`,
duration: 0.3,
ease: "power3.out",
stagger: {
each: 0.01,
},
});
}, obj.target);
//一定時間が経過すると元に戻るアニメーション
clearTimeout(wheelTimeOutID);
wheelTimeOutID = setTimeout(() => {
if (!obj.wheelState.isInviewPrevent) {
//inviewが発火してる間は発火させない
gsap.context(() => {
gsap.to(".js_animTarget", {
y: 0,
duration: 0.4,
ease: "back.out(3)",
stagger: {
each: 0.01,
},
});
}, obj.target);
}
}, obj.wheelState.transitionCanceDur);
});
wheelイベントが変えるobjにはwheelStateというオブジェクトが入ってます。isInviewPreventがtrue間はホイールイベントが発火しないようにできます。transitionCanceDurはスクロール量のthresholdを0にするまでの時間です。
leaveとenterアニメーションの登録
※leaveとenterはそれぞれresolveを待つようになってるので、promiseを返すようにする必要があります
/*===============================================
leaveアニメーション
===============================================*/
fixedView.on("leave", async (obj) => {
let moveVol = 12;
let moveY = 0;
let setY = 0;
const DURATIONVAL = 0.6;
if (obj.isForward) {
moveY = `${moveVol * -1}rem`;
setY = `${moveVol}rem`;
} else {
moveY = `${moveVol}rem`;
setY = `${moveVol * -1}rem`;
}
//次の要素を操作しておく
gsap.context(() => {
gsap.set(".js_animTarget", {
y: setY,
opacity: 0,
});
}, obj.nextTarget);
//今表示されてるのをフェードアウトさせる
return new Promise((resolve) => {
gsap.context(() => {
gsap.to(".js_animTarget", {
y: moveY,
opacity: 0,
duration: DURATIONVAL,
ease: "power3.out",
stagger: {
each: 0.01,
},
onComplete: () => {
resolve();
},
});
}, obj.currentTarget);
});
});
/*===============================================
enterアニメーション
===============================================*/
fixedView.on("enter", async (obj) => {
const DURATIONVAL = 0.6;
return new Promise((resolve) => {
gsap.context(() => {
gsap.to(".js_animTarget", {
y: "0",
opacity: 1,
duration: DURATIONVAL,
ease: "power3.out",
stagger: {
each: 0.01,
},
onComplete: () => {
resolve();
},
});
}, obj.nextTarget);
});
});
Discussion