要素までの距離を測る
スクロールが要素に達したことを感知できるよう、まずは表示させたい要素がブラウザの上端からどれぐらいの位置にあるのかを計測しておく必要があります。
でなければ、addEventListenerでscrollイベントを指定するにしても、スクロール量をどれぐらいに設定すれば良いのかがわかりませんからね。
Webページの上端からスクロールで表示させたい要素の距離は、offsetTopプロパティを使用して測ることができます。
offsetTopは読み取り専用のプロパティであり、基本的にはブラウザ(body要素)の上端からの距離を返します。
offsetTopプロパティは、table、th、tdなど表の要素を祖先に持つ場合や、position指定された要素を祖先に持つ場合、最も近い要素からの距離を返します。
下は、幅と高さを持つ要素を3つ用意し、それぞれの要素がbody要素の上端からどれぐらいの位置にあるのかをoffsetTopで測った例です。
<body>
<div class="scroll_appear">
<span class="distance"></span>
</div>
<div class="scroll_appear">
<span class="distance"></span>
</div>
<div class="scroll_appear" style="margin-top: 200px;">
<span class="distance"></span>
</div>
</body>
body {
display: grid;
place-items: center;
margin: 0;
font-size: 20px;
font-family: sans-serif;
background-color: white;
}
.scroll_appear {
width: 250px;
height: 200px;
margin: 32px auto;
background-color: darkslategrey;
color: white;
display: flex;
justify-content: center;
align-items: center;
}
// 距離を測りたい要素を全て取得
const scrollAppears = document.querySelectorAll(".scroll_appear");
// 距離を表示させるspan要素を全て取得
const distance = document.querySelectorAll(".distance")
// body要素の上端からの距離をoffsetTopで測り、span要素のテキストとして代入する
for (let index = 0; index < scrollAppears.length; index++) {
distance[index].textContent = scrollAppears[index].offsetTop;
}
上の表示結果は、iframe要素でWebページをこの記事内に埋め込んでいるので、このWebページの上端からではなく、iframe要素内の上端からの距離になっています。
なお、この例ではdiv要素とspan要素の総数および順番が完全に一致するので、”scroll_appear”とclass名を付けたdiv要素のみをfor文でループ処理しています。(indexをspan要素にも使い回している)
これで、スクロールで表示させたい要素が上端からどれぐらいの位置にあるのか、数値で測ることができました。
ちなみに、offsetTopはbody要素の上端から要素の外枠までの距離を返します。
要素の中心までの距離ではないので、注意が必要です。(下の画像を参照)
offsetTopの計測距離
あとは、addEventListenerでスクロールイベントを監視し、計測した数値にスクロール値が達するタイミングでアニメーションを発火させれば良いということになります。
アニメーションは、classList.add()を使って要素にclassを追加することで簡単にできます。
次の章では、JavaScriptでスクロールイベントの処理を行う前の準備段階として、CSSを追加してアニメーションが発火する前と、発火した後の状態を調整していきましょう!
POINT!
- ・body要素の上端から特定の要素までの距離を測るには、offsetTopプロパティを使えばOK!
- ・tableなど表の要素の場合、offsetTopの扱いが変わるので注意!
- ・アニメーションの発火は、classList.add()を使うと簡単!
CSSで要素をアニメーション
現状では、body要素の上端から要素までの距離を測れただけで、要素が表示されたままになってしまっています。
これをCSSで隠しておき、JavaScriptのclass操作によって”active”というclassが付加されたらアニメーションしながら現れるように調整していきましょう。
CSSで要素を非表示にする = displayプロパティでnoneを指定する…と考えてしまいがちですが、displayプロパティをnoneにするとoffsetTopで距離を測れなくなってしまいます。
なので、ここは素直にopacityで隠し、position(left)で左に移動させ、左からスッと現れるアニメーションにしてみたいと思います。
.scroll_appear {
width: 250px;
height: 200px;
margin: 32px auto;
background-color: darkslategrey;
color: white;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
position: relative;
left: -300px;
transition: all .5s ease-in-out;
}
.scroll_appear.active {
opacity: 1;
left: 0;
}
これで要素をアニメーションさせる準備が完了しました!
普段は、opacityプロパティで不透明度を0にして完全に見えないように隠しておき、acitveというclassが要素に付加されたらそれが解除される(opacityの値が1になる)ようにしています。
同時に、leftプロパティで移動させていた距離を元に戻すことによって、左からスッと現れるようなアニメーションになります。
なお、この時点で表示結果を掲載しても何も表示されない状態であるため、表示結果の掲載は省略します。
次の章では、JavaScriptでスクロールイベントを設定し、スクロール量が要素に達したらclassを付与するプログラムを書いていきましょう。
POINT!
- ・要素のdisplayプロパティをnoneにして非表示にすると、offsetTopで距離を測れなくなるので注意!
- ・要素はopacityプロパティなどを使って非表示(見えない状態)にしよう!
- ・移動を伴うアニメーションにしたい場合は、positionで調整しよう!
スクロールでclassを付与する
アニメーションの準備ができたので、次はJavaScriptでスクロールイベントを監視し、offsetTopで計測したスクロール値に達したらclassを追加する処理を行なっていきます。
スクロールイベントは、window.addEventListenerで監視することができます。
そしてclassの操作は、classList.add()で要素にclassを付加することができます。
なお、ターゲットとなる要素に達するまで画面をスクロールできるだけの縦幅が必要になるので、今回のサンプルではスペーサーとなるdiv要素(class=”spacer”)を追加しておきました。
スペーサーは、縦幅を持たせる以外に意味や役割を持つわけではないので、スペーサーのCSS調整の掲載は省略しています。
以上の点を踏まえて、サンプルコードを確認していきましょう!
<body>
<div class="spacer">下にスクロールさせてください</div>
<div class="scroll_appear">
<span class="distance"></span>
</div>
<div class="scroll_appear">
<span class="distance"></span>
</div>
<div class="scroll_appear" style="margin-top: 200px;">
<span class="distance"></span>
</div>
<div class="spacer">スペーサー</div>
</body>
// 距離を測りたい要素を全て取得
const scrollAppears = document.querySelectorAll(".scroll_appear");
// 距離を表示させるspan要素を全て取得
const distance = document.querySelectorAll(".distance")
for ( let index = 0; index < scrollAppears.length; index++ ) {
// body要素の上端から要素までの距離をoffsetTopで測り、elementDistanceに格納
const elementDistance = scrollAppears[index].offsetTop;
// span要素のテキストに距離を代入
distance[index].textContent = elementDistance;
// スクロールイベントの設定
window.addEventListener("scroll", () => {
// 現在の縦方向のスクロール位置を変数 y に格納
let y = window.scrollY;
// スクロールの位置がoffsetTopで計測した位置に達したら"active"のclassを追加
if ( y >= elementDistance ) {
scrollAppears[index].classList.add("active");
}
});
}
これで完成!…としたいところですが、実はこのコードには致命的なミスが含まれます。
これでは、offsetTopで計測した数値分、画面をスクロールさせなければ要素が表示されません。
つまり、このままでは要素が出現するタイミングが遅すぎるということになってしまいます。
上のコードを実行したデモを下に用意したので、実際にスクロールさせて要素が表示するタイミングを確認してみてください。
画面のスクロールに合わせて要素が表示されてはいるものの、そのタイミングが遅すぎるので改良が必要ですね。
次の章でこの問題を解決して、スクロールで要素が現れるアニメーションを完成させていきましょう!
POINT!
- ・スクロールイベントは、window.addEventListenerでscrollを指定することで監視できる!
- ・要素にclassを付加するには、classList.add()を使う!
- ・offsetTopで計測した距離をそのまま比較値にすると、要素が現れるタイミングが遅いので改良が必要!
要素が現れるタイミングを調整する
アニメーション発火のタイミングを調整するには、if文中の y >= elementDistance の部分を変更すれば良いのですが、なんとなくで数値を調整してもうまくいきません。
たとえば、タイミングを遅らせたいからといって、y >= elementDistance – 500 などと設定してしまうと、画面(ブラウザ)の大きさによってタイミングがズレてしまいます。
では、どうすれば良いのでしょうか?
解決策の一つとして、『ブラウザの高さを調べて、その数値を利用する』という方法が挙げられます。
ブラウザの高さを調べて、その数値を使って要素が現れるタイミングを調整すれば、PCやスマホなど画面の大きの違いに左右されなくなります。
要素を出現させるタイミングに絶対的な正解があるわけではありませんが、概ね、offsetTopの値からブラウザの高さ÷2を引いた値ぐらいで調整すると、良い感じになってくれます。
ブラウザの高さは、window.innerHeightで調べることができます。
以上を踏まえて、JavaScriptコードを修正してみましょう。
// 距離を測りたい要素を全て取得
const scrollAppears = document.querySelectorAll(".scroll_appear");
// 距離を表示させるspan要素を全て取得
const distance = document.querySelectorAll(".distance");
// 画面(ブラウザ)の高さを取得
const windowHeight = window.innerHeight;
for ( let index = 0; index < scrollAppears.length; index++ ) {
// body要素の上端から要素までの距離をoffsetTopで測り、elementDistanceに格納
const elementDistance = scrollAppears[index].offsetTop;
// span要素のテキストに距離を代入
distance[index].textContent = elementDistance;
// スクロールイベントの設定
window.addEventListener("scroll", () => {
// 現在の縦方向のスクロール位置を変数 y に格納
let y = window.scrollY;
// offsetTopの数値からブラウザの高さ÷2を引いた値に達したら"active"のclassを追加
if ( y >= elementDistance - ( windowHeight / 2 ) ) {
scrollAppears[index].classList.add("active");
}
});
}
これで要素が出現するタイミングが調整されました!
こちらも、上のコードを実行したデモを下に用意しているので、要素が出現するタイミングが改善されていることを実際に確認してみて下さい。
外部のライブラリに頼らず、素のJavaScriptでスクロールに合わせて要素を出現させるアニメーションが実装できました!
ライブラリは、必要のないコードまで読み込んでしまう=容量が重くなりがちになるので、ちょこっとWebページに動きをつけたいという程度であれば、素のJS(Vanilla JS)で実装するのもオススメです.
POINT!
- ・要素が出現するタイミングを調整する際は、画面(ブラウザ)の高さを取得してその数値を利用しよう!
- ・ブラウザの高さは、window.innerHeightで取得できる!
- ・offsetTopから、innerHeight÷2を引いた数値ぐらいを目安にするとGOOD!