hogashi.*

日記から何から

aタグで#topにリンクするとページ先頭にスクロールするのは仕様

 はてなエンジニア Advent Calendar 2022 - Hatena Developer Blog の 2023/1/5 の記事です。 id:hogashi です。

3行
  • a タグの href 属性に #top と書くと、クリックしてページの先頭にスクロールできる、という話をしているのを見て、
  • それって仕様なんだっけ、と思って調べたところ、
  • 仕様でした
こういうやつ

クリックしてこのページの先頭にスクロールする

<a href="#top">クリックしてこのページの先頭にスクロールする</a>
仕様を眺める

 MDN の a タグのページにちょろっと書いてあって、 HTML の仕様に定義されているよ、とある <a>: The Anchor element - HTML: HyperText Markup Language | MDN

Note: You can use href="#top" or the empty fragment (href="#") to link to the top of the current page, as defined in the HTML specification.

https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#linking_to_an_element_on_the_same_page

 リンク先はここ 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 top, then return the top of the document.

https://html.spec.whatwg.org/multipage/browsing-the-web.html#scroll-to-the-fragment-identifier

 "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:

  1. Set document's target element to null.

  2. Scroll to the beginning of the document for document. [CSSOMVIEW]

  3. Return.

https://html.spec.whatwg.org/multipage/browsing-the-web.html#scrolling-to-a-fragment:top-of-the-document

 "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:

 この記事を見てもらっていたようなのですが、確かにフォーカスなど他の挙動は全く見ていませんでした。 #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:

  1. Let fragment be document's URL's fragment.

  2. If fragment is the empty string, then return the special value top of the document.

  3. Let potentialIndicatedElement be the result of finding a potential indicated element given document and fragment.

  4. If potentialIndicatedElement is not null, then return potentialIndicatedElement.

  5. Let fragmentBytes be the result of percent-decoding fragment.

  6. Let decodedFragment be the result of running UTF-8 decode without BOM on fragmentBytes.

  7. Set potentialIndicatedElement to the result of finding a potential indicated element given document and decodedFragment.

  8. If potentialIndicatedElement is not null, then return potentialIndicatedElement.

  9. If decodedFragment is an ASCII case-insensitive match for the string top, then return the top of the document.

  10. Return null.

https://html.spec.whatwg.org/multipage/browsing-the-web.html#scroll-to-the-fragment-identifier