Home > Latest topics

Latest topics > Split Browser開発のよもやま話(8):ポップアップボタンの実装

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

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

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

Split Browser開発のよもやま話(8):ポップアップボタンの実装 - Feb 02, 2007

最初のリリース前の作り込みの話の続き。最終段階、ブラウズ領域の上下左右のポップアップボタンの実装について。このエントリは、XULにおける要素の重ね合わせ表示の方法に関する話が色々含まれています。多分。

実は当初は、こんな機能を付けるつもりは無かった。Split Browserのコンセプト自体が半分冗談みたいなものだったから、そこまで深く考えてなかったし。

ただ、実際に基本的な機能が揃って形になってくると、コンテキストメニューから「ブラウザを分割」を選んでさらにサブメニューから上下左右を選択する、という操作がだんだん鬱陶しくなってきた。と同時に、これをどうにかすることができれば使い勝手の面でブレイクスルーになるのではないか、という事も漠然と思うようになってきた。その最も安直な解決策として思い付いたのが、分割可能な領域の端にポインタが近付いたら勝手にボタンを表示して、それをクリックしたらその方向にそのブラウザを分割する、というものだったワケです。

ただ、思い付いたはいいものの、どうやって実装するかでだいぶあっちに行ったりこっちに行ったりウロウロしてた。

最初思い付いたのは、バインディングでの匿名内容の定義において水平・垂直それぞれのコンテナの両端に細長いボタンを付けておき、必要に応じて表示・非表示を切り替えるというものだった。


<content>
  <xul:vbox>
    <xul:button anonid="split-to-up"/>
    <xul:vbox anonid="vContainer">
      <xul:hbox>
        <xul:button anonid="split-to-left"/>
        <xul:hbox anonid="hContainer">
          <xul:subbrowser/>
        </xul:hbox>
        <xul:button anonid="split-to-right"/>
      </xul:hbox>
    </xul:vbox>
    <xul:button anonid="split-to-below"/>
  </xul:vbox>
</content>

実際にこんな感じにバインディングの定義を変更して使い勝手を見てみたんだけど……正直、微妙っていうか、こりゃダメだという感想だった。

まず、この構造だと、ボタンが表示される瞬間にそれより内側の内容がガクッと動いてしまう。これを回避する手段として、タブのドラッグ&ドロップ時にドロップ位置を示す矢印が他の部分に重なって表示されるようになっているのと同じ手法を使って、ボタンをその内側のブラウズ領域に重ねて表示するようにしてみた。

Webページのレイアウトでは、ネガティブマージン(marginに負の値を設定すること)による要素の重ね合わせはよく見掛けテクニックだけど、XULでそれを試しても普通は全然効果が無い。実は、XULでネガティブマージンを機能させるにはちょっとしたコツが必要だ。


button[anonid="split-to-up"] {
  position:relative;
  height: 10px;
  margin-bottom: -10px;
}

このように、position:relativeを指定し、ネガティブマージンの値を同じ幅または高さを設定することで、やっと、そのXUL要素が隣のXUL要素に重なって表示されるようになる。

しかし今回はこれでは全然役に立たなかった。通常、ネガティブマージンを指定された要素は隣の要素に重なって表示されるようになるんだけど、browser要素などのインラインフレームが隣り合うと、ネガティブマージンを指定された要素がフレームの内容の上に重なるのではなく、下に潜ってしまうんだ。

しかし仮にその点がクリアされたとしても、もっと根本的な所で使い勝手が悪いことにも気がついていた。よくよく考えれば分かることなんだけど、これだと、ブラウザを分割した時に「元からあったtabbrowser」と「新しく追加されたsubbrowser」との間ではなく、subbrowserよりも「向こう」の方にボタンが表示されてしまう。


<xul:button anonid="split-to-left"/>
<xul:hbox anonid="hContainer">
  <xul:tabbrowser/>
  <xul:subbrowser-container/> (追加されたsubbrowser)
</xul:hbox>
<xul:button anonid="split-to-right"/>

分割後のボックスの並びはこんな感じになる。ってことは、この例で「追加されたsubbrowser」と「tabbrowser」の間に新しくsubbrowserを追加するためには、「追加されたsubbrowser」よりも外側に表示されたボタンをクリックしないといけなくなるわけだ。これは全く直感的でない。

……というわけで、ここまでで書いた内容の実装は丸々ボツにして、別の方法を模索することにした。

次に思ったのは、自由に好きな位置に配置できる要素を使って、ポインタのある位置に動的にボタンを表示するというアプローチ。この場合、popup要素のようにそれ用の要素を使ってやる方法と、それ以外の普通のボックスを無理矢理(無理矢理という程でもないけど)ブラウズ領域の上に配置する方法の二通りが考えられる。

popup要素ノードは、showPopup()メソッドを使うとスクリプト制御によって好きなタイミング・位置でそのポップアップを表示させることができる。Second Searchでもこの機能を使ってキー入力に応じてポップアップを表示させている。一応解説しておくと、以下のような感じ。

  • 第2引数に座標のX、第3引数に座標のYを与えてやれば、その位置を基点にしてポップアップが表示される。
  • 第1引数に要素ノード、第2引数と第3引数に-1を与えてやれば、第1引数の要素ノードの位置を基点にしてポップアップが表示される。この時、第5引数と第6引数でポップアップの位置を制御できる。
    • 第5引数にはtopleft, topright, bottomleft, bottomrightのいずれかを文字列で渡す。それぞれ、第1引数の要素ノードのボックスの4つの角に対応している。
    • 第6引数も同様に、topleft, topright, bottomleft, bottomrightのいずれかを文字列で渡す。こちらは表示されるポップアップのボックスの4つの角に対応している。
    • 第5引数と第6引数のそれぞれの点が重なるようにポップアップが開かれる。例えば第5引数にbottomleft、第6引数にtopleftを指定すると、ドロップダウンリストのように、ポップアップの左上の角が要素ノードの左下の角に接するような形でポップアップが開かれる。
  • 第4引数には、popup, menupopup, tooltipのいずれかを文字列で渡す。どれを渡すかによってポップアップの挙動が微妙に変わるので注意。

