Home > Latest topics

Latest topics > タブのツリーを最適な段数で段組表示する

宣伝1。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能! シス管系女子って何!? - 「シス管系女子」特設サイト

宣伝2。Firefox Hacks Rebooted発売中。本書の1/3を使って、再起動不要なアドオンの作り方のテクニックや非同期処理の効率のいい書き方などを解説しています。既刊のFirefox 3 Hacks拡張機能開発チュートリアルと併せてどうぞ。

Firefox Hacks Rebooted ―Mozillaテクノロジ徹底活用テクニック
浅井 智也 池田 譲治 小山田 昌史 五味渕 大賀 下田 洋志 寺田 真 松澤 太郎
オライリージャパン

タブのツリーを最適な段数で段組表示する - Mar 06, 2016

ツリー型タブの最近の改善でやったことをまた地味に解説するよ。誰得な記事。

ツリー型タブには、「ブックマークからタブを複数開いた時に1つのツリーになってた方がいいけど、でもツリーにするには親になるタブが必要で、だからといって同列のブックマークの中で1つだけが親になるのってなんか違和感あるよね」という場面のための「グループ化専用のダミーのタブ」を開く機能がある。というか、about:treestyletab-groupというURIを読み込んだタブはそういう用途のタブとして扱うようになる。そのときタブの内容が空っぽだと味気ないので、そのタブ配下のタブのツリーをタブの内容として表示するようになっている。

(画像:配下のタブのツリーを表示しているタブ)

それとはまた別の話で、ツリーになっているタブの上でしばらく待つとそのタブ1つだけじゃなくて配下タブの名前も一緒にツリー表示して、さらにもう少し待つとそれらがリンクに切り替わってクリックしたらそのタブに切り替えられる、という機能もある。

(画像:ツリーを簡易表示したツールチップ) (画像:リッチな形式でツリーを表示したツールチップ)

見た目で分かるかもだけど、これらのツリーを生成する部分は共通のpseudoTreeBuilder(疑似ツリー)というモジュールになってる。

この疑似ツリーは、ちょっと前までのバージョンでは、タブの数が多くなるとそのままツリーが下に伸びていって、ウィンドウやツールチップの高さに収まらなくなった分はスクロールして見るという仕様になってた。 元々、おまけの機能だからそれほど作り込む必要性も感じてなかったんだけど、未処理のissueがたくさん溜まってたのを断捨離してたらその中に「このツリーをマルチカラム表示して欲しい」という要望があった事に気がついた。

このエントリを書くにあたって改めて調べ直してたら、別のもっと古いissueでは否定的なことを自分で言ってたみたいなんだけど、実際自分で使ってて「長いタイトルが折り返されるという事もなく巨大なツールチップが画面を覆い尽くして、そのくせちびちびスクロールしないと全項目は確認できない」という状況に意外とイライラしていたため、この機会に「じゃあやってみっか」と手を着けてみた。

で、最終的にはどうにかそれらしくマルチカラム表示できるようなった。

(画像:タブの中で疑似ツリーがマルチカラム表示されている) (画像:ツールチップの中で疑似ツリーがマルチカラム表示されている)

基本的にはCSSのマルチカラム機能を使ってるんだけど、ツールチップの方で僕の意図したとおりの結果を得るためにはちょっと工夫が必要だった。

「ツールチップの方で」と限定したのは、後で分かったんだけど、タブの中に表示する方はそこまで苦労しなくても勝手にいい感じにGeckoがレンダリングしてくれるから。 なので先にそっちの方から書いていきます。

コンテナの幅や高さが最初から決まっている場合(コンテンツ領域の中)

先に要件を整理しよう。

  • 項目をコンテンツ領域の中で2~3列くらいで段組表示したい。
  • 個々の列はコンテンツ領域の高さを越えないようにしたい。 スクロールバーを出すのは横方向だけにしたい。

まずコンテナとして、コンテンツ領域いっぱいに広がるボックスを置いておく。 コンテナには、内容が溢れたらスクロールできるようにoverflow:autoも指定しておく。

#tree {
  overflow: auto;
  box-flex: 1;
  -moz-box-flex: 1;
}

で、この中に置くツリーをマルチカラムにしたいわけなんだけど。

