Intersection Observerでトップに戻るボタンの表示を制御する
Intersection Observerとは
Intersection Observerは、要素とその親要素やビューポートとの交差状態を非同期で監視するためのWeb API です。要素が画面内に入ってきたり、画面外に出たりした際のイベントを検知できます。
参考記事
やりたいこと
Intersection Observerでボタンのクラスを付与・削除する
- MainVisualが画面から消えたらトップへ戻るボタンを表示させる
- Footerが表示されたらトップへ戻るボタンの位置をFooterより上の位置に固定させる
DEMO
コード
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>io-sample</title>
</head>
<body>
<header>
header
</header>
<main>
<section class="mv" id="mv">
<p>
MainVisualがviewportから消えるとbtnが表示される
</p>
</section>
<section class="contents">contents</section>
<section class="contents">contents</section>
<section class="contents">contents</section>
<section class="contents">contents</section>
</main>
<div class="btn-wrapper" id="btn-wrapper">
<a href="#" class="btn" id="btn">
<div class="btn-inner">
</div>
</a>
</div>
<footer id="footer">
<p>footerがviewportに入るとbtnの位置がfooter上に固定される</p>
</footer>
<script type="module" src="/main.js"></script>
</body>
</html>
style.css
html {
scroll-behavior: smooth;
}
body {
margin: 0;
font-family: Helvetica, sans-serif;
font-size: 16px;
font-weight: bold;
text-align: center;
text-transform: capitalize;
}
header, footer, section {
display: grid;
place-items: center;
}
header {
background-color: rgb(31, 33, 34);
height: 200px;
color: white;
}
.mv {
height: 400px;
background-color: rgb(180, 193, 204);
}
.contents {
margin: 20px auto;
width: 80%;
height: 400px;
background-color: rgb(181, 182, 194);
}
footer {
background-color: rgb(31, 33, 34);
height: 400px;
color: white;
}
.btn-wrapper {
position: relative;
}
.btn {
position: fixed;
right: 50px;
bottom: 100px;
visibility: hidden;
pointer-events: none;
opacity: 0;
transition: all 0.3s;
}
.btn.active {
visibility: visible;
pointer-events: auto;
opacity: 1;
}
.btn.absolute {
position: absolute;
right: 50px;
bottom: 50px;
}
.btn-inner {
width: 48px;
height: 48px;
background-color: rgb(226, 40, 40);
opacity: 0.7;
display: grid;
}
.btn-inner::before {
justify-self: center;
align-self: center;
margin-top: 15px;
content: '';
transform: translate(-50%, -50%);
width: 20px;
height: 20px;
border-top: solid 2px;
border-right: solid 2px;
transform: rotate(90deg);
transform: rotate(315deg);
border-color: white;
}
main.js
import './style.css'
/*==============================================================
トップへ戻るボタンの表示を切替 intersection observer
==============================================================*/
const btn = document.getElementById('btn');
const mv = document.getElementById('mv');
const mvOptions = {
root: null,
rootMargin: "0px 0px 0px 0px",
threshold: 0
};
const mvObserver = new IntersectionObserver(doWhenMvIntersect, mvOptions);
mvObserver.observe(mv);
function doWhenMvIntersect(entries) {
entries.forEach(entry => {
if (entry.isIntersecting ) {
btn.classList.remove('active');
} else {
btn.classList.add('active');
}
});
}
/*==============================================================
トップへ戻るボタンの位置を変更
==============================================================*/
const footer = document.getElementById('footer');
const footerOptions = {
root: null,
rootMargin: "0px 0px 0px 0px",
threshold: 0
};
const footerObserver = new IntersectionObserver(doWhenFooterIntersect, footerOptions);
footerObserver.observe(footer);
function doWhenFooterIntersect(entries) {
entries.forEach(entry => {
if (entry.isIntersecting ) {
btn.classList.add('absolute');
} else {
btn.classList.remove('absolute');
}
});
}
解説
監視対象を特定
今回監視対象にidを付与し document.getElementById
で取得しています。
複数の対象を取得する時はdocument.getElementsByClassName
で取得をします。
document.getElementsByClassName
はHTMLCollectionを返すため、単独で取得したい場合は
const header = document.getElementsByClassName('header')[0];
のように配列の0番目を取得する必要があります。
toggleは使わない
MVを監視対象にした場合、画面更新時に既に交差しているため、一度Intersection Observerが発火してしまいます。
クラス名の付与・削除にはtoggleが便利ですが、画面更新時に一度発火する状態でtoggleを使用すると意図しない挙動になりがちです。
そのため、MVと交差している状態ではremove、交差していない状態ではaddで制御しています。
ボタン位置の固定
footer上にボタンを固定するために、contentsセクションとfooterの間にbtn-wrapperを置き、position: relative;としています。
footerが表示されたらbtnのクラス名に”absolute”が付与されbtn-wrapper基準でbtnの位置が固定されます。
.btn-wrapper {
position: relative;
}
.btn {
position: fixed;
right: 50px;
bottom: 100px;
}
.btn.absolute {
position: absolute;
right: 50px;
bottom: 50px;
}
補足
scrollイベント
Intersection Observerの登場以前はこうした処理はscrollイベントを用いてスクロール量に応じて任意のクラスを付与する処理が一般的でした。しかし、スクロールする度に計算が走るため処理が重くなる、画面をresizeした時の処理を追加しなければならない、などデメリットがありました。Intersection Observerはscrollイベントのそういったデメリットを解消しています。
Discussion