たまに18歳未満の人や心臓の弱い人にはお勧めできない情報が含まれることもあるかもしれない、甘くなくて酸っぱくてしょっぱいチラシの裏。RSSによる簡単な更新情報を利用したりすると、ハッピーになるかも知れませんしそうでないかも知れません。
の動向はもえじら組ブログで。
宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。
以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能!
以前書いたtabbrowser要素のswapBrowsersAndCloseOther()
メソッドの上書きの話で書いてたサンプルが使えなくなっていたので直した。というか自作アドオンの機能を使おうとして正しく動かなかったのでコードを改めて見てみて、メソッドの書き換えの対象になってる箇所に変更があった事に気付いた(4月16日に変更が行われていたようだ)。
以下は、Shiretoko 3.5b5preに合わせて修正した後のバージョンのサンプルコード。
if ('swapBrowsersAndCloseOther' in aTabBrowser) {
eval('aTabBrowser.swapBrowsersAndCloseOther = '+
aTabBrowser.swapBrowsersAndCloseOther.toSource().replace(
'{',
'$& MyAddonService.destroyTab(aOurTab);'
).replace(
'if (aOurTab == this.selectedTab) {this.updateCurrentBrowser(',
'MyAddonService.initTab(aOurTab); $&'
)
);
}
if (aOurTab == this.selectedTab)
を目印にして「タブを入れ換えた後の再初期化処理」を入れてたんだけど、同じif文がこれより前の位置に増えたせいで、コードの挿入位置がずれてしまい、本来期待していた順番とは異なる順番で処理が行われてしまっていた。if文の中まで目印に含めるようにして一応回避したけど、これはこれで、if文の中を書き換えるアドオンがあると破綻してしまう可能性があるわけで……何かいい方法はないものだろうか。
今回の場合に限らず、「メソッドの入れ替えではなく動的な書き換えを行う」やり方には、こういう変更に逐一追従しなくてはならないという弱点がある。メソッドのインターフェース(引数の数や返り値)はそうそう変わらなくても、中身は結構頻繁に変わる。なので、気をつけておかないといけない。
もう1つの弱点として、書き換え対象のメソッドの中で参照されている変数がそのメソッドが定義された変数スコープからしか参照できないものであった場合に、それへの対処も必要となる。その変数の値がグローバルな名前空間からアクセス可能なものであれば、メソッドの書き換え時にその変数を同時に宣言し直してやればいいんだけど……
(function() {
var attrName = 'foo';
window.ExampleAddonService = {
method : function(aNode) {
aNode.setAttribute(attrName, true);
}
};
})();
// メソッドの書き換えには成功するが、attrNameという変数が
// 見つからなくなってしまうので、実行時にエラーになる
eval(
'window.ExampleAddonService.method = '+
window.ExampleAddonService.method.toSource().replace(
'{',
'{ AnotherAddonService.preProcess(aNode);'
)
);
この例のような場合になると、もうお手上げ。
それでも動的なメソッドの書き換えの方を僕が積極的に使う最大の理由は、同じやり方でメソッドを書き換える他のアドオンとの互換性を維持するためだ。別名で元のメソッドを保持しておくやり方の場合、他のアドオンが元のメソッドの名前で関数オブジェクトを取得しても、その関数の内容は元のメソッドとは全然違うから、書き換えられなくて動かなくなる、という事態が起こり得る。それを避けるためには、上の例のようなお手上げな事例を除き、可能な限り元のメソッドを動的に書き換えた方が良いということになる。
統合を批判する拡張開発の人たちは、 「自分のやりたいことに何が必要なのかを考えない人は、 そもそも拡張を使うな」とどうしても厳しい目線に立ってしまう。
これはちょっと違う。使い手は使い手自身のエゴに則って好きにすればいいと思う。
僕がしてるのは、「自分が何をしたいかもちゃんとは認識できてない、『とりあえず何でもいいから便利なプラグイン教えて』と言ってしまうような人」の、行動や思想を批判する事でも、そういう人達の行動を改めさせるべく啓蒙活動する事でもなく、「そういう人達のために自分が努力する事を、やめる」「そういう人達の視界に入らない場所に自分が逃げる」って事だ。
何故そういう人の行動や思想そのものを批判や否定しないのかというと、自分自身も別の場面では「そういう人」だと自覚しているからだ。そういう人、をターゲットにアホだのマヌケだの言う自分の姿を想像すると、「こいつ自分がそうなのを棚に上げて他人の事ばっかり言ってやがるな」とあまりに滑稽すぎて恥ずかしいので、同じ事を他人から言われたくないから、そういう事をしないようにしようと思っている。
今の所、XUL/Migemoの辞書ファイルは全部普通のテキストファイルなんだけど、これをSQLiteデータベースに置き換えてみようかなと思ってる。メリットがあるのかどうかは分からないんだが。
現在の辞書周りは基本的にはplus7さんが作られた物を踏襲していて、起動時に全ての内容を読み込み、JavaScriptの普通の文字列としてオンメモリで保持しておくようになってる。でもこれだとFennecで動かすのって多分きついだろうなあ、とは思ってた。
どうなんだろう、SQLiteって関係ないデータはメモリに読み込まずにいてくれたりするんだろうか? Fennecのデフォルト設定を見た限りでは、履歴の保持日数はFirefoxと同じ90~180日となってるから、これを全部メモリ上に読み込んだらエラい事になるよねぇ。と考えると、必要最小限だけメモリに読み込むようになってる事を期待してると考えていいんだろうか? それとも、これは単にFennec開発陣がSQLiteを過信あるいはモバイルプラットフォームを甘く見てただけで、実は全部オンメモリになっちゃいますとか?
まあとりあえず実装するだけしてみて、それから考えてみよう……
……とりあえずやってみたけど、なんか、めさめさ重い……ちゃんと作ってないから単にどっかで無限ループしてるのかもだけど。しかしそれを抜きにしても、辞書が約4MBだったのがSQLiteにしたら9MBに膨れ上がってしまった。モバイルで使うなんてのはますます非現実的な領域だ。工夫しないと全然お話にならんね……
追記。もうちょっと進めてみたけど、余計泥沼に嵌ってる気がしてきた。バックエンドのSQLite化は無しだな、というのが現時点での結論ですわ。
Tab Mix Plus は window オブジェクトレイパー - 地獄の猫日記 とか、自分の書いた話がきっかけとなって他の人もTMPの他のアドオンとの互換性に関する話を書いていたりしていて、それらを参照した上で「TMP使うのやめます」と表明している人もいるのですけれども。そこら辺の状況をもって「PiroはTMP批判の世論を作ろうとしているのだ! 扇動しているのだ! 排斥運動をしたがっているのだ! 私怨に狂っているのだ!」とかなんとか言い出すような人が現れたら嫌だなあ、ということで自己保身のために予防線を張っておく。
こんな事言っといてアレですが、僕は今Tab Mix Plusを使ってる人に「今すぐそんな物窓から投げ捨てちまえ!」と言いたい、わけではないんですよ。他のアドオンと組み合わせさえしなければ別に問題はないんだから、使ってて不満を感じてなければ使ってて全然構わないし、使うのをやめる必要もない。
つうかね。グローバルな名前空間がどうこう言い出したら、そもそもFirefox自身のコードだって結構酷いし。将来的に問題が起こりやすい・起こりにくい設計です、ということと、今問題が起こっています・起こっていません、ということはあくまで別の話だし。
単に、いくつもアドオンをとっかえひっかえしながら使うようなスタイルでFirefoxを使うんだったら、「Firefox+TMP」という環境をベースにするのは、お勧めできないし残念な思いをすることが多そうだよ、って話です。むしろ、「アドオンという仕組みが無かったSleipnir 1.x系のようなブラウザを使ってるのと同じようなもんだ」と思えば、別に何も変じゃないわけです。
Tim O'Reillyを名乗るTwitterアカウントからリンクされてた(ツリー型タブが紹介されてた)わけですが。
……これ、騙り? ボット?
いずれにせよとりあえず1つだけ言えることは、「プラグインゆーな!」ということですかね。(←そこかよ)
「次のエントリで書く」なんて予告じみたことを書いてはみたものの、どうも話がまとまらない。もう、思ったことを箇条書きで書くだけにする。
現実的に言って、1つのアドオンであらゆる人のニーズを満たすことは不可能だと思うし、そういう方向での努力はしない方がいいとも思う。何でもかんでも……と際限なく詰め込んでいくと、どこかで破綻すると思う。機能の詰め込みは、ある時点までは効率がいいんだけど、ある時点を超えてしまうと、デメリットの方が大きくなってくる。「Piro拡張化」なんて言われるようになる。
作る側の心理として、どうして機能を増やしたくなるのか。他の人はどうか知らないけど、僕はこんな感じだった。
そんな感じの理由でどんどん機能を加えていく。ある時点までは、それでうまく回る。応援してくれる人とか感謝してくれる人とか、モチベーションを高めてくれる声も寄せられる。でも、なんかの閾値を超えたところで、あらゆる物事が下り坂に転じる。
そんなわけで、精神的な負荷と時間的なコストがどんどん大きくなっていって、タブブラウザ拡張は破綻した。Firefox 1.5のサポート完全終了と共に、過去の遺物となった。
そういうわけで、過去の自分の失敗を徹底的に反省して、ゼロからやり直すことにした。
eval()
による部分的な書き換えで対処する。こういう考えに基づいて作ったのが、マルチプルタブハンドラ、情報化タブ、ツリー型タブ、ソース表示タブという4つのアドオンだ。上に書いたことを完全に守れてるとは言えない部分もあるけど、昔に比べれば遙かにマシになってると思う。
作る側としても、使う側としても、僕は今の方がずっと楽だ。
「多機能に」「1つだけで様々なニーズに応えられるように」という方向に頑張ると、縛りがどんどんきつくなってくる。1つのアドオンだけでできることを増やしていこうとすると、妙な話だけど、他のアドオンに頼れなくなっていく。作り込むほどに、そのアドオンを入れた結果が素のFirefoxとかけ離れてしまうから、他のアドオンとの互換性がどんどん失われていく。だから、1つのアドオンだけでしなきゃいけない事がイモヅル式にどんどん増えてくる。
ユーザの視点から見ると、「多機能なアドオン1つだけの世界で完結した使い勝手を享受する」か、「いろんなアドオンを組み合わせて使う」かの、どっちかを選ばないといけないということでもある。両立はできない。
ここで改めて念を押すけれども、僕は、Tab Mix Plusと上記のタブ系アドオンの組み合わせは推奨していない。一応TMPで動くようにはした、けれども、継続的に追っかけ続けるモチベーションが無い。何故なら、僕はTMPユーザではなく、TMPと組み合わせて壊れるようであっても僕自身は全く困らないから。TMPユーザでない他のアドオンの作者の中には、同じように感じてる人もいるんじゃないかと思う。自分が使ってもいないのに対応を迫られるなんて、厄介な奴だ、目障りな奴だ、みたいな。閑話休題。
「多機能なアドオンで、部分的には、痒い所に手が届くような感覚がある。でも、足りない部分もある。それを補うための他のアドオンと組み合わせて使うことは、残念ながらできない。」「単機能のアドオンをたくさん入れていて、それぞれはそれなりに便利。でも、いまいち連携が取れてなくて、痒い所に手が届かない。」そのどっちかを選ばないといけない。
一人あるいは少数の作者の頑張りだけに期待しなきゃいけない、期待して待ってなきゃいけない。多機能長大路線を歩むという事は、ユーザにそれを強いるという事だと思う。僕はそれが嫌になった。
多分、多機能な物を「作る」だけなら、誰にでもできるんだと思う。時間さえかければ。当時の僕にもうなる程の時間があった(大学生で、レベルもそんなに高くなかったから楽に単位を取れて、その分自由に時間を使えた)から、できてたんだと思う。でも、より大きな問題は「作った」の後にあると思う。メンテナンスし続けられるのか? 他のアドオンと協調させられるのか? 多機能長大路線ではそれは難しいと、僕は思った。
という報告を受けて調べてみて、解決策が見えなくて頭を抱えてる。
原因はFirefox 3以降でタブの拡張性が深刻なまでに低下したことに起因している。現在、Firefox 3でタブの中に独自の要素(ツリー型タブの「ツリーの開閉状態を示すアイコン」「折り畳まれたタブの数を表示するラベル」など)を追加しようとすると、バインディングを上書きしてやらないといけない。でも複数のアドオンでバインディングを上書き上書きとやってると、最後にバインディングを適用した誰かの物だけが生き残って、他のアドオンが追加したバインディングは全滅してしまう。今回の問題も、ツリー型タブがFirefox自身の提供するバインディングを上書きした後から、Vimperatorがさらにバインディングを上書きしているために、タブの要素構造がツリー型タブが想定する構造から変化して、初期化に失敗するようになってしまっている。
ツリー型タブ、マルチプルタブハンドラ、情報化タブの3つを開発するにあたっては、この問題に対処するために、以下のような方針をとることにした。
これを実現するために作ったのがこちらのライブラリ。3つのファイルで構成されてる。
3つを同じ所に置いてXULファイルだけをオーバーレイで読み込ませると、バインディングを上書きする。他のアドオンで同じライブラリが読み込まれてる時は、最新の物が使われる。
これをVimperatorの作者の人にも使ってもらえればいいんだけど……誰にコンタクトすればいいか分からないし、そもそも向こうにはわざわざこれを使う動機がない。Vimperatorユーザは基本的にVimperatorのカスタマイズですべてを解決する傾向にあるようだから、他のアドオンとの互換性が低くても問題にならないだろうし。
ていうか僕自身Vimperator使ってないから、わざわざ上記のような交渉をしようというモチベーションがない。というわけで、上に名前が出てる3つのアドオンのどれかとVimperatorとを共存させたいVimperatorユーザの人は、自分で作者にコンタクト取ってください。(丸投げ)
Tree Style Tab 0.7.2009040901公開した。アニメーション効果の実装の他に、細かいバグ修正も色々。
あと、実際どんな感じかというのをわかりやすく示せるかなと思って、デモ動画も作ってみた。CamStudioもNiVEも使うの久しぶり(っていうかVistaにしてからは初)だから、やり方思い出すのに苦労したよ……
<object width="425" height="344"><param name="movie" value="http://www.youtube.com/v/M9dUfyoHz3E&hl=ja&fs=1"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/M9dUfyoHz3E&hl=ja&fs=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"></embed></object>
ヌルヌル動いてるのは倍速再生してるわけではなく、これで等倍速です(一応)。タブの影はbox-shadowで描画してるので、Firefox 3.0.xだと影無しになります。
しかしYouTubeはアップロードが簡単になってるわ画質が上がってるわで、いつの間にかすんげーパワーアップしてますね。Stage6とかあった頃とは隔世の感があります。
以下余談。
実装の最後の段階になって困った点として、画面外にタブが開かれた時にそこまで自動スクロールしてくれないという問題が起こってた。スクロールしても、新しく開かれたタブの一個前のタブの位置にスクロールしちゃったりして。上の動画で言うと、0:57あたりで画面下の方でサブツリーを展開した時に、子タブが画面内におさまるように自動でスクロールしてるけど、これがちゃんと動かなくなってた。
なんでこんな問題が起こってたかというと、「そのタブが画面外にあるのかどうかを判定する」「そのタブの位置までスクロールする」といった処理が全部「タブの座標」を基準にしてたせいで、アニメーション開始時点やアニメーション中の中途半端な座標を元に処理を行ってしまい、もうシッチャカメッチャカになってた、という……
上記の処理を行う時に、アニメーション中の座標とアニメーション終了後の座標とのズレをきちんと考慮して計算してやればいいってだけの話なんだけど、普通に考えるとこれがめんどくさい。それぞれのタブがちょっとずつズレて表示されてるわけだから、座標を調べたいタブだけじゃなくてそれより前(上)にあるタブ全部について、そのタブはアニメーション中か?とか、そのタブは非表示か?とかを判別しながらオフセット値を調べないといけないわけで……いちいちそんな計算するのはめんどくさすぎる。メンテナンスし辛そうだし、コードの量が多くなりそうだし、真面目に書く気しないです(大学生の頃だったらやってたかもだけどね……時間は有り余ってたから)。
で、代わりに以下のようなやり方を思いついた。
コードにするとこんな感じ。
getYOffsetOfTab : function(aTab)
{
return document.evaluate(
'sum((self::* | preceding-sibling::*[not(@tab-collapsed="true")])'+
'/attribute::tab-y-offset)',
aTab,
null,
XPathResult.NUMBER_TYPE,
null
).numberValue;
},
sum()
は、渡されたノードセットの値を数値として合計したものを返すXPathの関数。受け取る結果の型をXPathResult.NUMBER_TYPE
と指定すれば、計算結果の数値を直接得られる。XPathはうまく使えばこんな風に、処理対象のノードの絞り込みだけじゃなくその後の処理(ここでは計算)まで一発で済ませられるので面白い。
トゥイーンの話を受けてごろたんがさらに発展的な話を書いてくれた。で、「ほうほう、こういうのをeasingと言うのか……」と自分の無知っぷり&何も知らんくせに偉そうなことを語る厚顔さに恥じ入りながら先のエントリを少し手直しした。
で、JSTweener という Tweener (as3 のモーショントィーンライブラリ) 互換のライブラリを使うと、標準で様々な easing 関数が利用できたり、タイマーが一つなので、数百, 数千オブジェクトをモーションさせるときにはだいぶ高速になる。
というのを読んで、確かに先のエントリに書いた物はいっこいっこのタブごとにタイマー走らせるから効率悪いよなあ、と思い、1個のタイマーだけで複数のアニメーションを走らせるための簡単なライブラリを作ってみた。ツリー型タブの開発版にも早速組み込んでる。
window['piro.sakura.ne.jp'].animationManager.addTask(
)
にアニメーション用の関数その他の引数を渡してやると、それを共通のタイマーで処理する、というごく単純なもの。window['piro.sakura.ne.jp'].animationManager.removeTask(
)
で関数の登録を解除してやる。クロージャ使って書くこと前提の不親切なAPIですんません……
1つ前のエントリに書いた、可変フレームなトゥイーン効果の実装の話。
今時は便利なJavaScriptのアニメーション用ライブラリが色々あるからわざわざ自分で書くような必要はないんだろうけど、自分はほんの一箇所だけのためにライブラリ全部突っ込むというのは気が引けるタイプなので、ピンポイントな実装とその理屈を(雑学として)書いておこう。
先に、タブのインデント幅変更の処理の完成したものを貼っておく。説明を簡単にするためにちょっと省略してる。
indentDuration : 200,
updateTabIndent : function(aTab, aProp, aIndent)
{
this.stopTabIndentAnimation(aTab);
var startIndent = this.getPropertyPixelValue(aTab, aProp);
var delta = aIndent - startIndent;
var duration = this.indentDuration;
var startTime = Date.now();
aTab.__treestyletab__updateTabIndentTimer = window.setInterval(function(aSelf) {
var progress = Math.min(1, (Date.now() - startTime) / duration);
var powerForStyle = Math.sin(90 * power * Math.PI / 180);
var indent = (progress == 1) ?
aIndent :
startIndent + (delta * powerForStyle);
aTab.setAttribute(
'style',
aTab.getAttribute('style')+';'+
aProp+':'+indent+'px !important;'
);
if (progress == 1) aSelf.stopTabIndentAnimation(aTab);
}, 10, this);
},
stopTabIndentAnimation : function(aTab)
{
if (!aTab.__treestyletab__updateTabIndentTimer) return;
window.clearInterval(aTab.__treestyletab__updateTabIndentTimer);
aTab.__treestyletab__updateTabIndentTimer = null;
},
getPropertyPixelValue : function(aElement, aProp)
{
var style = window.getComputedStyle(aElement, null);
return Number(style.getPropertyValue(aProp).replace(/px$/, ''));
},
なんでCSSOM使ってないの、って所にはツッコまないように。
アニメーションというと、つまりはちょっとずつ値を変えて再描画するということで、単純に考えたら多分こうなる。
function doAnimation(aElement, aStart, aEnd)
{
for (var i = aStart; i < aEnd; i++)
{
aElement.style.marginLeft = i+'px';
}
}
でもこれはダメ。CSSのプロパティを変更しても、その状態が描画されるより前に次の値がセットされてしまうので、間のアニメーションがアニメーションにならない。(CSSのプロパティが変更された瞬間に再描画する実装だったらこれでもいいかもだけど、少なくともGeckoではダメ。)
もうちょっと改良すると、こう。
function doAnimation(aElement, aStart, aEnd)
{
var margin = aStart;
var timer = window.setInterval(function() {
aElement.style.marginLeft = (margin++)+'px';
if (margin == aEnd) {
window.clearInterval(timer);
}
}, 10);
}
こういう風にタイマーを使ってやれば、きちんと各コマが描画されるようになる。
しかしこのやり方だと、最初の値から最後の値までの全コマが必ず描画される(=フレームレート固定)ので、貧弱な環境だとものすごい遅いアニメーションになってしまう。上の例だと、10ミリ秒ごとに1ピクセルずらして、というのを移動距離の分だけ繰り返すことになってしまう。
「高速な環境ではたくさん描画していいけど、低速な環境だと再描画を減らしてほしい。とにかく、1回のアニメーションは決まった時間の中できちんと終わらせたい。」これが可変フレームレートの基本的な考え方。
可変フレームレートにする場合、描画と描画の間にどれだけ時間がかかったか、というのが鍵になる。
JavaScriptのsetIntervalのタイマーは、(少なくともGecko/SpiderMonkeyでは、)仮に実行間隔を10ミリ秒にした場合、実際には「1回目の処理の時間」+「10ミリ秒のインターバル」+「2回目の処理の時間」+「10ミリ秒のインターバル」……という風な感じで時間が過ぎていく。アニメーションにかける全体の時間が分かっているなら、「n回目の描画開始時の時点で、アニメーションにかける時間全体の何%が過ぎたか」をまず計算して、移動量をそのパーセンテージから求めてやればいい。
function doAnimation(aElement, aStart, aEnd)
{
var delta = aEnd - aStart;
var duration = 200; // アニメーション効果全体を200ミリ秒で終わらせる
var startTime = Date.now(); // (new Date()).getTime() と同等
var timer = window.setInterval(function() {
var progress = Math.min(
1,
(Date.now() - startTime) / duration
);
var margin = (progress == 1) ?
aEnd :
aStart + (delta * progress) ;
aElement.style.marginLeft = margin+'px';
if (progress == 1) {
window.clearInterval(timer);
}
}, 10);
}
これで、可変フレームレートのアニメーションになった。progress
はアニメーションの進行状況を示していて、0(開始時点=0%)から1(終了時点=100%)の間の値を取る。開始時の値と終了時の値の差にこれをかけてやれば、「今の時点ではこれだけ移動してるはず(その位置に描画すればよい)」ということが分かるワケ。(ちなみにこの時点で、値が増加していくのか減少していくのかどっちなんだ、ということも気にせずに済むようになっている。)
この時点では、値の変化率は一定なので、理科の時間に習う「等速直線運動」ってやつになっている。実際のUIでこれをそのまま使うとちょっと味気ないので、もうちょっとかっこよくしてやりたくなるところだ。可変フレームレートならそれも簡単にできる。
よくあるのは、「最初はすごい速度で飛んできて、最後はフワッと着地する」みたいな効果だろう。これは三角関数のsin()やcos()を使えば簡単に計算できる。
円と複素数平面の勉強をしたことがあれば、0°から90°の間を1°ずつ動く間に、Xの値は「最初はゆっくりと、最後は急速に」Yの値は「最初は速く、最後はゆっくり」増加していくことが分かるだろう。sin(θ)やcos(θ)を使えば、この「XやYの値の変化率」を取り出して利用できる。
例えば「最初は速く、最後はゆっくり」のアニメーションなら、上の例の(delta * progress)
の所を(delta * Math.sin( (progress * 90) * Math.PI / 180 ))
にすればいい。これで、0°から90°までの間のYの値の変化率に応じた移動量になる。(Math.sin()
やMath.cos()
は角度をラジアンで指定しないといけないので、θ×π÷180で度数をラジアンに変換している。)
以上、中学や高校で勉強する数学って案外無駄にならないんだよ、というお話でした。
ちなみに、aStart + (delta * progress)
の所あたりを任意の式に置き換えれば、動き方を等速直線運動や等加速度運動やはね回る等の色んな形に変えることができる。これを、「<ruby><rb>最初の値</rb><rp>(</rp><rt>Start</rt><rp>)</rp></ruby>・<ruby><rb>値の最終的な変化量</rb><rp>(</rp><rt>total Change</rt><rp>)</rp></ruby>・<ruby><rb>アニメーションにかけたい時間</rb><rp>(</rp><rt>Duration</rt><rp>)</rp></ruby>・<ruby><rb>現在までに過ぎた時間</rb><rp>(</rp><rt>Time</rt><rp>)</rp></ruby>(最後の2つから現在の進度が分かる)という4つのパラメータを受け取り現在の値を返す関数」として定義した物をeasing関数と呼ぶそうで、世にある多くのアニメーションライブラリは、このeasing関数を入れ替えることで色んなエフェクトを得られるようにしたもの、と言うことができる。
……という風に読み解いてみると、普段使ってるライブラリが一体何をどのように処理しているのか分かっておもしろいんじゃないでしょうか。僕はライブラリ使ってませんが。
追記。Norahさんのこれって yield 使ったらダメなんだろうか?
というコメントを見た。べつに使っちゃダメってことはないし、むしろ自分も一瞬「あれ、これyieldで書けるんじゃね?」と思ったんだけど、どっちにしろタイマーでループ回すという事には変わりないし、そうなるとここで書いてるくらいの規模だとコードの量が無駄に増えるだけって気がしたので、そのままタイマーだけで書いた次第です。
追記。easing関数の説明を間違えてたので修正しました……