X-0035 XULウィンドウのフェードイン・フェードアウト

Firefox 1.5以降では透過ウィンドウがサポートされているため、上手く使えばウィンドウのフェードイン・フェードアウトも実現できます

ポイント

XULウィンドウのフェードイン・フェードアウトを実現するポイントは、以下の技術です。

  • ウィンドウの透過。ウィンドウのルート要素に background-color: transparent を指定すると、透過ウィンドウになる。
  • 透明度。CSS3のopacityプロパティを使うと、要素の透明度を変更できる。

つまり、透過ウィンドウの中に透明度が可変の要素を一つだけ置いておき、その透明度をタイマーで変更してやれば、XULウィンドウ全体があたかもフェードイン・フェードアウトしているかのように見せることができるわけです。

実装

利用するのが簡単になるように、バインディングを使うことにします。まず以下の内容をfadable-window.xmlなどの名前で保存します。

<?xml version="1.0"?>
<bindings id="fadableWindowBindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="fadable-window">
    <content>
        <xul:vbox flex="1" anonid="container">
            <children/>
        </xul:vbox>
    </content>
    <implementation>
      <constructor>
      <![CDATA[
        this.container = document.getAnonymousElementByAttribute(this, 'anonid', 'container');
      ]]>
      </constructor>
       <method name="fade">
        <parameter name="aDir" />
        <parameter name="aDelay" />
        <parameter name="aCallbackFunc" />
         <body>
         <![CDATA[
            if (this.fadeTimer) this.fadeFinish();

            this.fadeDelay   = aDelay;
            this.fadeStart   = (new Date()).getTime();
            this.fadeStep    = (aDir < 0 ? -1 : 1 );
            this.fadeOpacity = (aDir < 0 ? 1 : 0 );

            this.container.style.opacity = this.fadeOpacity;

            this.fadeTimer = window.setInterval(this.fadeCallback, 10, this, aCallbackFunc);
         ]]>
         </body>
       </method>
       <method name="fadeFinish">
         <body>
         <![CDATA[
            if (this.fadeTimer) {
                window.clearInterval(this.fadeTimer);
                this.fadeTimer = null;
            }
         ]]>
         </body>
       </method>
       <method name="fadeCallback">
        <parameter name="aThis" />
        <parameter name="aCallbackFunc" />
         <body>
         <![CDATA[
            aThis.fadeOpacity += aThis.fadeStep *
                                 (((new Date()).getTime() - aThis.fadeStart) / aThis.fadeDelay)/10;
            aThis.container.style.opacity = Math.min(1, Math.max(0, aThis.fadeOpacity));
            if (aThis.fadeOpacity <= 0 || aThis.fadeOpacity >= 1) {
                aThis.fadeFinish();
                if (aCallbackFunc)
                    aCallbackFunc();
            }
         ]]>
         </body>
       </method>
       <field name="fadeOpacity">0</field>
       <field name="fadeStep">0</field>
       <field name="fadeDelay">0</field>
       <field name="fadeStart">0</field>
       <field name="fadeTimer">null</field>
    </implementation>
  </binding>

</bindings>

次に、以下の内容をfadable-window.cssなどの名前で保存し、fadable-window.xmlと同じ位置に置いておきます。

:root {
    background-color: transparent !important;
    -moz-binding: url(fadable-window.xml#fadable-window) !important;
}

[anonid="container"] {
    background: white;
    color: black;
    opacity: 0;
}

透明度を最初から0にしてあるのは、フェードインを前提にしているためです。フェードインさせるつもりがなければ透明度の指定は不要です。色の指定も好みで変えてください。

使用例は以下の通りです。

<?xml version="1.0"?>
<?xml-stylesheet href="fadable-window.css" type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
           onload="this.fade(1, 200);">
   <description value="Hello, world."/>
</window>

fade()メソッドの引数は3つです。

  1. 透明度の増減方向(正の値=フェードイン、負の値=フェードアウト)
  2. フェードイン・フェードアウトにかける時間(ミリ秒)
  3. フェードイン・フェードアウト完了後に実行する関数(省略可能)

先の例では「0.2秒かけてウィンドウをフェードインさせる」ということになります。

既知の問題

  • deckの中にimageを含む内容を置くと、要素の内容が全く表示されなくなります。これについては、stackを使ってdeck自体を自分で実装し直すなどして対処する必要があります。
  • 半透明の時にウィンドウが黒ずんで見えるのは仕様のようです。