Webページのサイドバーをいい感じにstickyする、マウスホイールする!タブフォーカスでスクロールする!SidebarFixerを実装した記録
4-1. 2. の動作を変更しました。(2022/04/10)
僕の開発している Web 文書用のサイドバーをよしなにするスクリプトについてです。同様のスクリプトを実装したい方には結構参考になると思います。
1. はじめに
このブログのテンプレートは僕が2015年から開発を続けています。ブログのサイドバーをスクロールによしなに追従させる SidebarFixer.js を間もなく追加して、機能追加を続けてきました。
当初はスクロールイベントに反応するだけでしたが、マウスホイールイベントでサイドバーだけをスライド出来るようにしました。最後にサイドバー内のフォーカス要素がビューポート内に入るようにしました。
1-1. スペック
項目 | 使用するプロパティとイベント | IE | Presto | Gecko | Webkit |
---|---|---|---|---|---|
CSS-P に使うプロパティ | transform3D (*1), transform (*2) | 9 | ? | ? | ? |
position:fixed | 7 | 8 | 1 | ? | |
position:absolute , position:relative | 5 | 7(*3) | 0.? | ? | |
サポートするイベント | scroll | 5 | 7 | 0.? | ? |
マウスホイール
wheel , mousewheel , MozMousePixelScroll (Gecko 1.9.1+), DOMMouseScroll (Gecko 0.9.7+) | 6 | 9 | 0.9.7 | ? | |
サイドバー内の要素へのフォーカスイベント focusin , focus , DOMFocusIn | 5 | 8 | 0.9.6 | ? |
transform3D
を使わないとアニメーションが著しくガタつく環境があります。Android 3.1transform3D
をサポートするがバグの為に描画が乱れる環境にはtransform
を使います。IE, Chrome 1position:relative
は Opera 7.27 以下用。
1-2. 最新の Chrome で意図しない挙動に遭遇
この木曜日に、最新の Chrome で意図したように動かないことに気づいた時はビビりました。(僕は Firefox をメインのブラウザにしています。)
Chrome と Firefox で focus
イベントに続く scroll
が異なることが直接の原因だったと思います。しかし問題を見逃してきた大元には、場当たり的に機能追加を続けてきた失敗があると考えます。
ビューポート、コンテナ、サイドバーの位置関係の全ての組み合わせを列挙して、もれなく処理を定義して文書化することをサボったのでした。
1-3. コンテナの可視部分について
ようやく腰を据えて、各要素の位置関係のパターンを網羅する図を描きました。この図を眺めながら各イベントに対する処理を文書化して、処理の漏れが無いか確認していきました。
この作業に取り組んだところ、コンテナの可視部分、というキーワードでうまく整理できることが分かりました。
この記事でコンテナとは、メインカラムとサイドバー(サブカラム)を包む <div>
を指します。可視部分とはビューポートのこと。コンテナの可視部分とはビューポートとコンテナの重なり合う部分です。
1-4. 実装にあたって留意事項
ビューポートのリサイズによって、コンテナの高さとサイドバーの高さも変わりうる。またメインカラムとサイドバーの高さの逆転も起こりうる。
2. ドキュメントのスクロールでの動作
position:sticky
っぽい動作をします。サイドバーがコンテナの可視部分より高い場合には、常にサイドバーのどこかしらでサブカラム可視部分を覆うようにスクロールに追従します。
2-1. 動作の詳細
- サイドバーの高さ ≧ メインコンテンツの高さ
- ゼロ位置
- コンテナはビューポートの外
- ビューポートのトップより上ならば、サイドバーはコンテナの地へ
- ビューポートのボトムより下ならば、ゼロ位置
- サイドバーの高さ ≦ コンテナの可視部分の高さ
- サイドバーの天をコンテナの可視部分の天に揃える
- サイドバーの高さ > コンテナの可視部分の高さ
- サイドバーの天がコンテナの可視部分の天より下なら、サイドバーの天をコンテナの可視部分の天に揃える
- サイドバーの地がコンテナの可視部分の地より上なら、サイドバーの地をコンテナの可視部分の地に揃える
- スクロール量 < コンテナのy + サイドバーの高さ - コンテナの可視部分の高さ
- ゼロ位置
- これ以外
- サイドバーの地はコンテナの可視部分の地
2-2. TODO
サイドバーのコンテンツの先頭にページ内索引があってリンク集などが続く場合、なるべく索引をビューポートに入れ続けたい。そこでサイドバーの地がコンテナの地と揃うのは、コンテナの地が画面に入っている時とする。
逆に、サイドバーのコンテンツに軽重が無い場合、素早くサイドバー全体を見渡せるように先の動作となる。(4.4.3~4.4.4)
3. サイドバー上でのマウスホイールでの動作
文書自体のスクロールをキャンセルして、ホイールスクロール量分だけサイドバーだけがスライドします。文書をスクロールしなくてもサイドバーを見渡すことが出来るので、サイドバーにページ内索引等のメインカラムと連携するコンテンツがある場合には、特に活きる機能です。
3-1. 動作の詳細
- サイドバーの高さ ≦ コンテナの可視部分の高さ
mousewheel
イベントをキャンセルしない
- サイドバーの高さ > コンテナの可視部分の高さ
- サイドバーの天の最小値は、コンテナの可視部分の地 - コンテナのy - サイドバーの高さ
- サイドバーの天の最大値は、コンテナの可視部分の天 - コンテナのy
4. Tabキーでのリンク要素へのフォーカス移動
サイドバー内のリンク要素等への focus
イベントに反応して、要素が画面内に入るように調整します。フォーカスを得た要素が画面外にある場合に起る自動スクロールはブラウザ間で異なりますが、これを共通のルールで調整することで文書の閲覧性を一定にします。
4-1. 動作の詳細
- コンテナはビューポートの外(
focus
イベント直後のスクロールが起きていないケース、未確認)- ビューポートの高さ ≦ サイドバー下のフォーカスを得た要素の高さなら、コンテナのyにスクロールする
- コンテナはビューポートの上側なら、コンテナの地 - 要素の高さにスクロールする
- コンテナはビューポートの下側なら、コンテナのy + 要素の高さ - ビューポートの高さにスクロールする
window.scroll()
の呼び出しに対して、同期か非同期でスクロールイベントが起きる場合を想定したコードにする。 - サイドバーの高さ ≦ コンテナの可視部分の高さ
可視部分の天と要素の天を合わせるサイドバーの天をコンテナの可視部分の天に揃える(2022/04/10)
- サイドバー下のフォーカスを得た要素の高さ ≦ コンテナの可視部分の高さ
- 要素の天をコンテナの可視部分の天に合わせると、サイドバー下に隙間が出来る場合、可視部分の地と要素の地を合わせる
- 完全に入っている場合
- サイドバー上に隙間が出来る場合、可視部分の天と要素の天を合わせる
- これ以外の場合、何もしない
- 要素の天がコンテナの可視部分に入っている場合、可視部分の地と要素の地を合わせる
- 要素の地がコンテナの可視部分に入っている場合、可視部分の天と要素の天を合わせる
- 要素はコンテナの可視部分より上、可視部分の天と要素の天を合わせる
- 要素はコンテナの可視部分より下、可視部分の地と要素の地を合わせる
- これ以外
- コンテナの可視部分の天と要素の天を合わせる
4-2. リンクへのフォーカスを Vivaldi で有効化する
Chronium ベースのブラウザである Vivaldi はデフォルトでリンク要素へのフォーカス移動が無効になっています。
ウェブページ > ウェブページのフォーカス > すべてのコントロールとリンクをフォーカスする にチェックを付けます。
4-3. リンクへのフォーカスを Safari 3.1.2 で有効化する
Safari 3.1.2 はデフォルトでリンク要素へのフォーカス移動が無効になっています。
編集 > 設定 > 詳細 > Tab キーを押したときに Web ページ上の各項目を強調表示 にチェックを付けます。
5. その他の動作
- シングルカラムに変化したのを検出してサイドバーの CSS-P をリセットする
resize
イベント- スクロール位置を再取得してサイドバーの位置を調整する
window.onblur
イベント- 何か処理が必要か?不明