JavaScriptでのページ内スクロールとDOCTYPE宣言でハマった話
ページ内のスクロール処理、例えば[上へ]ボタンや[下へ]ボタンは様々なWebサイトで見られますが、実装方法はいろいろと異なりますし、特定の要素を指定したスクロールについては少しややこしいので、そのあたりの解説を記事にしよう……………………と思っていました。
その前に基本となるページの最上部および最下部へスクロールする方法についてまとめておこうとサンプルコードを書き始めたら、これがうまく動作せず、解決までに数時間悩むハメになったので、また同じ目に遭わないよう、トラブル事例として記録しておきます。
大抵の場合はアンカータグで済む
あらかじめ、
<a name="hogehoge">フッター</a>
のような飛び先を用意しておき、
<a href="#hogehoge">[下へ]</a>
のようなアンカーリンクで飛ぶ方法ですね。
実際に下記のリンクをクリックするとこの記事の下のほうへ移動します。
ここはWordPressを使ったブログなので、上のサンプルではページ最下部ではなく、記事の最下部にアンカータグを置いていますが、実際に使うときはテンプレートを書き換えてアンカータグを配置すれば良いでしょう。
あるいは、ページ内リンクはID指定でも行えるため、<div id="footer">~</div>というタグが使われている当ブログのような場合は <a href="#footer">[下へ]</a> と書くだけでも構いません。
■ID=footerまで飛ぶ例
[下へ]
更に言うと、ページ内の最上部へ移動するだけなら <a href="#top">[上へ]</a> だけでOKです。<a name="top"> のようなアンカータグを用意する必要もありません。
これは昔から各ブラウザがそういう独自仕様で動いていたのですが、HTML5以降は標準仕様として決まったため、堂々と使って構いません。
スクロールはしたいがURLは変えたくない場合
先述のとおり、アンカータグによるページ内スクロールはとても便利ですが、URLの末尾に #hogehoge や #top といったURLフラグメントが付く問題があります。
…いや実際には問題どころか、それが付くからこそ、ページ内のここを見て!と#付きのURLを送れる利点も大きいのですが、Webシステムにおいて自前でURLの解析をしていたり、はたまた(ページリロードを伴わずに)動的にIDが動く場合など、#を付けずにページ内移動したいなぁ~というケースもあるんですね。
そういうケースではJavaScriptを使って↓こんな感じでスクロールさせます。
■HTML例
<a href="javascript:window.scrollTo(0, 0);" >[上へ]</a>
■実行例
どうでしょう。ここでクリックした場合は、ページ最上部へ移動するものの #top は付かないですよね?
あ、既に前のサンプルをクリックしていて、URLの後ろに # が付いていた場合はそれが消えたりしませんから、いったん # を消してからお試しください。
ページ最下部へのスクロールは少しややこしい
さきほどの window.scrollTo 関数では飛び先のX座標、Y座標を指定する必要がありました。ページの最上部ならX座標0、Y座標0で固定なため簡単でしたが、最下部となるとちょっと話が変わってきます。
■強引なHTML例
<a href="javascript:window.scrollTo(0, 10000);" >[下へ]</a>
アレコレ小難しいことを考えるのが面倒な人向け。実際ぼくもちょっとしたテストサイトならコレで済ませることもあります。
Y座標が実際のページより大きい分には勝手にブラウザが最大値で止めてくれるので、10000とか指定しちゃう。
■実行例
[下へ]
しかし昨今は無限スクロール※を実装したサイトもありますし、10000pxを超えちゃうかも知れません。じゃあもっと大きな数値にしとく? なんだかそれも気持ち悪い。
そんなわけで、マジメな解説サイトではこんな例を出していることが多いのではないでしょうか。■真面目なHTML例
<a href="javascript:toBottom();">[下]へ</a>
<script>
function toBottom()
{
let elm = document.documentElement;
let y = elm.scrollHeight - elm.clientHeight;
window.scrollTo(0, y);
}
</script>
documentElementのscrollHeightには(表示されていない部分も含めた)ページ全体の高さが入っています。
そして、clientHeightにはウィンドウの高さ、もっと厳密に言うならブラウザで表示している範囲内の高さ、が入っています。
つまり、うちのような縦長長文のブログで、ページ全体の高さが10000pxくらいあった場合、フルHDサイズのモニターでブラウザを最大(フルスクリーン表示)にしても、せいぜい1080pxまでしか表示できません。
このとき、scrollHeightには10000、clientHeightには1080が入っているというわけ。
だから、10000から1080を引いて(scrollHeight - clientHeight)、Y座標8920までスクロールしなさい、としているのです。
やだ~かしこ~い。
■実際の値
documentElement.scrollHeight | |
documentElement.clientHeight |
PCでこのページを見ている場合はブラウザのウィンドウの高さを変えるたびに上の値が変わるのが確認できるかと思います。
な・の・で・す・が・・・・
DOCTYPEによってdocumentElementの仕様が異なる
言い訳ですが、ぼくは業務系のプログラミングが本職で、Web系は趣味でやっているレベルなのでDOCTYPE宣言(ページの最上部に書くアレ)なんておまじない、くらいに考えていました。
特にIE全盛時代に現役バリバリ(死語?)だったため、DOCTYPEはIEの後方互換を保つためのものだろう、と。しかもHTML5になってからは不要だろう、とすら思っていました。
実際、HTML5からはSGML文書ではなくなったため、DOCTYPEは不要である、という記述をどこかで見かけて鵜呑みにしてたんですね。だから自分が作るサイトではDOCTYPEなんて付けてませんでした。
だが、これが間違いであり、HTML5に準拠して書くなら <!DOCTYPE html> の記述は必須だったんです。
このDOCTYPE宣言がないと、Chromeは後方互換モードで動き、documentElementのscroll関連で返す値が変わります。具体的には scrollHeight も clientHeight もページの最大サイズを返してしまうのです。
そんな状況で先ほどのコードを動かしたら、10000 - 10000 = 0となり、ページ最下部どころか、常にページ最上部へ飛ぶコードになってしまうわけですね。
これにハマってハマって午前中一杯つぶしてしまいました…。
自分が書いたコードは動かない、でもGoogle検索でヒットした様々なページのサンプルは動いている。何かスペルミスでもしているのかと思い、別サイトのサンプルコードをコピペして、ローカルで実行してみても動かない。なんだ…なにが違うんだ…。
そういえば昔は document.body.scrollHeight と document.body.clientHeight を使っていた気がする、と思い出し、この記述をすると正常に動作。えぇぇぇ???じゃあ巷にあふれる documentElement.clientHeight は何なんだ…と悩みに悩んでDOCTYPE宣言が違うことにようやく気が付いた次第。いやぁ…お恥ずかしい…。
まとめ
- ページ内の移動にはアンカータグが楽ちん
- #topのようなURLフラグメントを付けたくない場合はJavaScriptのscrollToを使う
- 但し、DOCTYPE宣言によって documentElement.clientHeight が返す値が異なる点は要注意
- 特に理由がなければ <!DOCTYPE html> は付けよう
というところでしょうか。
結局、ページ最下部の移動も scrollTo(0, 100000); みたいな力技のほうがDOCTYPEとか気にしなくて良い分、良かったんじゃねーか…と思えてきた今日この頃でした。
無駄に長くなってしまったので、要素を指定したスクロールについては(覚えていたら)また次回。