ちなみに、このメソッドはtooltip要素でもmenupopup要素でも使える。

しかし、これにもいくつか問題がある。

  • popup要素によるポップアップは実際に表示される位置がズレることがある。ポップアップメニューは基本的に画面内に表示されるようになっていて、画面の外にはみ出しそうなときは勝手に別の位置に表示されるんだけど、そのせいで、狙った位置にポップアップが出てくれないことがある。(しかも、ウィンドウの位置を変えても表示位置が元に戻らなかったりして……)これの原因は、どうも、document.popupNodeに何らかの要素ノードが入っているかどうかによってshowPopupの座標指定の原点が変化するかららしい。document.popupNodeが空の場合はスクリーンの左上が原点となり、要素ノードが入っている場合はdocument.documentElementの座標が原点になる……みたい? showPopupを呼ぶ時には、document.popupNodeに常に何らかの値が入っているようにするか、逆に、常にnullにするか、どちらかに統一しておくと楽だと思う。僕の場合は今では、コンテキストメニューのようにdocument.popupNodeの値を見て挙動を変えるメニューがあることも考えて、showPopupを呼ぶ前にはdocument.popupNodeに常に何か要素を入れる方で統一している。
  • 一度に複数のポップアップを表示させた時の挙動がイマイチ怪しい。ウィンドウをクリックすると普通なポップアップは勝手に閉じられるけど、二つ以上ポップアップを同時に開くと最後に開いた奴しか閉じてくれなかったりする。hidePopup()メソッドで手動で閉じることもできるけど。

実際、Tab Catalogでも当初はpopupを使って表示するつもりだったんだけど、上記のような問題に悩まされたので諦めてしまった。その時代わりに使ったのが、Split Browserでも採用していて、最後に紹介する、CSSのポジショニングを使ったやり方だった。

CSSのポジショニングでの方法を紹介する前にもう一つだけ他の方法も紹介しておくと、stack要素を使う方法というのもある。詳しくはリンク先を見ての通りなんだけど、stack要素の中に置いた要素ではleft属性とtop属性で好きな位置に要素を移動できるんだそうな。今回の目的を考えると、これが一番適した方法に思える。

ただ、この方法を採る場合は当然ながら、要素構造をいじらないといけない(ポップアップを表示したい範囲全体をstackの中に入れる必要がある)。CSSのポジショニングを使ったやり方には、stackを使ったやり方と同じような使い勝手で使えて、要素構造をいじらなくても済むという利点がある。


<vbox id="splitbrowser-add-button-container"
         style="
           position : fixed;
           top      : 0;
           left     : 0;
           z-index  : 32000;
         ">
  <toolbarbutton id="splitbrowser-add-button"
         oncommand="SplitBrowser.onAddButtonCommand(event);"
         hidden="true"
         style="
           position : fixed;
           z-index  : 65000;
         "/>
</vbox>

HTMLでポジショニングというとposition:absoluteを使う場合が多いと思うんだけど、XULの要素ではposition:fixedを使う。これで要素がウィンドウ内で絶対配置されるようになるので、あとはtopleftで好きな位置に移動する(スクリプトで制御する場合は要素ノードのstyle.leftstyle.topにアクセスすることになる)。なんでボタン自体にこのスタイル指定をせずにわざわざボックス要素を使っているのかというと、ぶっちゃけ、こうしないとちゃんと表示されなかったから。場合によっては別にこんな事しなくても大丈夫みたいなんで、そこはまあ、トライアンドエラーで試してみてねと。

なお、このvbox#splitbrowser-add-button-containerは、オーバーレイによってブラウザウィンドウの末尾に挿入されている。ポジショニングを使う場合は親要素がポジショニングで配置し直されていなければ別にどこに置いても大丈夫だ。

後はもう話は簡単で、mousemoveイベントをブラウズ領域の上で拾うようにして、内容領域の端の方に来たらボタンのhidden属性を外す、ただそれだけ。まあ、反応するエリアの大きさを上下左右辺の真ん中あたりに絞るとか(これは後のバージョンで加えた処理)、使い勝手のためにいくつか工夫はしてるけど。イベントハンドラは、要素のネストを考慮するのが面倒だったので、tabbrowser要素のコンテナになる要素とsubbrowser要素にバインディングで指定しておくことにした。

ここまででいちおう予定していた機能+アルファが全部揃ったので、バージョン0.1として公開した。ここまでで、作業時間は約2日。手の遅い自分にしてはなかなか頑張った方ではないだろうか。

とまあこんな感じの作業を最初のリリースまでの間にやっていた訳なんだけれども、このよもやま話はまだもうちょっとだけ続くんじゃよ(亀仙人)。

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

Comments/Trackbacks

TrackBack ping me at


の末尾に2014年1月19日時点の日本の首相のファミリーネーム(ローマ字で回答)を繋げて下さい。例えば「noda」なら、「2007-02-02_splitbrowser-popupbuttons.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 ブラウザ無料ダウンロード