はてなエンジニア Advent Calendar 2022 - Hatena Developer Blog の 2023/1/5 の記事です。 id:hogashi です。
3行
- a タグの href 属性に
#top
と書くと、クリックしてページの先頭にスクロールできる、という話をしているのを見て、 - それって仕様なんだっけ、と思って調べたところ、
- 仕様でした
仕様を眺める
MDN の a タグのページにちょろっと書いてあって、 HTML の仕様に定義されているよ、とある <a>: The Anchor element - HTML: HyperText Markup Language | MDN 。
Note: You can use
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#linking_to_an_element_on_the_same_pagehref="#top"
or the empty fragment (href="#"
) to link to the top of the current page, as defined in the HTML specification.
リンク先はここ https://html.spec.whatwg.org/multipage/browsing-the-web.html#scroll-to-the-fragment-identifier で、 "Scrolling to a fragment" という仕様。パーセントデコードした fragment が top
という文字列だったら、 "top of the document" が指定されることになっている。つまり a タグじゃなくても、 URL に #top
と書いたらページ先頭にスクロールできる。
追記: ただし id="top"
な要素がある場合は、そちらにスクロールされます。これは手順の上で先に採用されるためで、この記事の最後に詳しく追記しました。
If decodedFragment is an ASCII case-insensitive match for the string
https://html.spec.whatwg.org/multipage/browsing-the-web.html#scroll-to-the-fragment-identifiertop
, then return the top of the document.
"top of the document" のときどうなるかは https://html.spec.whatwg.org/multipage/browsing-the-web.html#scrolling-to-a-fragment:top-of-the-document に書かれていて、ページの先頭にスクロールするようになっている。
Otherwise, if document's indicated part is top of the document, then:
https://html.spec.whatwg.org/multipage/browsing-the-web.html#scrolling-to-a-fragment:top-of-the-document
Set document's target element to null.
Scroll to the beginning of the document for document. [CSSOMVIEW]
Return.
"Scroll to the beginning of the document" の部分は CSS の仕様 https://drafts.csswg.org/cssom-view/#scroll-to-the-beginning-of-the-document にリンクされていて、その document の viewport のスクロール位置を先頭にするみたいな手順が書かれていて細かくてすごい。その document のということは、 iframe で表示しているときなどは、そのフレーム内の先頭へのスクロールになるわけですね。
ちなみに HTML での #top
の挙動で使われる仕様ですみたいなことが端書きされていて丁寧。
Note: This algorithm is used when navigating to the #top
fragment identifier, as defined in HTML.
https://drafts.csswg.org/cssom-view/#scroll-to-the-beginning-of-the-document
追記1:
Twitterは知見の塊か…
— 長谷川喜洋★星のソムリエ®/HASEGAWA Yoshihiro (@hiro_ghap1) 2023年2月4日
aタグで#topにリンクするとページ先頭にスクロールするのは仕様https://t.co/j67qDBUpYg
それ自体は仕様だけど、クリックしてページ先頭へ遷移後に Safari ではフォーカスを失い、Firefox ではフォーカスの移動がなかった(#top が設定されたaタグのままだった)
勉強になった… https://t.co/CaBRTjQyF9
この記事を見てもらっていたようなのですが、確かにフォーカスなど他の挙動は全く見ていませんでした。 #top
のときページの先頭にスクロールする仕様自体、古いブラウザの実装を互換性のために HTML の仕様に入れているのでは、という話もあるので、この仕様を積極的に利用していくことはあまり推奨されないかもしれません (つまり先頭に戻る用の要素を別途置くのがよい)。
おまけ
ちなみに 「a top MDN」みたいな検索キーだと、今回の話題が全然ヒットしなくて難しかった (CSS の top とか window.top とかが出てきてしまう) ですが、 a タグは「anchor」にしたり、記号込みで完全一致させるために「"#top"」としたりするとうまいことヒットする感じでした。僕はあんまりうまくいかなくて && a タグのページに記述があるの見逃してて、色々試したら stackoverflow が出てきて MDN へのリンクをゲットできた……(たどり着けているので良い)。
ちなみに2、 window.location.hash
が変わったときは hashchange
イベントが発火して便利 Window: hashchange event - Web APIs | MDN 。今回の記事の iframe 部分の URL 表示に使って初めて知りました。
追記2: id="top"
な要素があるときは?
ブコメで予約語という単語を見て、たしかに id="top"
なタグを書くとどうなるのか??? と思って glitch で試しました。id="top"
なタグにスクロールされます。
これは、仕様でいうところの indicated part を決める手順において、そういう要素が見つかったときはそっちが先に採用されるためです (手順4で終了するパターン。先頭に戻るのは手順9)。上のほうではこの手順のひとつだけを抜粋してしまったので紛らわしかったですね……。
For an HTML document document, the following processing model must be followed to determine its indicated part:
https://html.spec.whatwg.org/multipage/browsing-the-web.html#scroll-to-the-fragment-identifier
If fragment is the empty string, then return the special value top of the document.
Let potentialIndicatedElement be the result of finding a potential indicated element given document and fragment.
If potentialIndicatedElement is not null, then return potentialIndicatedElement.
Let fragmentBytes be the result of percent-decoding fragment.
Let decodedFragment be the result of running UTF-8 decode without BOM on fragmentBytes.
Set potentialIndicatedElement to the result of finding a potential indicated element given document and decodedFragment.
If potentialIndicatedElement is not null, then return potentialIndicatedElement.
If decodedFragment is an ASCII case-insensitive match for the string
top
, then return the top of the document.Return null.