Topへ戻る
スクロールすると右下に「↑ Top」ボタンが浮かび上がり、押すと先頭へ滑らかに戻る。
プレビュー
操作枠内をスクロールすると動きます。
仕様書
- 向く場面
- 長い記事・一覧で先頭へ戻る導線が要るページ
- フッターに戻るリンクが無い長尺ページ
- スクロール量に応じて出したい補助操作
- 避ける場面
- 1〜2画面で収まる短いページ
- 右下に他の固定要素(チャット等)がある画面
- 常時表示(目的が伝わらない)
- 構成要素
- 浮遊ボタン
- ラベル(↑ Top)
- 本文
- 持っている状態
- 通常
- ホバー
- キーボード
Tab でボタンへ移動・Enter/Space で先頭へ戻る(button 標準)
- 支援技術
aria-label「先頭へ戻る」。非表示中は visibility:hidden でタブ順・読み上げから外れる。reduced-motion では出現を即時にし scrollTo を behavior:auto にする
- 動き
出現は opacity と translateY 12px を 0.3s ease(消えるときは visibility を 0.3s 遅らせて切る)。先頭へは scrollTo({behavior:smooth})
デザインの要点
- 出すのはスクロール 80px 超から。ファーストビューで見えていると「何のボタンか」が伝わらず、常設の装飾になる
- 非表示は opacity:0 だけで済ませない。visibility:hidden を 0.3s 遅らせて重ねると、フェードは残しつつ Tab と読み上げから外れる
- pointer-events:none を非表示時に必ず付ける。透明なボタンが右下に居座り、その下のリンクの当たり判定を横取りする
- スクロールはブラウザの scrollTo({behavior:smooth}) に任せる。自前のアニメより軽く、reduced-motion では auto に切り替えるだけで済む
使いどころ
長い記事や一覧ページのように、下までスクロールしてから最初に戻りたくなる場面に向く。逆に、1〜2画面で収まる短いページでは出す意味が薄く、常時見えてしまうと『何のためのボタンか』が分かりにくくなる。表示のしきい値(このデモでは80px)はコンテンツの分量に応じて調整すべきで、ファーストビューを出た瞬間に出すと『すぐ現れて邪魔』に感じやすい。モバイルの下部にタブバーやチャットウィジェットなど他の固定要素がある場合は、位置が重ならないよう配置を調整するか、その画面ではこのボタン自体を出さない判断も要る。
仕組み
表示・非表示は display の切り替えではなく opacity と transform(translateY) を transition させる二状態方式で行っている。display: none にすると transition が発火しないため、この実装は常にDOM上に存在させたまま opacity: 0 + pointer-events: none で『見えないが位置は確保されている』状態を作り、.is-visible クラスが付くと opacity: 1 + pointer-events: auto に切り替わる。表示判定は scroll イベントを passive で監視し、scrollTop が80pxを超えたかどうかだけで is-visible をトグルする単純なロジック。クリック時のスクロールは JS でアニメーションループを書かず、documentElement.scrollTo({ top: 0, behavior: 'smooth' }) というブラウザネイティブのスムーズスクロールに任せている。
実装の注意
非表示時に pointer-events: none を付け忘れると、opacity: 0 で見えないだけのボタンが position: fixed のまま画面右下に居座り続け、その真下にある本来クリックしたい要素(リンクやボタン)の当たり判定を横取りしてしまう。この実装はそこを塞いでいるが、コピーして改造するときに見落としやすい点。scroll イベントに { passive: true } を付けていないと、スクロール中に毎回ブラウザがpreventDefaultの有無を確認する必要が生じ、スクロールがカクつく原因になる。また position: fixed の bottom: 20px は、モバイルでアドレスバーが伸縮するブラウザやホームバー(セーフエリア)のある端末では下端に近づきすぎることがあり、bottom: max(20px, env(safe-area-inset-bottom)) のような調整が実務では必要になる。
アクセシビリティ
ボタン要素自体はキーボードでフォーカス可能なので、opacity: 0 で見えないだけの状態だとTabキーでフォーカスが当たり、キーボード・スクリーンリーダー利用者からは『見えない謎のボタン』としてタブ移動の途中に現れてしまう。この実装は非表示時に visibility: hidden を重ねることでタブ順と読み上げツリーの両方から外している。visibility は transition の対象にでき、閉じる側では『opacity 0.3s, visibility 0s linear 0.3s』とフェードが終わってから切り、開く側では visibility を遅延なしで戻すので、フェードの見た目を保ったまま到達不能にできる(display: none では transition が発火しない)。加えて、クリック後にスクロールが先頭へ戻ってもフォーカスはボタンに残ったままなので、実ページでは本文の見出しなど適切な要素へフォーカスを移すと、スクリーンリーダー利用者にも『先頭に戻った』ことが伝わりやすくなる。prefers-reduced-motion: reduce では matchMedia で判定して scrollTo の behavior を 'smooth' から 'auto' に切り替えて瞬間移動にし、表示アニメーションの transition も消している。
調整して使う
しきい値(スクロール量)・位置(右下/左下)・アイコンかテキストか(↑のみ/『Top』併記)が主な調整点。読了までの残量を伝えたいなら、円形のプログレスリングをボタンの周囲に描き足して現在位置を視覚化する応用もできる。フッターに専用の『トップへ戻る』リンクがすでにあるページでは、フローティングボタンは冗長になるので、フッター手前で非表示に戻す(スクロール位置が下端付近ならis-visibleを外す)判断もできる。表示のしきい値は固定px(現在80px)ではなく、ページ全体の高さに対する割合(例えば20%スクロールしたら)にしておくと、極端に長い/短いページが混在するサイトでも体感がそろう。
コード(コピーして使えます)
<div class="btt-hint">↓ スクロール ↓</div>
<div class="btt-filler">
<p>もっと下へスクロールすると<br>右下にボタンが現れます</p>
</div>
<div class="btt-filler btt-filler--end">
<p>ここまで来たら<br>ボタンを押してみて</p>
</div>
<button class="btt-btn" aria-label="先頭へ戻る">↑ Top</button>