TOPに戻るボタンがあるサイトをよく見かけます。
そのTOPに戻るボタンは、ページを読み込んだ瞬間は表示されなくて、下にスクロールすると表示されたりします。
このギミックは、javascriptとCSSで実現している場合が殆どだと思います。
この記事では、ページスクロールを検出するとても簡単なスクリプトをご紹介します。
ページスクロールを検出するjavascript
ページTOPから100pxスクロールしたら、スクロールしたと判定
- Intersection observerを使用、動作は軽い。
- ページTOP付近にl-scrollObserverをCSSで配置し、監視
- l-scrollObserverがviewportに入ったり、viewportから消えると発火し、toggleScrollClassを呼び出だす。
- toggleScrollClassは、htmlタグのdata-scrolledをトグルさせる。
- l-scrollObserverの位置をCSSで変えると、スクロール検出位置を変更できる。
function setScrollEvent() {
// IntersectionObserverをブラウザがサポートしているかどうか
const isObserveSupported =
window.IntersectionObserver && 'isIntersecting' in IntersectionObserverEntry.prototype;
if ( ! isObserveSupported ) return;
const observerOptions = {
root: null,
rootMargin: '0px',
threshold: 0,
};
const scrollObserver = new IntersectionObserver( ( entries ) => {
entries.forEach( ( entry ) => {
toggleScrollClass( ! entry.isIntersecting );
} );
}, observerOptions );
const theObserver = document.querySelector( '.l-scrollObserver' );
scrollObserver.observe( theObserver );
}
function toggleScrollClass( isScrolled ) {
if ( isScrolled ) {
document.documentElement.setAttribute( 'data-scrolled', 'true' );
} else {
document.documentElement.setAttribute( 'data-scrolled', 'false' );
}
}
<?php
add_action('wp_footer',function(){
?>
<div class="l-scrollObserver" aria-hidden="true"></div>
<?php
});
.l-scrollObserver {
display: block;
height: 1px;
left: 0;
pointer-events: none;
position: absolute;
top: 100px;
visibility: hidden;
width: 100%;
z-index: 1000;
}
カスタマイズする場合のポイント
スクロール検出位置を変更する
CSSのtop: 100px;を好きな数値に変えれば、スクロール検出位置が変わります。
ページのTOPから100pxの位置が画面から消えると、発火するのがデフォルトです。
attributeをつける対象を変更する
attributeをつける対象が<html>タグなので、違う対象に変える場合はエレメントを変更します。
function toggleScrollClass( isScrolled ) {
var elm = document.querySelector('セレクタ');
if ( isScrolled ) {
elm.setAttribute( 'data-scrolled', 'true' );
} else {
elm.documentElement.setAttribute( 'data-scrolled', 'false' );
}
}
チャタリングする不具合現象が起きた
このスクリプトは、ヘッダーサイズをscrollの有無に合わせてリサイズするギミックと組み合わせると、変化点付近でマウスを止めた時にチャタリングする現象が発生しました。
ArkheやSWELLなどでも使われているものですが、それらのテーマはヘッダー高さを変更していないので、問題が起きていないようです。
原因ははっきりわからないのですが、ある程度揺れてもスクロールの有無の判定がガチャつかないようにヒステリシス特性を持たせた方が安定すると思います。
幸い、Intersection Observerには要素が完全に表示、完全に非表示という区別で動きを分けらえるので、それを組み合わせると問題なくなりました。
<script>
//オプション
const options = {
//対象の要素が領域に出入りする際(0)との全体が表示される際(1)を指定
threshold: [0, 1],
}
const span = document.querySelectorAll('.l-scrollObserver');
//コールバック関数の定義
const callback = (entries) => {
//各 entry に対して
entries.forEach( (entry) => {
//交差の割合が1以上の場合(全体が見えている場合)
if (entry.intersectionRatio >= 1) {
entry.target.classList.add('allInView');
span.textContent = '.allInView';
document.documentElement.setAttribute( 'data-scrolled2', 'false' );
//交差の割合が0の(見えなくなった)場合
} else if(entry.intersectionRatio === 0){
entry.target.classList.remove('allInView');
span.textContent = '';
document.documentElement.setAttribute( 'data-scrolled2', 'true' );
//console.log('removed'); //確認用
}
});
}
const observer = new IntersectionObserver(callback, options);
const targets = document.querySelectorAll('.l-scrollObserver');
targets.forEach( (elem) => {
observer.observe(elem);
});
</script>
まとめ
あくまでも、ページTOPからスクロールしたかしてないかだけの判定ですので、ファーストビューをスクロールの有無で切り替える用途では便利だと思います。
かなりラフなスクロール検出ですが、その分、コードも短く軽い実装だと思います。
ページTOP以外の要素を監視して同じことを行う場合は、逆方向スクロールのことを考えないといけないので、今回の実装では対応できません。