本連載は分散型マイクロブログ用ソフトウェアMisskeyの開発に関する紹介と、関連するWeb技術について解説しています。
今回はMisskeyのWebクライアント
なお、
Misskey Webの設計思想
Misskey Webの設計思想として、
“同じように使える”
現在はこのような設計思想で開発していますが、Misskey初期の頃は、デスクトップ版Webクライアントとモバイルデバイス向けWebクライアントを別々に作っていました。これにはメリットもあるのですが、コンポーネントを共通化できないデメリットのほうがはるかに大きく、しばしば
そこで、デスクトップでもモバイルでも共通で使えるユニバーサルなUIに統一するという設計に変更ました。これはレスポンシブデザインとも言われますが、後ほど紹介するとおりMisskey Webではそれよりも深いレベルで統合を行っています。
UIコンポーネント
実際に個々のコンポーネントの一部を紹介します。
パネル
パネルは、基本的なコンポーネント

パネルはグリッド状に置かれることもあります。

なお、パネル内にあるパネルは背景色が同化することを防ぐため交互に色が変わるようにしています。しかし現在のMisskeyのクライアントでは、パネル単体の中に別のパネル単体があるケースは存在しないため、具体例の紹介はできません。ただし後述のように、別のコンポーネントの一部としてパネルが使われる場合に、パネルが入れ子になるケースがあります。
ボタン
ボタンは基本的な要素のひとつで、クリックすることにより何らかの操作を行うものです。

Misskeyでは通常のボタンに加え、以下のバリエーションがあります。
- primary:テーマカラーの単色塗り潰し。何かの
「決定」 や 「保存」 など - gradate:テーマカラーのグラデーション。primaryの中でもさらにボタンを強調したい場合
(アカウント作成など) - danger:赤文字。何かを削除するときなど
- rounded:角丸
特に使い分けが決まっているわけではなく、使われる場所に合うものを選んでいます。たとえば、次のボタンはroundedが設定されています。

フォルダ
フォルダは内容を折りたたむことができ、内容を表すテキストをヘッダーとして持つことができるコンポーネントです。内容の部分は前述したパネル扱いになります。

ヘッダー部分はスクロールに追従します。このようなUIをWebではSticky Headerと呼びます。

フォルダを入れ子にすることもできます。パネルであるためこの場合も背景色が交互に代わり、背景と同化しないようになっています。

フォルダ内では、複数のパネルを持つことができます。

ヘッダーにはサフィックスとして簡単なステータスなども表示できます。

このように、画面の情報量を少なく保ったまま必要な情報を詰め込めるため、特に設定画面などで多用されるコンポーネントになっています。
ツールチップ
ツールチップは、ボタンの説明などを表示することに使われるコンポーネントです。

ただ、モバイルデバイスとの相性が良くないため、できるだけツールチップなしでも操作に困らないUI設計を心がけています。
ポップアップメニュー
ポップアップメニュー
デスクトップのポップアップメニューは次のように表示されます。

モバイルでは、いわゆるドロワーメニューと呼ばれるデザインになります。

あるコンポーネントを右クリックすることで、ポップアップメニューが出現する場合があります。その場合には
というのも、スマートフォンやタブレットでは右クリックの概念がないからです
また、メニューはネストする

画面右側に表示するスペースがない場合は左側に表示されます。

削除などの操作時にワンクッション入れる目的でも使用しています。

メニューの背景色がパネルよりも明るいことに気づいたでしょうか。こうすることでコンポーネントの前後関係、重なりをわかりやすくしています。
セレクトボックス
セレクトボックスはメニューと同様に、デスクトップとモバイルでデザインが異なります。
デスクトップのセレクトボックスはプルダウンして表示されます。

モバイルではドロワーで表示されます。

テキストボックス
テキストボックスのコンポーネントです。

背景がパネルである場合、ボーダーを表示して同化しないようにしています。

ネストされたパネルと同様に背景色を変えても良いのですが、ライトテーマで白背景の場合、こういったフォームで使われるコンポーネントが灰色だとグレーアウト
ダイアログ
アイコン、テキスト、選択肢を設定可能なモーダルダイアログです。

ダイアログの外側をクリックして閉じることもでき、その場合はキャンセルが選択されたものとして扱います。
ウィンドウ
Misskey Webでは各種ページをアプリ内ウィンドウで開くことができます。たとえば、ページ遷移を発生させずに、ユーザー情報を確認するといったことが可能です。
以前の記事で少し触れましたが、それぞれのウィンドウを別々にルーティングするために、Vue標準のルーターは使わずに自前でルーターを用意しています。

ウィンドウは辺をドラッグしてサイズを変更したり、最大化するためのボタンがあったりと、一通りのインタラクションを行えます。
iframeを使っているわけではなく、実際にそこにDOMを展開しているため、ウィンドウ内で表示されたポップアップやツールチップはウィンドウの外に飛び出して表示されることも可能です。
設定メニュー
設定メニューは、設定画面などで用いられるナビゲーションです。
デスクトップの場合は左に表示されます。

モバイルでは全画面でグリッド状に表示されます。

タブ
タブはページ上部に表示されます。タイムラインの種類を切り替えるなど、ページ内容を複数の選択肢の中から選べる場合に使用されます。

タブにはアイコンだけ表示し、ツールチップで完全なタイトルを表示するなど、画面の幅が狭い場合でも見やすいように工夫しています。それでも収まらない場合は横スクロールできるようになっています。
論理コンポーネント
目に見える形でのデザインを持たないコンポーネントを論理コンポーネントと呼んだりします。
そのひとつがSticky Containerというコンポーネントで、これは

単純なSticky headerであれば、CSS標準の機能で事足りますが、上記画像のように複数のSticky headerがネストされていて、それぞれが重ならずにスクロールに追従するようなUIを実現するためには一工夫必要になります。
そこで用意されたのがSticky Containerで、このコンポーネントでは内部的なスクリプトによってヘッダーが適切な位置に固定されるように設定します。
テーマ
Misskeyにはダークモードにも対応したテーマ機能があり、ユーザーが見た目を自由に変更できるようになっています。
テーマは以下のような形式で定義されていて、標準でインストールされているテーマのほかにも、サードパーティ製のテーマをインストールすることも可能になっています。

ウィジェット
Misskeyではウィジェットと呼ばれる様々な種類のコンポーネントをクライアント上に自由に配置することができ、カスタマイズ性の高いインターフェイスを実現しています。

ウィジェットの種類は豊富ですが、それぞれのコンポーネントはdynamic importで読み込んでいるため、使っていないウィジェットは読み込まれず軽量化が実現されています。
Storybook
Misskeyの開発においては、Storybookを導入しています。各種コンポーネントの表示を簡単に確認できるようになっているほか、表示の差分を検出して、コードを変更した際に表示が意図したものになっているかのチェックをするようにしています。
具体的な解説や紹介を行うと長くなってしまうので、Storybookについては別の機会に単独の記事として取り上げたいと思います。
まとめ
今回はMisskey WebにおけるUIデザインの工夫や各種コンポーネントについて紹介しました。
UIを設計する上では、レスポンシブ対応、アクセシビリティ、ダークモードなど考慮すべきことが多いですが、UXに直結する部分ですので丁寧な実装が求められます。
Misskeyでは採用していませんが、最近はTailwindなど便利なCSSフレームワークも増えていますので、開発者の学習コストが増えるというデメリットもありますがそれらを採用するのも選択肢のひとつです。
今回紹介したものは一部ですので、また機会があればMisskeyのUIについて取り上げたいと思います。