Stretcher
以下は、Mac OS X 10.4.11 上の Xcode 2.5 で説明しています。このウィジェットは、BlankWidgetで解説したことは繰り返しませんので、そちらを参照してください。
1 ウィジェット
Stretcher.wdgt は、以下のようなウィジェットです。
真ん中に「Click to Strech(伸ばすにはクリック)」と書かれています。クリックすると下のように広がります。
今度は「Click to Shrink(縮めるにはクリック)」となっていて、その下に「This text should only be visible when widget is streched(このテキストはウィジェットが伸ばされた時だけ見えるはずです)」と書かれています。この 2 つの状態は一瞬にして移り変わるのではなく、ある程度の時間をかけてアニメーションで動かされています。
アニメーションの様子をくわしく観察するには、シフトキーを押しながらクリックします。するとスローモーションで伸縮が行われます。
2 ファイル構成
このサンプルのファイル構成は以下のようになります。
AppleClasses というフォルダ内に AppleAnimator.js というファイルがあります。これは Apple クラスと呼ばれる Apple が提供している JavaScript クラスの 1 つです。このほかにスクロール用やスライダーなどがありますが、ここではアニメーション用のクラスだけが組み込まれています。Mac OS X v10.4.3 以降では、これはシステムに置かれていて、
<script type='text/javascript'
src='/System/Library/WidgetResources/AppleClasses/AppleAnimator.js'
charset='utf-8'/>
を head 内に書くことで、組み込まなくても利用できます。ただし、この方法だと v10.4.3 より前では動作しません。このサンプルのように、必要なものをコピーすれば、v10.4.3 より前でも動作します。
Default.png は、最初の縮んだ状態での背景と同じです。Top.png と Bottom.png は、一番上と一番下の背景の角が丸くなった部分です。Center.png は高さ 1 ピクセルで、角が丸くなっている部分以外の幅が同じ部分の背景を埋めるための画像です。
Info.plist の最初に BackwardsCompatibleClassLookup キーが値 TRUE とともに置かれていることに注意してください。これはクラス参照において下位互換性がある(v10.4.3 より前にも対応できる)方法が使われていることが宣言されています。v10.4.3 以降のシステムでこのキーが指定されていれば、AppleClasses/... という形のスクリプト読み込みを見つけたとき、それが自動的にその時のシステムの Apple クラスで置き換えられます。すなわち、組み込まれているファイルではなく、動作するシステムの最新バージョンが使われることになります。これがうまく機能するために、Apple クラスのファイルはウィジェット直下の AppleClasses というフォルダに入れられている必要があります。
また、このサンプルの Info.plist では、BlankWidget のキー以外に、Height と Width というキーが指定されています。これは省略可能なキーで、それぞれ、ウィジェットの高さと幅を指定します。このサンプルで指定されているサイズは Default.png、すなわち縮んでいる時のサイズと同じです。他のキーは、BlankWidget と同じです。主要 HTML は Stretcher.html に指定されています。
3 Stretcher.html
それでは、主要 HTML ファイルを見てみます。
Stretcher.html
<!DOCTYPE HTML PUBLIC
"-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<!-- Stretcherは最初に用意された背景画像の上と下の半分を示す
- 「strech」アニメーションは、単に下半分を下に向けて移動させる
- 「center」div は1ピクセルの繰り返される画像として定義され、
- これは下半分が下に移動したことで作られたすき間を満たすことになる
- 塗りつぶしパターンは、元の画像の中央から1ピクセルの行としてとられる
- これは、出たり入ったりして移動する固定サイズの下半分を使っている
- Phone Book(日本版では Business)ウィジェットに対する別のやり方である
- このモデルは中央の内容が適応できるので伸ばす距離をより簡単に変更できる
- topEdge、bottomEdge、center スタイルに対して Stretcher.css を参照
-
- (たとえば「縮小モード」のように)最初に期待される高さに
- ウィジェットのInfo.plistのHeightキーを設定する
- デフォルト画像を作るには、最初の状態でウィジェットウインドウの
- スクリーンショットをとる(コマンド-シフト-4、スペースバー、クリック)-->
<html>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=ISO-8859-1'>
<title>Stretcher Example Widget</title>
<script type='text/javascript' src='Scripts/StretcherMain.js'></script>
<script type='text/javascript' src='Scripts/Stretcher.js'></script>
<!-- 10.4.3 より前に対する下位互換性
- ウィジェットのトップレベルに AppleClasses ディレクトリを含め、相対パスを使う-->
<script type='text/javascript' src='AppleClasses/AppleAnimator.js'></script>
<style type='text/css'>
@import "Stretcher.css";
</style>
</head>
<body onload='loaded();'>
<div id='front' onclick='stretcher.stretch(event);'>
<div id='topEdge'></div>
<div id='bottomEdge'></div>
<div id='center'>
<span id='ctrTitle'>Click to Stretch</span>
<span id='ctrContent'>This text should only be visible
when the widget is stretched.</span>
</div>
</div>
<div id='debugDiv'></div>
</body>
</html>
構造は単純です。スクリプトファイルが 3 つも読み込まれていることに注意してください。StretcherMain.js は独自のもので、loaded など 2 つの関数が実装されています。Stretcher.js も独自で、strecher オブジェクトが定義されていて、id 属性が front の div 要素に設定されている onclick ハンドラの strech(event) メソッドなどが実装されています。
body 要素には読み込まれた時に実行される loaded が設定されていて、そのなかに 2 つの div 要素があります。後のほうの div は debugDiv(デバッグ用 div)という id で、最初は空です。前の方の div 要素が本体で、これにクリックされた時のハンドラが設定されています。
最初の div 内には、一番上の部分を表す div、一番下を表す div、中央を表す div があります。中央の div 内には、「Click to Strech」という文字列が入っている span 要素と、伸ばされた時に見えることになるテキストが収められた span 要素があります。
4 Stretcher.css
スタイルシートを見てみましょう。通常と同じような設定の部分は省きます。
Stretcher.css > 背景各部
#topEdge {
top:0;
...
height:28px;
background-image: url("Images/Top.png");
background-repeat: no-repeat;
}
#bottomEdge {
bottom:0;
...
height:29px;
background-image: url("Images/Bottom.png");
background-repeat: no-repeat;
}
#center {
...
top:28px; /* topEdge.bottomまたは(topEdge.top + topEdge.height)に設定 */
bottom:29px; /* bottomEdge.topまたは(bottomEdge.bottom + bottomEdge.height)に設定 */
...
background-image:url("Images/Center.png");
background-repeat:repeat-y;
overflow:hidden;
}
#center の ID セレクタ部分に注目してください。top が #topEdge の top と height を足した値に、bottom が # bottomEdge の bottom と height を足した値に設定されています。また、#topEdge と #bottomEdge の background-repeat プロパティが no-repeat(繰り返しなし)に設定されているのに比べて、#center は repeat-y(縦方向のみ繰り返し)に設定されています。また、はみ出した部分の表示方法の指定である overflow プロパティが hidden(表示しない)になっています。この設定によって、ctrContent という id 属性をもつ span 要素内の文字列が表示されないことになります。ちなみに overflow は、通常の設定(visible)だとはみ出して表示されます。また、scroll だとブラウザによるスクロール領域になります。また auto はブラウザまかせです。
Stretcher.css > デバッグ用
#debugDiv {
border-style:dotted;
border-color:black;
background:gray;
position:absolute;
top:300px;
left:10px;
right:10px;
height:200px;
width:90%;
overflow:scroll;
display:none;
}
display プロパティが none(表示しない)になっていることに注意してください。このため、デバッグ出力は表示されません。
5 StretcherMain.js
このスクリプトファイルには、変数と本体が読み込まれた時に呼ばれるものなど 2 つの関数が実装されています。
StretcherMain.js > stretcher
var stretcher;
// すべてを始める
function loaded () {
stretcher = new Stretcher(document.getElementById('front'), 100, 250, changeText);
}
新しく Stretcher オブジェクトのインスタンスを作成して、それを変数 stretcher に格納しています。Strecher オブジェクトは Stretcher.js で実装されています。このコンストラクタについては後で説明しますが、最後の引数はオブジェクトが伸縮を終了したときに呼ばれるコールバック関数です。ここで指定されている changeText は、このファイルで実装されています。
StretcherMain.js > changeText
// stretcher がウィジェットの状態にもとづいて伸縮を完了した時に
// 正しい状態テキストを表示するためのコールバック
function changeText () {
document.getElementById('ctrTitle').firstChild.nodeValue
= stretcher.isStretched() ? "Click to Shrink" : "Click to Stretch";
}
id が ctrTitle という要素(span)の最初の子のノード値として、stretcher に伸縮状態を問い合わせ、それにもとづいてテキストを設定しています。
6 Stretcher.js
Stretcher オブジェクトはこのファイルで実装されています。まずコンストラクタを見てみます。
6.1 コンストラクタ
Stretcher.js > Stretcher
/*
* Stretcher コンストラクタ 引数:
* -- element: 伸縮する要素
* -- stretchDistance: 内容を伸ばたほうがいい(ピクセル単位の)距離
* -- stretchDuration: 伸縮アニメーションがどのぐらいかかったほうがいいか(ms 単位)
* -- onFinished: コールバック(コールバックが必要ないなら、null を渡す)
*/
function Stretcher (element, stretchDistance, stretchDuration, onFinished) {
this.element = element;
this.stretchDistance = stretchDistance;
this.duration = stretchDuration;
this.onFinished = onFinished;
this.multiplier = 1;
// アニメーションの速度(大きいと遅い)
// min と max 位置は伸縮サイズを変えるために変更できる
// getComputedStyle は見えている対象(この場合 stretcher 要素)による
// そのため内容が表示されるまで Stretcher をインスタンス化しない
this.minPosition
= parseInt(document.defaultView.getComputedStyle(this.element, "")
.getPropertyValue("height"));
this.maxPosition = this.minPosition + this.stretchDistance;
// 最初の「縮小」状態におけるものに変数を設定する
this.positionFrom = this.minPosition;
this.positionTo = this.maxPosition;
// 新しい AppleClasses のサポート
var self = this; // タイマー・イベントハンドラにおける有効範囲の問題を除去
// selfはプライベートインスタンス変数
this.stretchAnimator = null;
// AppleAnimation コールバック、実際にサイズを変更する所
// これはプライベート変数 self にアクセスできる特権メソッド
this.nextFrame = function (animation, now, first, done) {
self.element.style.height = now + "px";
}
// これはプライベート変数 self にアクセスできる特権メソッド
this.changeDirection = function () {
if (self.positionTo == self.maxPosition) {
self.positionTo = self.minPosition;
} else {
self.positionTo = self.maxPosition;
}
}
// AppleAnimator に対するコールバック、進行中の伸縮を中断するためにも呼ばれる
// これはプライベート変数 self にアクセスできる特権メソッド
this.doneStretching = function () {
// 縮めたところなら、アニメーションが完了した「後」、新しいものにウインドウをサイズ変更
if (window.widget
&& (parseInt(self.element.style.height) == self.minPosition)) {
window.resizeTo(parseInt(document.defaultView
.getComputedStyle(self.element, "").getPropertyValue("width")),
self.minPosition);
}
self.positionFrom = parseInt(self.element.style.height);
self.changeDirection();
delete self.stretchAnimator;
if (self.onFinished) {
self.onFinished();
}
}
}
だいぶ複雑ですが、順番を追っていけば、わかるでしょう。
最初に各種インスタンス変数を設定しています。this.multiplier = 1; のような文で、オブジェクトのインスタンス変数(メンバー)を設定できます。この multiplier は後でわかりますがアニメーションの速度です。これより前の部分は引数を保存しているだけの変数なので、わかると思います。
つぎに最小位置と最大位置を設定しています。getComputedStyle は、Safari では document 以下のみ適用できる関数であることに注意してください。これは動的なスタイルを計算するので、その時の表示上のスタイル値を返してくれます。逆にいえば、スタイルが適用された状態でなければいけないので、コメントで注意されているわけです。このコンストラクタは読み込み完了後のイベントハンドラから呼ばれているので、これは大丈夫です。それから最大位置を最小位置に伸ばす距離を足したものにしています。それから、今計算した値を使って、最初の拡げる場合の変数を設定しています。positionFrom は元、positionTo は目標です。
つぎに注意してください。JavaScript ではオブジェクトは通常のパブリックな変数(this.XXX = ??? の形)とは別に、プライベート(そのオブジェクト内からのみ参照できる)な変数を設定できます。このようにオブジェクトのコンストラクタ内で var 指定で変数を作ると、それはプライベート変数となります。また、注意すべきはコンストラクタの引数も暗黙のプライベート変数であるということです。このコンストラクタでは、パブリックなインスタンス変数に引数をコピーしていましたが、プライベート使用のみなら、パブリックな変数にコピーする必要はありません。
プライベートな変数は、当然のことながらコンストラクタ内からアクセスできます。では、パブリックな関数など外部から操作するにはどうすればいいでしょうか。それが以下のコードとなります。ここでは、特権メソッド(プリビレッジドメソッド)(privileged method) という仕組みを使って、プライベート変数を操作できる関数を定義しています。この特権メソッドはパブリック関数内から呼びだせます。このようにプライベート変数を扱う、アクセサや内部関数はコントスラクタ内でオブジェクト作成時に定義します。
これが使われているのは、文脈によって、this がさまざまなものを表してしまうからです。コンストラクタ内でプライベート変数 self に this を代入することで、self がつねにこのインスタンスを示していることを確実にできます。
その後、アニメーション実行中でないので stretchAnimator を null に初期化しています。
これ以後は、特権メソッドの定義です。これらはアニメーション時のコールバック等として呼び出されます。
nextFrame は、伸縮対象の要素の高さを now で渡されたピクセルに設定しているだけです。
changeDirection は、対象位置が最大位置なら最小位置にして、そうでないなら逆にして、方向を切り替えています。
つぎに doneStretching は、伸縮完了処理をします。縮めた場合なら、取得した幅と最小の高さでウインドウをサイズ変更します。それから元の位置を設定し、方向変更をしています。そして設定したアニメーション実行オブジェクトを削除し、終了ハンドラが設定されていればそれを呼び出します。
6.2 Apple クラスによるアニメーション
実際にアニメーションを開始するメソッドを見る前に、Apple クラスのアニメーション使用について簡単にまとめてみます。くわしいことは『Dashboard プログラミングトピック』>「アニメーションの使用」を見てください。
アニメーションを使うには 2 つの方法があります。簡単なものは、コンストラクタ AppleAnimator(duration, interval, start, finish, handler) を使うもので、引数は、タイマー持続時間、発動間隔、開始値、終了値、ハンドラです。ハンドラは function handler(animation, current, start, finish) の形のもので、引数は、呼び出したアニメーション(下を参照)、現在値、開始値、終了値となっています。こうして作成したアニメーターを start() と stop() メソッドで開始・終了します。
もっと複雑な方法は、コンストラクタ AppleAnimator(duration, interval) を使うものです。引数はタイマー持続時間、発動間隔だけです。これだけでは何も起こらないので、アニメーションオブジェクトを作ってこれに追加します。コンストラクタ AppleAnimation(start, finish, handler) を使います。引数は、開始値、終了値、ハンドラです。ハンドラの形式は function animationHandler(animation, current, start, finish) で上と同じです。こうして作ったアニメーションオブジェクトを currentAnimator.addAnimation(currentAnimation); のような形で追加します。アニメーターに対して複数のアニメーションオブジェクトを追加できるので、同じ持続時間と発動間隔をもつ、異なる効果や対象を扱うアニメーションを複数同時実行できます。上の簡単な場合は、1つのアニメーションだけを持つ場合に相当します。
アニメーターはプロパティを持っていて、duration(持続期間)、interval(間隔)、animations(アニメーション配列)、timer(タイマーの現在値)、oncomplete(タイマー完了時のハンドラ)です。
アニメーションのプロパティは、from(開始値)、to(終了値)、now(現在値)、callback(ハンドラ)となっています。
また、AppleAnimator というクラスが既定義されています。これは長方形のサイズ変更アニメーションに対して簡単な方法を提供しています。
6.3 アニメーション開始
つぎは実際にアニメーションを開始する stretch メソッドを見てみます。
Stretcher.js > Stretcher
/*
* これは「instance.stretch(event)」のようにStretcherインスタンスを経由してのみ呼ばれたほうがいい
* ことを引き起こすことになり、関数は失敗することになる。引数 :
* -- event: (onclick ハンドラから)すべてを発動させたマウスクリック
* スローモーションの伸縮を行うためシフトキーをチェックする
*/
Stretcher.prototype.stretch = function (event) {
if (event && event != undefined && event.shiftKey) {
// スローモーションを有効にする
this.multiplier = 10;
} else this.multiplier = 1;
// 現在伸縮中なら
if (this.stretchAnimator) {
this.stretchAnimator.stop();
var handler = this.onFinished;
this.onFinished = null;
this.doneStretching();
this.onFinished = handler;
}
// 新しいサイズの内容に対する場所を作るため伸ばす前にウインドウをサイズ変更
if (window.widget && (this.positionTo == this.maxPosition)) {
window.resizeTo(parseInt(document.defaultView.
getComputedStyle(this.element, "").getPropertyValue("width")),
this.positionTo);
}
this.stretchAnimator = new AppleAnimator(this.duration * this.multiplier, 13,
this.positionFrom, this.positionTo, this.nextFrame);
this.stretchAnimator.oncomplete = this.doneStretching;
this.stretchAnimator.start();
}
最初の prototype に注意してください。これは原型・鋳型という意味で、このオブジェクトのすべてのインスタンスに共通なパブリックメソッドを宣言するために使われています。オブジェクトにパブリックなメソッドを追加するには、このような形を使います。
まずシフトキーが押されていた場合にスローモーションを行うために、渡された event を調べています。シフトキーが押されていたら、multiplier(倍率)の値を 10 にします。
つぎに現在アニメーション実行中ならそれを停止し、終了ハンドラを null にした後で終了処理を呼び出し、それから再び終了ハンドラを設定しています。
それから伸ばす場合なら、ウインドウをサイズ変更しています。これを行わないと、ウインドウのサイズがそのままで内容だけが拡げられ残りの部分は隠れたままになってしまいます。
最後にアニメーターを作って実行しています。これは上で説明した簡単な場合です。持続時間に multiplier の値が掛けられていることに注意してください。これによりアニメーションが行われる時間が長くなります。発動間隔が一定であることに注意してください。このように Apple クラスのアニメーションでは、発動ごとの値を独自に計算するのではなく、開始値と終了値と持続時間を指定することで、ハンドラが呼ばれたときに設定する必要がある値を自動的に計算して、current 引数として渡してくれます。このため、複数の設定値の場合、複数のアニメーションを設定できるようになっているわけです。ハンドラのなかで呼ばれた回数などで処理を行わないように注意してください。
6.4 残りのメソッド
さて、他のメソッドも見てみます。
Stretcher.js > isStretched
/*
* Stretcher が最大位置にあるかどうかを報告する
* このメソッドを Stretcher が現在アニメーション中かどうかを決定するために呼ばないこと
* アニメーションが完了したとき通知されるよう onFinished ハンドラを設定する
*/
Stretcher.prototype.isStretched = function() {
return (parseInt(this.element.style.height) == this.maxPosition);
}
残りはデバッグ用コードです。
Stretcher.js > 変数と DEBUG
var debugMode = false;
// debug div に対する書き出し
function DEBUG(str) {
if (debugMode) {
if (window.widget) {
alert(str);
} else {
var debugDiv = document.getElementById("debugDiv");
debugDiv.appendChild(document.createTextNode(str));
debugDiv.appendChild(document.createElement("br"));
debugDiv.scrollTop = debugDiv.scrollHeight;
}
}
}
デバッグモード用変数を設定し、ウィジェットの時はコンソールに書き出されるように、alert を使い、そうでない場合は debugDiv 内に文字列を書き出しています。
Stretcher.js > toggleDebug
// debugMode フラグを切り替え、Safari 内の場合だけ debugDiv 内に示されるようにする
function toggleDebug() {
debugMode = !debugMode;
if (debugMode == true && !window.widget) {
document.getElementById("debugDiv").style.display = "block";
} else {
document.getElementById("debugDiv").style.display = "none";
}
}
これは debugMode 変数の値を変更し、Safari で開かれた場合には debugDiv が表示されるようにします。
6.5 デバッグ
さて、せっかくデバッグモードがあるので、それについて調べてみましょう。このサンブルを Safari で開いても何も起こりません。どうなっているのでしょうか。当然のことながら、最初の debugMode の値は false です。なので、これをいじってやらなければなりません。
Stretcher.html の body 要素の onLoad ハンドラ部分を onload='toggleDebug(); loaded();' に修正します。それから再び Safari で読み込みます。クリックして拡張すれば以下のような表示になります。
このように下の灰色の部分にデバッグ情報(DEBUG で書き出された文字列)が表示されるようになります。
この部分の表示はどこから来ているのでしょうか。付属の AppleClasses フォルダ内の AppleAnimator.js を見ると以下のような部分があります。
AppleAnimator.js > AppleAnimator
...
DEBUG("done: oncomplete: " + self.oncomplete);
...
このように、このファイルのあちこちにデバッグ出力が埋め込まれていて、それが表示されるようになっています。
では、これをウィジェットとしてインストールしてみましょう。コンソールを開いて、ウィジェットをクリックした後に見てみます。しかし、alert() で出力されたはずのものは何もありません。
じつは、これはシステムに存在する Apple クラスにはデバッグ出力部分がないからなのです。システムのファイルとの差分をとってみれば、DEBUG 部分以外は同じであることがわかるでしょう。このサンプルでは、下位互換性のために BackwardsCompatibleClassLookup キーが値 TRUE に設定されていました。これが設定されていると、システムの Apple クラスが優先的に使われてしまいます。そこでウィジェット内の Info.plist を修正し、このキーを除去するか、FALSE にします。そしてウィジェットを再びインストールします。
今度は、Safari 上で表示されたのと同じような文字列がコンソールに出力されているのがわかります。
Mac OS X v10.5 以降では、Dashcode を使えば、このような複雑なデバッグ操作は必要ではありません。
7 AppleAnimator クラス
ここまでで、このサンプルの動作は十分わかったと思います。以下では、Apple クラスのほうを見てみます。アニメーションを利用したいだけの人は、以下の説明を読む必要はありません。
7.1 AppleAnimator オブジェクト
このサンプルに付いてる AppleAnimator.js には、上で説明したように DEBUG 文が入れられています。このファイルは Mac OS X v10.4.11 のシステムと DEBUG 文以外では同じで、Mac OS X v10.5 でもそのままです。まずコンストラクタを見てみましょう。
AppleAnimator.js > AppleAnimator
function AppleAnimator (duration, interval,
optionalFrom, optionalTo, optionalCallback)
{
// まず引数が 2 つだけの場合として初期化がなされる
this.startTime = 0;
// 開始時を 0 に
this.duration = duration;
// 持続時間を引数に設定
this.interval = interval;
// 間隔を引数に設定
this.animations = new Array;
// アニメーション追加用配列作成
this.timer = null;
// タイマーなし
this.oncomplete = null;
// 終了時のハンドラなし
this._firstTime = true;
// 初回を true に
var self = this;
// プライベート変数に自身を保存しておく
this.animate = function (self) {
// 特権メソッドの定義
function limit_3 (a, b, c) {
// 特権メソッド内の内部関数定義
return a < b ? b : (a > c ? c : a);
}
var T, time;
var ease;
var time = (new Date).getTime();
// time を現在時に
var dur = self.duration;
var done;
T = limit_3(time - self.startTime, 0, dur);
// 通常は現在までの時間
time = T / dur;
// 進んでいる割合
ease = 0.5 - (0.5 * Math.cos(Math.PI * time));
// 現在の値を曲線上の値として得る
done = T >= dur;
// 持続時間以上かどうか
var array = self.animations;
// アニメーション配列をコピー
var c = array.length;
// アニメーション配列の個数
var first = self._firstTime;
// 初回かどうかをコピー
for (var i = 0; i < c; ++i)
// 配列内のアニメーションを計算値で実行
{
array[i].doFrame (self, ease, first, done, time);
}
if (done)
// 持続時間以上なら
{
self.stop();
// 停止
DEBUG("done: oncomplete: " + self.oncomplete);
if (self.oncomplete != null)
// 終了ハンドラがあれば
{
self.oncomplete();
// それを呼び出す
}
}
self._firstTime = false;
// 初回でない
}
// 残りの 3 つの引数があれば自動的にアニメーションを設定
if (optionalFrom !== undefined && optionalTo !== undefined
&& optionalCallback !== undefined)
{
DEBUG("creating default animation");
this.addAnimation
(new AppleAnimation (optionalFrom, optionalTo, optionalCallback));
}
}
それほど難しくはないと思います。まず最初は引数 2 つだけとしてインスタンス変数を設定し、プライベート変数にこのインスタンス自身を保存しておきます。次は他から呼ばれる animate メソッドの定義です。最後に、引数がさらに渡されていれば、それを使ってアニメーションオブジェクトを自動作成しています。
特権メソッド animate では、開始時から現在時までを計算し、0 から持続時間内に収まるように修正し、それから曲線上の値として進み具合を計算し、配列内のアニメーションを実行し、持続時間以上なら停止して終了しています。実際のフレーム動作はアニメーションの doFrame で行われていることがわかります。
つぎはアニメーションを開始するメソッドです。
AppleAnimator.js > start
AppleAnimator.prototype.start = function () {
if (this.timer == null)
// タイマーがnull の場合だけ
{
var timeNow = (new Date).getTime();
// 現在時
var interval = this.interval;
// 間隔
this.startTime = timeNow - interval; // 1フレーム前に戻す
this.timer = setInterval (this.animate, interval, this);
// タイマー設定
}
}
時間の設定を行って、JavaScript の繰り返し動作を行う関数を使っています。この関数の引数は、最初が呼び出される関数で、その次が間隔です。以下にさらに引数を渡すこともでき、その場合、その余分の引数がタイマー発動時に呼ばれる関数に渡されます。この関数の戻り値は個々のタイマーを識別するために使われます。そのため、必ず保存しておかなければなりません。
つぎはアニメーションを停止するメソッドです。
AppleAnimator.js > stop
AppleAnimator.prototype.stop = function () {
if (this.timer != null)
// タイマーがnull でない場合だけ
{
clearInterval(this.timer);
// タイマーを無効化
this.timer = null;
// タイマーなしに
}
}
保存した戻り値を使って clearInterval を呼び出し、繰り返し動作をキャンセルしています。
つぎはアニメーションを追加するメソッドです。
AppleAnimator.js > addAnimation
AppleAnimator.prototype.addAnimation = function (animation) {
this.animations[this.animations.length] = animation;
}
単に配列にアニメーションを追加しているだけです。
7.2 Animation オブジェクト
実際のアニメーションを行うアニメーションクラスを見てみます。まずコンストラクタです。
AppleAnimator.js > AppleAnimation
function AppleAnimation (from, to, callback)
{
this.from = from;
this.to = to;
this.callback = callback;
DEBUG("from: " + from + " to: " + to + " callback: " + callback);
this.now = from;
this.ease = 0;
this.time = 0;
}
簡単です。渡された開始値、終了値、ハンドラを保存しているだけです。その他のインスタンス変数を初期化しています。つぎは毎回のアニメーションを行うメソッドです。
AppleAnimator.js > doFrame
AppleAnimation.prototype.doFrame = function (animation, ease, first, done, time) {
var now;
if (done)
// 最後なら終了値に
now = this.to;
else
// それ以外は渡された割合にしたがって値を計算
now = this.from + (this.to - this.from) * ease;
this.now = now;
this.ease = ease;
this.time = time;
this.callback (animation, now, first, done);
}
アニメーターオブジェクトは時間範囲内の曲線によって進みぐあいの割合を計算していましたが、そうして渡された値をここで実際の値へと変換しています。最後の場合だけ割合にかかわらず終了値にしています。それから変数を設定して、設定されたコールバックを呼んでいるだけです。
このクラスはこれだけです。こうして、アニメーターとアニメーションの作業分担の様子が理解できたと思います。アニメーターは時間の進行を扱い、アニメーションは値の進行を扱い、ハンドラを呼び出します。
7.3 AppleRectAnimation オブジェクト
さて、長方形アニメーションを行うクラスが前もって定義されています。それを見てみます。
AppleAnimator.js > AppleRectAnimation と継承部分
function AppleRectAnimation (from, to, callback)
{
this.from = from;
this.to = to;
this.callback = callback;
this.now = from;
this.ease = 0;
this.time = 0;
}
AppleRectAnimation.prototype = new AppleAnimation (0, 0, null);
コンストラクタは初期値と終了値とハンドラを設定しているだけです。つぎはアニメーション実行です。
AppleAnimator.js > AppleRectAnimation と継承部分
AppleRectAnimation.prototype.doFrame = function (animation, ease, first, done, time) {
var now;
function computeNextRectangle (from, to, ease)
{
return addRects (from, timesRect (minusRects(to, from), ease));
}
if (done)
now = this.to;
else
now = AppleRect.add (this.from,
AppleRect.multiply (AppleRect.subtract (this.to, this.from), ease));
//computeNextRectangle (this.from, this.to, ease);
this.now = now;
this.ease = ease;
this.time = time;
this.callback (animation, now, first, done);
}
内部関数 computeNextRectangle は結局使われていないようです。from に対して、from と to の長方形の差に割合 ease を掛けた結果を now にしています。
このファイルには、AppleRect オブジェクトも実装されていて、これが AppleRectAnimation で使われています。このオブジェクトはいくつかのメソッドを定義していますが、長方形どおしの和や差、数値を掛けたりというメソッドで、それほど複雑なものではないので、ここでは取り上げません。こうしたメソッドを使って、中間的な長方形を計算し、それが AppleRectAnimation のフレーム動作で使われているわけです。
管理人:神吉 秀典 E-mail: