HTMLとCSSは多くの問題を解決できるが、JSもまた重要だ
2017年に「Effective前端1:能使用html/css解决的问题就不要使用JS」という記事を読んだ。最初に読んだときは深く共感し、当時まだ不慣れだった多くのテクニックを学べたので、ぜひ一読をおすすめしたい。JavaScriptはほぼ大半の問題を解決できるが、アクセシビリティの観点やパフォーマンス、バンドルサイズの観点から見れば、CSSで解決できるに越したことはないのは確かだ。しかし、「できるだけJSを使わない」ということは「JSを一切使わない」こととは異なり、この両者には明確な違いがある。この記事では、前述の記事を再読しつつ、僕が改善できると考えるポイントをいくつか挙げていく。
:hover を使ったヒント・スタイルの表示
確かに、:hover を使ってユーザーにそのUIコンポーネントが操作可能であることを知らせるのは、フロントエンドエンジニアにとって基本的な常識と言える。元の記事では、:hover を使ってドロップダウンメニューの効果を実現できることについても触れられている。
- Item1
- Item2
ホバー時に display: block に変更し、通常時は display: none にしておく。これは一見何の問題もないように見えるが、もしユーザーがマウスでナビゲーションしていなかったらどうだろうか?キーボードでナビゲーションしている場合、:hover は効果を発揮しない。また、トリガーとなるUI要素とドロップダウンリストが隣接していなければならないなど、DOM構造上の制約も受ける。
したがって僕からの提案は、要素がインタラクティブであることを示すために :hover を使う際には、マウス以外の方法でナビゲーションしているユーザーがどうやって操作するかを考慮することだ。具体的には以下の通りだ:
- 追加で
:focusを指定するか、clickイベントをリッスンしてドロップダウンメニューを開閉できるようにする aria-expandedを付与してスクリーンリーダーにメニューの現在の開閉状態を伝え、さらにキーボードナビゲーションを実装してユーザーが上下キーで項目を選択できるようにする
- Item1
- Item2
この例では、ホバーでドロップダウンをトリガーするだけでなく、JavaScriptを併用して focus や mouseover イベントを監視し、aria-expanded を切り替えている。キーボードナビゲーションは本稿の主旨から外れるため実装していない。ちなみに、aria-expanded を利用する場合、CSSを以下のように調整することもできる:
.dropdown-item:hover + .item,
.item[aria-expanded="true"]
{
/* style */
}
:checked と隣接セレクタによるカスタムスタイル
カスタムチェックボックスやラジオボタンを実装したい場合、記事内で言及されているような、擬似クラスと隣接セレクタを組み合わせるテクニックが必要になるだろう。これによって手軽にカスタムチェックボックスを実装できる。
:checked を使うメリットは、クラスをトグルするために別途イベントリスナーを登録する必要がない点にある。カスタムチェックボックスやラジオボタンを作るなら、<div> でイチから作り直すのではなく、可能な限りこの手法を優先すべきだ。<div> 製のコンポーネントは考慮すべき事項が山ほどある上に、見た目は不恰好でも少なくとも機能する標準のチェックボックスほどアクセシビリティや使いやすさが担保できない場合が多い。
ただし、このアプローチを採用する際にはいくつか注意点がある:
- スクリーンリーダーにそのチェックボックスやラジオボタンの用途を伝えるため、
aria-label(あるいはaria-labelledby)を使用すること。 - 値の変化を通知するために、必要に応じて
<div role="status"></div>などの手法を用いること。 - フォーカス時のスタイル処理を実装すること。
スクリーンリーダーがチェックボックスを読み上げるときは、ラベル名と選択されているかどうかしか読み上げない。もしチェックボックスの目的が単なる選択/未選択ではなく、例えばダークテーマの切り替えなどである場合、別途ヒントや状態通知を追加した方がスクリーンリーダーにとって分かりやすくなる。
input を非表示にしつつフォーカスを受け取れるようにするため、直接 display: none を使うのではなく、別のCSSプロパティを使って視覚的に隠している。同時に、キーボードナビゲーション時にフォーカススタイルが当たるよう :focus-visible を指定した。これにより、Tabキーなどのキーボード操作時のみスタイルが適用され、マウスクリック時には不要なフォーカスのアウトラインが表示されずに済む。
複数カラムの等高化
元の記事は2016年に書かれたものであり、そこで紹介されている手法は機能するものの少しオールドスクールだ。2022年現在ではFlexboxのサポートが十分に普及しているため、直接Flexboxで解決できる。さらに細やかなレイアウト制御が必要ならCSS Gridを使ってもいい。
仕組みとしてはFlexレイアウトの特性を利用しており、align-items の初期値は stretch なので、各アイテムの高さは同一行(row)内で最も高いものに揃う。注意点として、複数行にまたがる必要がある場合は flex-wrap: wrap を忘れずに指定することだ。そうでなければ、デフォルトのFlexboxはすべてを1行の中に詰め込もうとしてしまう。
Information
This photo is taken by Unsplash Clay Banks
Tokyo
This photo is taken by Unplash Jezael Melgoza.
I like Tokyo because it's a such good place where I can see people meltdown
フォームの送信
ネイティブの <form> が何十年も前から存在していることを多くの人が見落としがちだという著者の指摘には、僕も大いに賛同する。<form> を使ってフォーム内容を定義すれば、大量のJavaScriptコードを削減できる。
記事内では、ブラウザ標準のフォームバリデーション機能と :invalid 擬似クラスを組み合わせてスタイルを制御できることが紹介されており、サンプルではinvalid状態のときに送信ボタンのスタイルを opacity: 0.5 にしている。ただ、これはサンプルの都合かもしれないが、著者は <button> ではなく <span> を使っている。実際の実装では <button> を使用し、入力値が不正なときにはJavaScriptを介して disabled を付与するのが適切だろう。
もしformについてまだあまり詳しくないなら、以下の2つの記事を参考にしてほしい:
疑似クラスの活用
著者は :checked、:focus、:invalid などを挙げており、こうした擬似クラスをうまく活用すれば不要なJavaScriptを削減でき、コードの可読性も向上する。
擬似クラスに関しては、比較的新しい擬似クラスを紹介する記事を僕自身も書いているので、興味があれば参考にしてほしい。レイアウトに役立つ疑似クラス
おわりに
2017年はちょうど僕がフロントエンド開発に入門した時期で、細かな仕様への配慮までは手が回っていなかった。今改めて振り返ってみると、優れたユーザー体験を持つUIを実装するには多くのディテールを考慮する必要があり、単にCSSを適用するだけで済む話ではないと気づかされる。とりわけアクセシビリティを考慮する上では、多くの場合JavaScriptが不可欠な役割を担っているのだ。
関連記事
- Three.js で僕の部屋を表現する 僕が React Three Fiber を使って実際の自分の部屋をブラウザ上に再現し、実世界のオブジェクトを目録に見立て、空間の記憶を通してここ数年の生活と仕事について語った話。
- フロントエンドで画像を扱う際に注意すべきこと Jake Archibaldの記事を起点に、現代のレスポンシブ画像の書き方を整理する。なぜwidth/heightを付ける必要があるのか、CSSのaspect-ratioはいつ使うべきか、AVIFとWebPの選び方、そしてpicture/source/srcsetを使ったモバイル向け画像の切り替えについて。
- CSS field-sizing — たった1行のCSSでフォーム要素を自動リサイズする かつてtextareaの自動高さ調整は、JavaScriptでscrollHeightを監視するしかなかった。しかしCSSのfield-sizing: contentなら、わずか1行で代替でき、textarea、input、selectに対応している。本記事では従来のやり方のペインポイントと、field-sizingの使い方をまとめる。
- リンクの下線をもっと見栄え良くする:text-underline-offset デフォルトでは下線と文字が近すぎて、このスタイルを好まないデザイナーもいるし、僕自身もあまり綺麗ではないと感じていた。