レスポンシブなスライド アウト サイドナビを構築する方法の基本的な概要
この記事では、レスポンシブでステートフルなウェブ用の Sidenav コンポーネントをプロトタイプ作成する方法を紹介します。このコンポーネントは、キーボード ナビゲーションをサポートし、JavaScript の有無にかかわらず動作し、ブラウザ間で動作します。デモをお試しください。
動画でご覧になりたい場合は、こちらの YouTube 版をご覧ください。
概要
レスポンシブ ナビゲーション システムの構築は難しいものです。キーボードを使用しているユーザーもいれば、高性能なデスクトップを使用しているユーザーもいます。また、小型のモバイル デバイスからアクセスしているユーザーもいます。すべての訪問者がメニューを開閉できるようにします。
ウェブ戦術
このコンポーネントの調査では、いくつかの重要なウェブ プラットフォーム機能を組み合わせることができました。
私のソリューションにはサイドバーが 1 つあり、540px
以下の「モバイル」ビューポートでのみ切り替わります。540px
は、モバイルのインタラクティブ レイアウトとパソコンの静的レイアウトを切り替えるためのブレークポイントになります。
CSS :target
疑似クラス
1 つの <a>
リンクは URL ハッシュを #sidenav-open
に設定し、もう 1 つは空(''
)に設定します。最後に、ハッシュに一致する id
を持つ要素があります。
<a href="#sidenav-open" id="sidenav-button" title="Open Menu" aria-label="Open Menu">
<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu"></a>
<aside id="sidenav-open">
…
</aside>
これらのリンクをクリックすると、ページ URL のハッシュ状態が変更され、疑似クラスを使用してサイドナビの表示と非表示を切り替えます。
@media (max-width: 540px) {
#sidenav-open {
visibility: hidden;
}
#sidenav-open:target {
visibility: visible;
}
}
CSS グリッド
以前は、絶対位置または固定位置のサイドナビ レイアウトとコンポーネントのみを使用していました。ただし、Grid では grid-area
構文を使用して、複数の要素を同じ行または列に割り当てることができます。
スタック
プライマリ レイアウト要素 #sidenav-container
は、1 行 2 列のグリッドで、それぞれ stack
という名前が付けられています。スペースが制約されている場合、CSS は <main>
要素のすべての子を同じグリッド名に割り当て、すべての要素を同じスペースに配置してスタックを作成します。
#sidenav-container {
display: grid;
grid: [stack] 1fr / min-content [stack] 1fr;
min-height: 100vh;
}
@media (max-width: 540px) {
#sidenav-container > * {
grid-area: stack;
}
}
メニューの背景
<aside>
は、サイド ナビゲーションを含むアニメーション要素です。このタグには、ナビゲーション コンテナ <nav>
([nav]
という名前)と、メニューを閉じるために使用されるバックドロップ <a>
([escape]
という名前)の 2 つの子があります。
#sidenav-open {
display: grid;
grid-template-columns: [nav] 2fr [escape] 1fr;
}
2fr
と 1fr
を調整して、メニュー オーバーレイとそのネガティブ スペースの閉じるボタンに最適な比率を見つけます。
CSS 3D 変換とトランジション
レイアウトがモバイル ビューポート サイズで積み重ねられるようになりました。新しいスタイルを追加するまでは、デフォルトで記事がオーバーレイ表示されます。次のセクションでは、以下のような UX を目指します。
- 開閉のアニメーション
- ユーザーが同意した場合のみ、モーションでアニメーション表示する
- キーボード フォーカスがオフスクリーン要素に入らないように
visibility
をアニメーション化する
モーション アニメーションの実装を開始するにあたり、アクセシビリティを最優先に考えています。
ユーザー補助の動き
スライドアウト モーション エクスペリエンスを望むユーザーばかりではありません。このソリューションでは、メディアクエリ内の --duration
CSS 変数を調整することで、この設定が適用されます。このメディアクエリ値は、モーションに関するユーザーのオペレーティング システムの設定を表します(利用可能な場合)。
#sidenav-open {
--duration: .6s;
}
@media (prefers-reduced-motion: reduce) {
#sidenav-open {
--duration: 1ms;
}
}
これで、サイドナビが開閉するときに、ユーザーがモーションの低減を希望している場合は、モーションなしで状態を維持しながら、要素をすぐにビューに移動できます。
移行、変換、翻訳
Sidenav out(デフォルト)
モバイル デバイスでサイドナビのデフォルトの状態をオフスクリーン状態にするため、transform: translateX(-110vw)
を使用して要素を配置します。
サイドナビの box-shadow
が非表示のときにメインのビューポートに表示されないように、通常のオフスクリーン コード -100vw
に 10vw
を追加しました。
@media (max-width: 540px) {
#sidenav-open {
visibility: hidden;
transform: translateX(-110vw);
will-change: transform;
transition:
transform var(--duration) var(--easeOutExpo),
visibility 0s linear var(--duration);
}
}
Sidenav in
#sidenav
要素が :target
と一致したら、translateX()
の位置をホームベースの 0
に設定し、URL ハッシュが変更されたときに、CSS が要素を -110vw
の外側の位置から var(--duration)
を経由して 0
の内側の位置にスライドさせる様子を観察します。
@media (max-width: 540px) {
#sidenav-open:target {
visibility: visible;
transform: translateX(0);
transition:
transform var(--duration) var(--easeOutExpo);
}
}
移行の可視性
このメニューが画面外にあるときにスクリーン リーダーから隠し、システムが画面外のメニューにフォーカスを当てないようにすることが、今回の目標です。これを実現するために、:target
が変更されたときに可視性のトランジションを設定します。
- 表示する際は、可視性を切り替えないでください。すぐに表示して、要素がスライドインしてフォーカスを受け入れるのを確認できるようにしてください。
- アウト時は、可視性を遷移させますが、遅延させることで、アウト遷移の最後に
hidden
に切り替わります。
ユーザー補助機能の UX の強化
リンク
このソリューションは、状態を管理するために URL を変更することに依存しています。当然ながら、ここでは <a>
要素を使用する必要があります。そうすることで、アクセシビリティ機能が無料で利用できるようになります。インタラクティブな要素に、意図を明確に伝えるラベルを付けましょう。
<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu"></a>
<a href="#sidenav-open" id="sidenav-button" class="hamburger" title="Open Menu" aria-label="Open Menu">
<svg>...</svg>
</a>
これで、マウスとキーボードの両方で、主な操作ボタンの意図が明確に示されるようになりました。
:is(:hover, :focus)
この便利な CSS 関数疑似セレクタを使用すると、ホバー スタイルをフォーカスと共有することで、ホバー スタイルをすばやく包括的に設定できます。
.hamburger:is(:hover, :focus) svg > line {
stroke: hsl(var(--brandHSL));
}
JavaScript を追加する
escape
を押して閉じます
キーボードの Escape
キーでメニューを閉じられるはずですよね?これを配線しましょう。
const sidenav = document.querySelector('#sidenav-open');
sidenav.addEventListener('keyup', event => {
if (event.code === 'Escape') document.location.hash = '';
});
ブラウザの履歴
開閉操作によってブラウザの履歴に複数のエントリが追加されるのを防ぐため、次の JavaScript をインラインで閉じるボタンに追加します。
<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu" onchange="history.go(-1)"></a>
これにより、閉じたときに URL 履歴エントリが削除され、メニューが開かれなかったかのように動作します。
フォーカス UX
次のスニペットは、開閉ボタンが開閉した後にフォーカスを当てるのに役立ちます。切り替えを簡単にしたい。
sidenav.addEventListener('transitionend', e => {
const isOpen = document.location.hash === '#sidenav-open';
isOpen
? document.querySelector('#sidenav-close').focus()
: document.querySelector('#sidenav-button').focus();
})
サイドナビが開いたら、閉じるボタンにフォーカスします。サイドナビが閉じたら、開くボタンにフォーカスします。これを行うには、JavaScript で要素の focus()
を呼び出します。
まとめ
私がどのようにして達成したかをご理解いただけたかと思います。では、あなたならどうしますか?これは、楽しいコンポーネント アーキテクチャになります。スロット付きの最初のバージョンを作成するのは誰ですか?🙂
アプローチを多様化し、ウェブで構築するすべての方法を学びましょう。Glitch を作成して、そのバージョンを ツイートしてください。下のコミュニティ リミックス セクションに追加します。
コミュニティ リミックス
- カスタム要素を使用した @_developit: デモとコード
- @mayeedwin1(HTML/CSS/JS を使用): デモとコード
- @a_nurella によるグリッチ リミックス: デモとコード
- @EvroMalarkey(HTML/CSS/JS を使用): デモとコード