CSSのマルチカラム表示を有効にするだけなら、マルチカラムにしたい内容を含む上位の要素に列の数か幅を指定してやればいい。

2列なら、こう。この場合、列の幅はコンテナの半分になる(自動的に決定される)。

#tree > .treestyletab-pseudo-tree-root {
  -moz-column-count: 2;
  /* -moz-columns: 2 auto; と同等 */
}

列幅固定なら、こう。この場合、コンテナの中に1列しか収まらない時はマルチカラムにならないという、いわゆるレスポンシブデザイン的な挙動になる。

#tree > .treestyletab-pseudo-tree-root {
  -moz-column-width: 20em;
  /* -moz-columns: auto 20em; と同等 */
}

列数固定だとウィンドウの幅が広いときに余白ばかり広がってしまってあまり嬉しくないので、ツリー型タブではとりあえず主観で20emと指定してみた。 自分の使ってる環境では、これでだいたい2~3列になる。

マルチカラム表示したいツリーを構成しているXUL要素の中で、内容が複数列に泣き別れてもいい物(=「1つのタブ」に対応する「アイコンとラベル文字列のペア」以外のすべてのボックス)は明示的にdisplay:blockにしないといけない。 display:-moz-boxのままだと、要素の作るボックスは途中でぶった切られる事が無いので、マルチカラム表示にはならない。

/* required to apply CSS multi columns */
#tree > .treestyletab-pseudo-tree-root,
#tree > .treestyletab-pseudo-tree-root vbox {
  display: block;
}

当初はこれに気付いてなくて、わざわざXHTMLとXULを混ぜて実装してた。 でも、後になってからdisplayの値を変えればXUL要素でも問題ない事が分かったので、今は全部XULに戻してある。

問題1:縦に長くなる

しかし、これだけだと項目の数が多い時に「縦に長いツリーがマルチカラムで表示される」「画面に収まらなかった部分は下にスクロールしないと見えない」ということになる。

(画像:縦方向にスクロールバーが出ているのが分かる)

こういうのは一覧性が悪くて死ねる。 だって、左の列を上から順に下まで見た後、次の項目を見るにはまた一番上までスクロールし直さなきゃいけないんですよ? あり得ないでしょ……

つまり、各列がコンテンツ領域の高さより高くなられると困る。 項目の数が多いときは、「縦に長ーいリストを2列で表示する」んじゃなくて、「コンテンツ領域の高さに収まる短いリストを3列、4列と列を増やして表示させる」方がいい。 ということ。

じゃあってんでさっきの要素に最大の高さを100%と指定してみても、これは効果がなかった。相変わらず、親のボックスの大きさを超えてスクロール可能なまま。

#tree > .treestyletab-pseudo-tree-root {
  -moz-columns: auto 20em;
  height: 100%;
}

なので、resizeイベントでコンテナの高さを調べて、その都度ピクセル値で適切な高さを指定するようにしてみたら、ようやく縦のスクロールバーが消えて横方向にスクロールできるようになった。

...
  this.window.addEventListener('resize', this, false);
...
handleEvent : function GT_handleEvent(aEvent)
{
  switch (aEvent.type)
  {
    ...
    case 'resize':
      return this.onResize();
    ...
  }
},
...
onResize : function GT_onResize()
{
  ...
  var container = this.document.getElementById('tree');
  var tree = container.firstChild;
  PseudoTreeBuilder.columnizeTree(tree); // ここでcontainerの高さをtreeのheightにピクセル値で指定し直す。
},

CSSのマルチカラムでは「列の数」の指定は絶対ではなくて、コンテナの幅と高さから溢れた分の内容は自動的に列の数を増やして表示するようになってる。 コンテナがoverflow:autoになっていれば、溢れた分は横スクロールすれば見える。

問題2:下に余白ができる時がある

全体の高さを指定していても、項目の数が「全部1列で表示するには多いが、2列を埋めるには足りない」程度の時には、領域の下の方に妙に余白が出てしまう。

(画像:バランス良く配置されたせいで下の方に余白ができている)

これは-moz-column-fill:balanceという初期値のせい。この指定の時には、各列の内容の高さがなるべく揃うようにレイアウトされるため、結果として下の方に余白が産まれることになる。

まぁそれはそんなに悪くはないんだけど、今までコンテンツ領域の高さいっぱいまで並んでた物が急にその半分とかくらいの高さになるのは気持ち悪い。 違和感が無いのは、やはり、領域の高さに収まらなくなった分からちょっとずつ次のカラムに流し込まれていくという感じだろう。

そのための指定が-moz-column-fill:autoで、これを指定しておくと、1列目は下まで項目が埋まって、入りきらなかった分だけが2列目に溢れるようになる。

(画像:1列目は高さいっぱいまで項目があり、溢れた分だけが2列目に行っている)

実は、この-moz-column-fill:autoを使うのにも、ボックス全体の高さを明示的に制限しておく必要がある。 というのも、-moz-column-fill:autoの時は「高さが指定の高さに収まらなかったときに、その分を次のカラムに流す」という事になるので、高さが未指定だと「内容が溢れることもないからいつまで経ってもマルチカラムにはならず、縦にどんどんリストが伸びていく」という結果になってしまう。 ……というわけなので、やっぱり全体の高さはスクリプトで動的にピクセル値で指定してやらないといけないのでした。

コンテナの幅や高さが決まっていない場合(ツールチップ)

こちらも要件を整理する。

  • ツールチップの中で項目を2~3列くらいで段組表示したい。
  • ツールチップは画面を覆い尽くすほどの大きさにはなって欲しくない。 最大の幅と高さは制限したい。
  • 必要以上の余白は要らない。 小さく表示して問題無いなら、小さく表示したい。

ここで曲者なのが3つ目の要件。 コンテンツ領域の中であれば「与えられた領域の中を最大限使って、残りは余白扱いにすればいい」ということになるんだけど、ツールチップだとそうもいかない。 というか、必要最小限のサイズで出てくれないと困る。 「内容を表示するのに必要な最小の大きさをどうやって決めるのか?」 これがツールチップの場合の難しい所だ。

先に結論を言ってしまうと、「とりあえず許されてる最大の大きさで試しにレンダリングしてみて、その後、実際に必要な大きさで改めて表示し直す」というのが答えになる。

大きく表示してから小さく縮めるのって格好悪くない?

とはいうものの、ツールチップを1回表示した後そこから小さく縮めるというのは見た目が宜しくない(一瞬だけとはいえ目に見える状態で大きなツールチップが出るというのはイラつきますよね)。

じゃあどうすれば?という事になるんだけど、逆転の発想というかなんというか、ツリー型タブでは「そもそも最初からベストのサイズで表示する事は諦める」という解決策をとっている。 どういう事かというと、いきなり違うサイズのツールチップを出すとギャップで見当識を失うので、まずは元の普通のツールチップと同じ大きさ・位置で1回表示して、そこからアニメーションでツールチップを必要な大きさまで拡大する、という事をしてる。

(画像:ツールチップの大きさが拡大される様子のアニメーション)

ツリー型タブの高機能なツールチップはだいたい元のツールチップよりは大きくなるので、この「1回小さめのサイズで表示した」状態で必要なサイズを確定させて、後からゆっくり拡大すればいい。

元より小さなツールチップで何故「最大の大きさで試しにレンダリングしてみる」ができるのかというと、XULのarrowscrollbox要素を使ってるから。 これは元々は、ツールチップが画面いっぱいに広がっていてもまだ内容が収まりきらない時のための対策として使っていた。 arrowscrollboxは中にどんなに大きな物を置いてもそれ自体やその親(ツールチップ)の大きさが広がらないので、安心して「めいっぱい大きく表示して、最小限必要なサイズをその結果から割り出す」ということができる。

(画像:arrowscrollboxの中で大きなツリーが準備されている様子)

ツリーを収めるのに最適なサイズの求め方

「幅も高さもいい感じにする」というのは難しいので、ツリー型タブの場合はまず「画面の全体を覆い尽くさない程度」ということで画面の高さの70%(例えば画面が縦1024ピクセルなら、その7割の717ピクセル)を最大の高さとして、その中でマルチカラム表示の指定を反映させている。

arrowscrollbox > .treestyletab-pseudo-tree-root {
  -moz-columns: auto 20em;
  -moz-column-fill: auto;
  height: 717px; /* ←実際にはその都度計算する */
  max-height: 717px; /* ←実際にはその都度計算する */
}

こうすると、高さについては指定の高さまでに収まって、幅は必要な分だけ自動的に列が増えていくという形で、ツリー全体を収めるのに必要な領域を確定する事ができる。

高さは既に分かっているので、残るは幅なんだけど、これはツリー自体のboxObject.widthclientWidthからは分からない。 というのも、この時点でのマルチカラム表示されたツリーは「列の幅は指定されているが列の数は指定されていないので、要素自身の幅を超えて列が表示されている(可能性がある)」という状態で、要素自体の幅を調べても意味がないのです。 overflow:autoになってる要素の幅を計測しても中身の幅は分からない、というのと同じね。

こういう時の「本来の内容の幅」は、RangeのgetBoundingClientRect()で調べられる。 これは、DOMのRangeが含んでいる範囲の全要素が収まる矩形の位置や大きさを計測してくれるという便利なAPI。 これを使ってツリーの要素の「内容を」選択してそのサイズを測れば、ツリー全体を収めるのに本当に必要な最小のサイズが分かる。

var range = tree.ownerDocument.createRange();
range.selectNodeContents(tree);
var rect = range.getBoundingClientRect();
range.detach();

(図:ツリーの幅の計測基準)

あとは、ツールチップ全体を広げるだけ。 「ツリー全体を収めるのに必要な幅」と「画面の幅さの80%(例えば画面が横1280ピクセルなら、その8割の1024ピクセル)」のどちらか小さい方をツールチップの新しい幅にしてやる。 ツリーはツールチップの中のarrowscrollbox要素の中に置かれているので、ツールチップがツリーよりも小さければ、はみ出た部分はスクロールして見る事ができる。

まとめ

以上、マルチカラムなポップアップをいい感じに表示するためのノウハウの話でした。

実は、ここに書いた内容はmasterのHEADでの話で、解説を書くにあたって調べ直した結果の洗練された内容になってる。 今リリースしてるバージョンでは、そこまで洗練されてない状態の(でもだいたい同じような結果を得られている)コードになってるので、どこが無駄だったのかを洗い出して「こいつ技術力低いなあ!」と笑いものにしてくれていいです。

それにしても、昔なつかしNetscape Communicator 4では、フォルダの中にブックマークが大量にあるときはこんな風に段組表示してくれて一覧性が高くてものすごく便利だったんだけど、Geckoエンジンベースで作り直されたNetscape 6以降や、その流れの先にあるFirefoxには、その機能はついぞ引き継がれなかった。 業を煮やして複数のポップアップを並べることで擬似的に再現できないか?という実験をしてみたこともあったんだけど、不安定でダメだった。

今だったら、このやり方で行けるんじゃないか?という気がする。 というか、SUMOの質問に寄せられてる回答でStylish用のスタイルシートが公開されてて、2つの例のうち1つはCSSマルチカラムでやるようになってたので、間違いなくできる。

ただ、例に挙がってるStylish用のスタイルシートはカラム数が固定されていて、1列でいい時にまで常に2列3列になってしまうという問題がある。 ネスケ4の頃のそれのような使い勝手を実現するには、ここで解説したような細かい調整をやらないといけないんだと思う。 僕自身はやる元気がないので、誰か代わりにやってくれないかなあ……と、実現する見込みの低そうな事をネットの片隅でこっそり呟いてみる次第です。

分類:Mozilla > XUL, , , , , , 時刻:03:52 | Comments/Trackbacks (0) | Edit

Comments/Trackbacks

TrackBack ping me at


の末尾に2014年1月19日時点の日本の首相のファミリーネーム(ローマ字で回答)を繋げて下さい。例えば「noda」なら、「2016-03-06_multicolumn.trackbacknoda」です。これは機械的なトラックバックスパムを防止するための措置です。

Post a comment

writeback message: Ready to post a comment.

2014年1月19日時点の日本の首相のファミリーネーム(ひらがなで回答)

Powered by blosxom 2.0 + starter kit
Home

カテゴリ一覧

過去の記事

1999.2~2005.8

最近のつぶやき

オススメ

Mozilla Firefox ブラウザ無料ダウンロード