OSDN Git Service

Support the rotate attribute of the animateMotion element
[sie/sie.git] / org / w3c / dom / smil.js
1 /*SIE under the MIT Lisence\r
2  */\r
3 /* Copyright 2016 dhrname and other contributors\r
4  * http://sie.osdn.jp/\r
5  *\r
6  * Permission is hereby granted, free of charge, to any person obtaining\r
7  * a copy of this software and associated documentation files (the\r
8  * "Software"), to deal in the Software without restriction, including\r
9  * without limitation the rights to use, copy, modify, merge, publish,\r
10  * distribute, sublicense, and/or sell copies of the Software, and to\r
11  * permit persons to whom the Software is furnished to do so, subject to\r
12  * the following conditions:\r
13  * \r
14  * The above copyright notice and this permission notice shall be\r
15  * included in all copies or substantial portions of the Software.\r
16  * \r
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
18  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r
20  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\r
21  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\r
22  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\r
23  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r
24  */\r
25 \r
26 /*$frame オブジェクト\r
27  * 全体のフレームの管理を行う\r
28  */\r
29 base("$frame").mix ( {\r
30   /*フレームレート。1ミリ秒何フレームか。計算を省略するためミリ秒使用*/\r
31   fpms: 0.024,\r
32 \r
33   /*タイムラインのリスト (時間区間の設定ができる)*/\r
34   timelines: [],\r
35 \r
36   /*開始フレーム数。アニメーションの開始条件となる\r
37    * 単位はフレーム数であって、秒数ではない*/\r
38   begin: 0,\r
39 \r
40   /*活動継続時間 (Active Duration)のフレーム数。アニメーションの継続条件となる\r
41    * 単位はフレーム数であって、秒数ではない*/\r
42   activeTime: Number.MAX_VALUE,\r
43   \r
44   /*現在のフレーム数*/\r
45   currentFrame: 0,\r
46   \r
47   /*アニメーションを開始させるメソッド*/\r
48   startAnimation: function() {\r
49     /*__step関数は最後に書く*/\r
50     __step();\r
51   },\r
52   \r
53   /*アニメーションが停止した状態かどうか、停止しているならばtrue*/\r
54   isPaused: false,\r
55 \r
56   /*アニメーションを停止させるメソッド*/\r
57   pauseAnimation: function() {\r
58     this.isPaused = true;\r
59   },\r
60 \r
61   /*setFrame メソッド\r
62    * フレーム数を数値num まで進めるか、戻す*/\r
63   setFrame: function( /*number*/ num) {\r
64     if((num < this.begin) || (num >= (this.begin+this.activeTime))) {\r
65       return;\r
66     }\r
67     this.currentFrame = num;\r
68     var timelines = this.timelines;\r
69     for (var i=0;i<timelines.length;++i) {\r
70       if (timelines[i] === this) {\r
71         /*自分自身が含まれていると、永久に再帰を繰り返して、「スタック領域が不足」してしまう*/\r
72         continue;\r
73       }\r
74       timelines[i].setFrame(num);\r
75     }\r
76   },\r
77   \r
78   /*addLineメソッド\r
79    * タイムラインを追加したあと、trueを返す\r
80    * ただし、引数objのobj.beginとobj.activeTimeが定まっていない場合はfalseを返す*/\r
81   addLine: function( /*$frame*/ obj ) {\r
82     if(!obj || (!obj.begin && (obj.begin !== 0))\r
83     || (!obj.activeTime && (obj.activeTime !== 0)) ) {\r
84       /*どちらのプロパティも未確認の場合、タイムラインは追加されない*/\r
85       return false;\r
86     }\r
87     var timelines = this.timelines;\r
88     if ( timelines.indexOf(obj) >= 0 ) {\r
89       this.removeLine(obj);\r
90     }\r
91     timelines.push(obj);\r
92     timelines = void 0;
93     return true;\r
94   },\r
95   \r
96   /*removeLine メソッド\r
97    * 指定されたタイムラインのオブジェクトを、リストから削除する*/\r
98   removeLine: function( /*$frame*/ timeline ) {\r
99     var list = this.timelines,\r
100         j = list.indexOf(timeline);\r
101     if (j > -1) {\r
102       list.splice(j, 1);      //Arrayのspliceを利用して、リストからtimelineを排除\r
103     }\r
104     list = j = void 0;\r
105   }\r
106 } ).mix( function($frame) {\r
107   /*$endFrame オブジェクト\r
108    * 終了時の処理をするためのフレームを集める*/\r
109   $frame.up("$endFrame").mix( {\r
110     timelines: []\r
111   } );\r
112    \r
113   /*$begin オブジェクト\r
114    * 開始のタイミングを計算する*/\r
115   $frame.up("$begin").mix( {\r
116 \r
117     /*開始時刻やタイミングが書かれた文字列*/\r
118     string: "",\r
119 \r
120     /*イベントやindefinteで未解決かどうか*/\r
121     isResolved: false,\r
122     \r
123     /*イベントが適用される要素*/\r
124     eventTarget: document.documentElement,\r
125     \r
126     /*現在のフレーム数を改めて初期化*/\r
127     currentFrame: 0,\r
128     \r
129     /*イベント同期で使う時間差のフレーム数*/\r
130     eventOffset: 0,\r
131     \r
132     /*repeat(1)など文字列内に書かれたリピート回数*/\r
133     repeat: 0,\r
134     \r
135     /*accessKey(a)の"a"などキーイベントの対象となる文字列*/\r
136     accessKey: "",\r
137 \r
138     /*trim メソッド\r
139      * 文字列中の空白を除去*/\r
140     trim: function(str) {\r
141       /*strがString型以外のときは必ずエラーを出す*/\r
142       return str.replace(/[\s\n]+/g, "");\r
143     },\r
144 \r
145     /*offset メソッド\r
146      * 引数に渡された文字列から、ミリ秒単位に変換した時間を、解析して返す*/\r
147     offset: function(str) {\r
148       str = str || "0";\r
149       var plusminus = str.charAt(0),\r
150           /*parseFloatのエイリアス*/\r
151           _float = parseFloat,\r
152           s = _float( str.match(/[\d.]+ms$/) || "0") + sec() + min() + h();\r
153       if (plusminus === "-") {\r
154         s *= -1;\r
155       }\r
156       plusminus = _float = sec = min = h = void 0;\r
157       return s;\r
158       \r
159       /*00:00:0と00:0と、0sなどの文字列をミリ秒へ変換*/\r
160       function sec() {\r
161         return str2num( 1000, /[\d.]+s$/, /[\d.]+$/ );\r
162       };\r
163       function min() {\r
164         return str2num( 60000, /[\d.]+min$/, /\d\d:[^:]+$/ );\r
165       };\r
166       function h() {\r
167         return str2num( 3600000, /\d+:\d\d:/, /[\d.]+h$/ );\r
168       };\r
169       function str2num(s, /*RegExp*/ a, /*RegExp*/ b) {\r
170         return s*( _float(str.match(a) || "0") || _float(str.match(b) || "0") );\r
171       };\r
172     },\r
173 \r
174     /*event メソッド\r
175      * 引数の文字列から、idとイベントに相当する文字列のプロパティを持ったオブジェクトを返す\r
176      * idがない場合や、イベントがない場合は空文字列を該当のプロパティに入れる*/\r
177     event: function(str) {\r
178       str = str || "";\r
179       if (/[\+\-]/.test(str)) {\r
180         /*数値がある場合は切り取っておく*/\r
181         str = str.slice(0, str.search(/[\+\-]/));\r
182       }\r
183       if (str.indexOf(".") > -1) {\r
184         /*ドットが見つかった場合、IDとイベントに分けておく*/\r
185         var ide = str.split(".");\r
186         /* エラーが起きて、idが空文字列ならば、evtも空文字列。逆も然り*/\r
187         return {\r
188           id: (ide[1] && ide[0]),\r
189           event: (ide[0] && ide[1])\r
190         };\r
191       } else {\r
192         return {\r
193           id: "",\r
194           event: str\r
195         };\r
196       }\r
197     },\r
198     \r
199     /*parse メソッド\r
200      * stringプロパティを解析して、フレーム数を算出し、結果を$frame.beginプロパティに出力\r
201      * また、イベントリスナーに登録をしておく*/\r
202     parse: function() {\r
203       /*初期値を設定*/\r
204       this.begin = 0;\r
205       this.isResolved = false;\r
206       var str = this.trim(this.string),\r
207           plusminus = str.search(/[\+\-]/),\r
208           event = null,\r
209           ele;\r
210       if (str === "indefinite") {\r
211         this.begin = Number.MAX_VALUE;\r
212       } else if (plusminus > 0) {\r
213         /*Event-Value +/- Clock-Value の場合*/\r
214         this.begin = this.offset( str.slice(plusminus) );\r
215         event = this.event(str);\r
216       } else if ( /[^\+\-\d]/.test(str.charAt(0)) ) {\r
217         /*Event-Valuen のみの場合*/\r
218         event = this.event(str);\r
219       } else {\r
220         /*+/- Clock-Value のみの場合*/\r
221         this.begin = this.offset( str );\r
222         /*イベントもindefiniteもないので、解決済みと考える*/\r
223         this.isResolved = true;\r
224       }\r
225       /*もしもあれば、リピートの回数を求める*/\r
226       this.repeat = /repeat\((\d+)\)/.test(str) ? +RegExp.$1 : 0;\r
227       /*もしもあれば、押されるはずのキーを求める*/\r
228       this.accessKey = /accessKey\(([^\)]+?)\)/.test(str) ? RegExp.$1 : "";\r
229       this.begin = Math.floor( this.begin * this.fpms);\r
230       if (str === "indefinite") {\r
231         /*begin属性の値がindefiniteの場合は、何もしない。\r
232          * 開始時刻はlistenerメソッドの呼び出しか、beginElementメソッドに依存*/\r
233       } else if (event) {\r
234         ele = event.id ? this.eventTarget.ownerDocument.getElementById(event.id)\r
235                         : this.eventTarget;\r
236         /*イベントの時間差を設定しておく*/\r
237         this.eventOffset = this.begin;\r
238         if (this.repeat > 0) {\r
239           ele && ele.addEventListener("repeatEvent", (function(evt) {\r
240             if (evt.detail === this.repeat) {\r
241               this.listener(evt);\r
242             } }).bind(this), true);\r
243         } else if (this.accessKey) {\r
244           document.documentElement.addEventListener("keydown", (function(evt) {\r
245             if (evt.char === this.accessKey) {\r
246                 this.listener(evt);\r
247               } }).bind(this), false);\r
248         } else {\r
249           var evtName = /^(?:begin|end|repeat)$/.test(event.event) ? event.event + "Event"\r
250                           : event.event;\r
251           ele && ele.addEventListener(evtName, this.listener.bind(this), false);\r
252         }\r
253       } else {\r
254         /*イベントの影響を防ぐため\r
255          * すでに、フレームオブジェクトのタイムラインには登録済みなので、\r
256          * フレームではなく、自分独自のタイムラインに登録しておけばよい*/\r
257         this.$frame = this;\r
258       }\r
259       s = event = str = plusminus = ele = void 0;\r
260       return this;\r
261     },\r
262     \r
263     /*イベントのリスナーとして、parseメソッドで使う*/\r
264     listener: function(evt) {\r
265       this.begin = this.eventOffset + this.$frame.currentFrame;\r
266       var s = this.$activate;\r
267       s.begin = this.begin;\r
268       this.activeTime = s.call() || Number.MAX_VALUE;\r
269       this.simpleDuration = s.simpleDur;\r
270       s = void 0;\r
271       this.$frame.addLine(this);\r
272       this.isResolved = true;\r
273     }\r
274     \r
275   /*$activate オブジェクト\r
276    * 活動継続時間などを計算するための計算実体\r
277    * $begin オブジェクトからの継承*/\r
278   } ).up("$activate").of( {\r
279     \r
280     /*単純継続時間のパースされる前の文字列*/\r
281     dur: "indefinite",\r
282     \r
283     /*活動をストップさせるためのオブジェクト*/\r
284     end: $frame.$begin.up("$end"),\r
285 \r
286     /*リピート回数*/\r
287     repeatCount: null,\r
288     \r
289     /*繰り返し時間*/\r
290     repeatDur: null,\r
291 \r
292     /*単純継続時間 (単位はフレーム数)*/\r
293     simpleDur: function() {\r
294       return ( (this.dur === "indefinite") || !this.dur ) ?\r
295                 null\r
296               : Math.floor(this.offset(this.dur) * this.fpms) ;\r
297     },\r
298 \r
299     /*最小値に制限される\r
300      * 最小値 <= 活動継続時間 とならなければならない*/\r
301      min: "0",\r
302      \r
303     /*最大値に制限される\r
304      * 活動継続時間 <= 最大値 とならなければならない*/\r
305      max: "indefinite",\r
306     \r
307     /*解決した(計算する)ときの時間*/\r
308     resolvedTime: function() {\r
309       return Date.now();\r
310     },\r
311     \r
312     /*関数型の呼び出しメソッド\r
313      * base.jsのofメソッドを活用して活動継続時間を算出\r
314      * 計算方法はSMILアニメーション 3.3.4節を参照\r
315      * http://www.w3.org/TR/smil-animation/#ComputingActiveDur\r
316      */\r
317     call: function() {\r
318       var ind = "indefinite",\r
319           dur = this.simpleDur,\r
320           isIndefRepeatCount = (this.repeatCount === ind),\r
321           isIndefRepeatDur = (this.repeatDur === ind),\r
322           isIndefEnd = (this.end === ind),\r
323           isDur = dur || (dur === 0),\r
324           isEnd = this.end || (this.end === 0),\r
325           isRepeatCount = this.repeatCount || (this.repeatCount === 0),\r
326           isRepeatDur = this.repeatDur || (this.repeatDur === 0),\r
327           actList = [],\r
328           min = Math.floor(this.offset(this.min) * this.fpms),\r
329           max = (this.max === ind) ? null : Math.floor(this.offset(this.max) * this.fpms),\r
330           s;\r
331       if (indef()) {\r
332         /*注意点として、活動継続時間が不定かつ、min属性とmax属性が指定されたときの記述が仕様にないため、\r
333          * W3Cのテストスイート(animate-elem-66-t.svg)に従い、max属性の値を返す*/\r
334         if (max) {\r
335           return max;\r
336         }\r
337         return null;\r
338       }\r
339       if (isDur && this.repeatCount && !isIndefRepeatCount) {\r
340         actList.push( dur * this.repeatCount );\r
341       }\r
342       if (isRepeatDur && !isIndefRepeatDur) {\r
343         actList.push( Math.floor( this.offset(this.repeatDur) * this.fpms) );\r
344       }\r
345       if (isEnd && !isIndefEnd) {\r
346         actList.push( this.end - this.begin );\r
347       }\r
348       if (isDur && !isRepeatCount && !isRepeatDur) {\r
349         /*repeatCountやrepeatDur属性が指定されていない場合*/\r
350         actList.push( dur );\r
351       }\r
352 \r
353       /*長くなるため、インライン関数を活用\r
354        * indef関数は活動継続時間が不定かどうか、もし、不定なら真を返す*/\r
355       function indef() {\r
356         if(isIndefEnd) {\r
357           return true;\r
358         } else if (isEnd) {\r
359           return false;\r
360         }\r
361         return !!( (!isDur && !isRepeatDur)\r
362                    || (isIndefRepeatCount && !isRepeatDur)\r
363                    || (isIndefRepeatDur && !isRepeatCount)\r
364                    || (isIndefRepeatCount && isIndefRepeatDur)\r
365                  );\r
366       };\r
367       \r
368       ind = dur = isIndefRepeatCount = isIndefRepeatDurindef = isDur = isEnd = isRepeatDur = isRepeatCount = indef = void 0;\r
369 \r
370       if (actList.length === 1) {\r
371         s = actList[0];\r
372       } else if(actList.length > 1) {\r
373         /*属性が競合するときは、最小値をとること (SMILアニメーション 3.3.4節)*/\r
374         s = Math.min.apply(Math, actList);\r
375       } else {\r
376         return null;\r
377       }\r
378       if ( max && (min > max) ) {\r
379         return s;\r
380       }\r
381       min && (min > s) && (s = min);\r
382       max && (max < s) && (s = max);\r
383       return s;\r
384     }\r
385   } );\r
386   $frame.$begin.$end.of( {\r
387     call: function() {\r
388       if (!this.string) {\r
389         return null;\r
390       }\r
391       this.parse(this.string);\r
392       return this.isResolved ? this.begin\r
393                              : "indefinite";\r
394     }\r
395   } ).mix( {\r
396     /*イベントリスナー用の関数*/\r
397     listener: function(evt) {\r
398       if (this.begin <= 0) {\r
399         /*強制的に終了させる*/\r
400         this.removeLine(this.$begin);\r
401       }\r
402       this.begin = this.eventOffset + this.$frame.currentFrame;\r
403       var s = this.$begin.$activate;\r
404       s.end = this.begin;\r
405       /*未解決だったendの値が、イベントの発生により、解決して再定義されたとき、\r
406        * $activateオブジェクトを使って活動継続時間を再計算する*/\r
407       this.$begin.activeTime = s.call();\r
408       this.isResolved = true;\r
409       s = void 0;\r
410     }\r
411   } );\r
412 } );\r
413 /*$from オブジェクト\r
414  * 呈示値 (presentation value)の計算をする。値そのものを返すための計算実体*/\r
415 base("$from").of( {\r
416   /*呈示値が書かれた文字列*/\r
417   string: "",\r
418   \r
419   /*呈示値の数値の部分だけを抜き出した配列を返す*/\r
420   numList: function() {\r
421     var s  = this.string.match(/[\-\+]?[\d\.]+(?:[eE][\-\+]?[\d\.]+)?/g)\r
422              || [];\r
423     if (s) {\r
424       /*mapメソッドで代用してもよい*/\r
425       for (var i=0;i<s.length;++i) {\r
426         s[i] = parseFloat(s[i]);\r
427       }\r
428     }\r
429     return s;\r
430   },\r
431   \r
432   /*呈示値の文字部分だけを抜き出した配列を返す*/\r
433   strList: function() {\r
434     /*replaceメソッドで1E-10などの対策*/\r
435     return this.string.replace(/\d[eE][\-\+\d]/g, "")\r
436                       .match(/[^\d\-\+\.]+/g);\r
437   },\r
438   \r
439   from: base("$from").up().mix( {\r
440           from: null\r
441         } ),\r
442   \r
443   /*$toオブジェクトにこのオブジェクトを適用させる関数*/\r
444   call: function() {\r
445     /*advanceメソッドで使うために、stringを保持*/\r
446     this.numList.string = this.string;\r
447     if (this.numList.length\r
448           && (this.additive[0] === 0)\r
449           && (this.accumulate[0] === 0)\r
450           ) {\r
451       /*配列の項目がundefinedだと困るので、配列を初期化する*/\r
452       var additive = [],\r
453           accumulate = [];\r
454       for (var i=0;i<this.numList.length;++i) {\r
455         /*0で配列を初期化しておく*/\r
456         additive[i] = accumulate[i] = 0;\r
457       }\r
458       this.additive = additive;\r
459       this.accumulate = accumulate;\r
460     }\r
461     /*文字部分の配置パターンは4通りあるので、ここでstrListを処理\r
462      * (1) a 0 の場合\r
463      * (2) 0 a\r
464      * (3) a 0 a (ノーマルパターン)\r
465      * (4) 0 a 0\r
466      * これらのパターンのうち、(1)(2)(4)を(3)のパターンに統一したのが以下の処理*/\r
467     /*文字列が1aのように、数値で始まるかどうか。始まったら真*/\r
468     if (!this.string || !this.numList.length || !this.strList) {\r
469       return this.numList;\r
470     }\r
471     var isNormal = (this.numList.length < this.strList.length);\r
472     if (/^[\-\+]?[\d\.]/.test(this.string) && !isNormal) {\r
473       /*文字列が1aのように、数値で始まる場合*/\r
474       this.strList.unshift("");\r
475     }\r
476     if (/\d$/.test(this.string) && !isNormal) {\r
477       /*文字列がa1のように、数値で終わる場合*/\r
478       this.strList.push("");\r
479     }\r
480     return this.numList;\r
481   }\r
482     \r
483 } )\r
484  .mix( {\r
485    /*advanceメソッドの返り値で使われる小数点以下の桁数*/\r
486    degit: 0,\r
487    \r
488    /*additve属性やaccumulate属性が設定された、累積アニメーションか、加法アニメーションで使われる*/\r
489    additive: [0],\r
490    accumulate: [0],\r
491    \r
492    /*advance メソッド\r
493     * アニメーションの進行具合を示す進捗率 t (0 <= t <= 1)をもとに、現在の呈示値を算出するためのもの\r
494     * callメソッドが前もって呼び出されていることが前提となる*/\r
495     advance: function(t) {\r
496       if ( (t < 0) || (1 < t)) {\r
497         throw new Error("An Invalid Number Error");\r
498       }\r
499       if (!this.string) {\r
500         return "";\r
501       } else if (!this.from.length) {\r
502         /*discreteのために、this.stringに数値が入っていない場合の対処*/\r
503         if (t === 1) {\r
504           return this.string.trim();\r
505         }\r
506         return this.from.string.trim();\r
507       }\r
508       var str = "",\r
509           numList = this.numList,\r
510           strList = this.strList,\r
511           fromNumList = this.from,\r
512           deg = this.degit,\r
513           additive = this.additive,\r
514           accumulate = this.accumulate;\r
515       for (var i=0,nuli=numList.length;i<nuli;++i) {\r
516         /*原点Oを(0,0,...0)とおく\r
517          *$fromと$toを、原点Oからの二つのベクトル (n次空間のベクトル)、ベクトルOFとベクトルOTと考える\r
518          *$fromと$toの二つの端の点FとTを結ぶ線分を、t : 1-t で内分する点をPとおく\r
519          * このときのベクトルOPを求めたのが以下の式*/\r
520         str += ( t * numList[i] + (1 - t) * fromNumList[i] + additive[i] + accumulate[i]).toFixed(deg);\r
521         strList && ( str += strList[i+1] );\r
522       }\r
523       /*文字列はcallメソッドにより、a0aのパターンになっているので、aの部分を追加*/\r
524       str = (strList ? strList[0] : "") + str;\r
525       numList = strList = fromNumList = i = nuli = deg = additive = accumulate = void 0;\r
526       return str.trim();\r
527     },\r
528     \r
529     /*distanceメソッド\r
530      * fromベクトルから自分自身のベクトルへの距離 (ノルム)の数値を返す。callメソッドを使うので注意すること*/\r
531      distance: function(from) {\r
532        if (!from) {\r
533           return 0;\r
534        }\r
535        var toList = this.call(),\r
536            fromList = from.call ? from.call() : from,\r
537            s = 0;\r
538        if (!toList || !fromList) {\r
539          return 0;\r
540        }\r
541        for (var i=0, tli=toList.length; i<tli; ++i) {\r
542          s += (toList[i] - fromList[i])*(toList[i] - fromList[i]);\r
543        }\r
544        return Math.sqrt(s);\r
545      },\r
546      \r
547      /*setAdditive メソッド\r
548       * additve属性がsumのときに使われるメソッド\r
549       * 引数は親要素の、現在の属性値*/\r
550       setAdditive: function(str) {\r
551         if (!str) {\r
552           return 0;\r
553         }\r
554         var from = this.$from.up();\r
555         from.string = str;\r
556         return ( this.additive = from.call() );\r
557       },\r
558      \r
559      /*setAccumulate メソッド\r
560       * accumulate属性がsumのときに使われるメソッド\r
561       * 引数は現在のリピート回数*/\r
562       setAccumulate: function(num) {\r
563         if (!num || isNaN(num)) {\r
564           return 0;\r
565         }\r
566         return ( this.accumulate = this.numList.map( function(d) {\r
567           return d * num;\r
568         } ) );\r
569       }\r
570   } )\r
571   /*fromプロパティの初期化*/\r
572  .up("$to").from = null;\r
573  \r
574  /*計算モードを定めるための計算実体\r
575   *補間の細かい制御などを行う*/\r
576  base("$calcMode").mix({\r
577    /*計算モード (calcMode属性の値)*/\r
578    mode: "linear",\r
579 \r
580    /*keyTimesの区間\r
581     * たとえば、"0, 0.5, 0.7, 1"の場合、時間の区間はそれぞれ、0.5 (=0.5-0)  0.2 (=0.7-0.5)  0.3 (=1-0.7)である\r
582     * このうち、どれか一つが値として入力される*/\r
583    keyTime: 1,\r
584    \r
585    /*keySpline属性の値を設定*/\r
586    keySplines: null,\r
587 \r
588    /*callメソッドで使う関数\r
589     * 進捗率を時間の圧縮率( = keyTimeプロパティ)で割ることで、現在、どこまで進んでいるのかを求めることができる*/\r
590    _f: function (t) {\r
591          /*tは進捗率*/\r
592          var tkey = this.keyTime;\r
593          if ( (tkey === 0) && t) {\r
594            t = 0; \r
595          } else if (!tkey || !isFinite(tkey) ) {\r
596            return this.string;\r
597          } else {\r
598            t = t / tkey;\r
599            t = (t > 1) ? Math.floor(t) : t;\r
600          }\r
601          tkey = void 0;\r
602          return isNaN(t) ? this.string\r
603                          : this.to.advance(t);\r
604      },\r
605      \r
606      /*discreteモードのときには、上記の_f関数の代わりに、以下の関数を用いる*/\r
607      funcForDiscrete: function (t) {\r
608         if (isNaN(t)) {\r
609           return this.string;\r
610         } else if (t === 1) {\r
611           return this.to.string;\r
612         } else {\r
613           return this.to.advance(0);\r
614         }\r
615       }\r
616  }).of( {\r
617    \r
618    /*全体の行列ノルム(距離)*/\r
619    norm: 1,\r
620 \r
621    /*無効だった場合の呈示値*/\r
622    string: "",\r
623    \r
624    /*与えられたアニメーションの進捗率を使った時間の圧縮率を計算して呈示値を返すための関数を作る*/\r
625    call: function() {\r
626      var f = this._f.bind(this);\r
627      if (this.mode === "linear") {\r
628        this.to.call();\r
629        return f;\r
630      } else if (this.mode === "paced") {\r
631        /*keyTimes属性は無視され、ベクトルの距離の割合から計算される*/\r
632        this.keyTime = this.to.distance(this.to.from) / this.norm;\r
633        return f;\r
634      } else if (this.mode === "spline") {\r
635        var tk = this.keySplines,\r
636            /*必ず関数を返すようにするため、円周率を返す関数tfを返して、nullの代わりとする*/\r
637            tf = function(x) {\r
638                  return Math.PI;\r
639            };\r
640       tf.isNotAnimate = true;\r
641       if (!tk) {\r
642          return tf;\r
643        }\r
644       for (var i=0,tki = NaN;i<tk.length;++i) {\r
645        tki = tk[i];\r
646        if (isNaN(tki)) {\r
647          return tf;\r
648        }\r
649        if ( (tki < 0) || (1 < tki)) {\r
650          return tf;\r
651        }\r
652      }\r
653      this.to.call();\r
654      var x2 = tk[0],\r
655          y2 = tk[1],\r
656          x3 = tk[2],\r
657          y3 = tk[3],\r
658          x4 = 1,\r
659          y4 = 1,\r
660          Ax = x4-3*(x3-x2),\r
661          Bx = 3*(x3-2*x2),\r
662          Cx = 3*x2,\r
663          Ay = y4-3*(y3-y2),\r
664          By = 3*(y3-2*y2),\r
665          Cy = 3*y2,\r
666          _newton = Math.qubicnewton; //高速化のためのエイリアス\r
667      if ( ( (x2 === 0) || (x2 === 1) )\r
668           && (y2 === 0)\r
669           && ( (x3 === 1) || (x3 === 0) )\r
670           && (y3 === 1) ) {\r
671             /*linearモードと同じ効果 (収束ではない可能性を考慮)*/\r
672             this.to.call();\r
673             return f;\r
674      }\r
675      var tkey = this.keyTime;\r
676      if (tkey || isFinite(tkey) ) {\r
677        /*keyTimeから時間の収縮率を3次ベジェ曲線に適用しておく*/\r
678        Ax *= tkey;\r
679        Bx *= tkey;\r
680        Cx *= tkey;\r
681        Ay *= tkey;\r
682        By *= tkey;\r
683        Cy *= tkey;\r
684      }\r
685      tkey = tk = x2 = y2 = x3 = y3 = x4 = y4 = void 0;\r
686      return function (x) {\r
687         /*3次ベジェ曲線は媒介曲線\r
688          *x = (x4-3*(x3-x2)-x1)*t*t*t + 3*(x3-2*x2+x1)*t*t + 3*(x2-x1)*t + x1\r
689          *y = (y4-3*(y3-y2)-y1)*t*t*t + 3*(y3-2*y2+y1)*t*t + 3*(y2-y1)*t + y1\r
690          * ただし、0 <= t <= 1\r
691          * スプラインモードの場合、x1 = y1 = 0, x4 = y4 = 1\r
692          * ベジェ曲線のxの式が三次方程式であるため、その解 t から、ベジェ曲線の y を求める\r
693          * なお、ニュートン法の初期値はxとする\r
694          * なぜなら、xの式をみると、xが増加傾向となるスプラインモードでは、係数が負となる可能性が低いため*/\r
695         var t = _newton(Ax, Bx, Cx, -x, x);\r
696         return f(Ay*t*t*t + By*t*t + Cy*t);\r
697       };\r
698     } else if (this.mode === "discrete") {\r
699       this.to.call();\r
700       return this.funcForDiscrete.bind(this);\r
701     }\r
702   }\r
703 } ).to = base("$from").$to;\r
704 \r
705 \r
706 /*ニュートン法により、三次方程式 a0x^3 + a1x^2 + a2x + a3 の解を求める\r
707  * 引数bは初期値*/\r
708 Math.qubicnewton = function(a0, a1, a2, a3, b) {\r
709   var eps = 1e-15,                          //収束誤差\r
710       fb = a0 *b*b*b + a1 *b*b + a2*b + a3; //方程式の結果\r
711   if (fb === 0) {\r
712     return b;\r
713   }\r
714   /*限界の収束回数は100回*/\r
715   for (var i=0;i<100;i=(i+1)|0) {\r
716     /*数値nは与えられた三次方程式を微分したもの*/\r
717     var n = 3* a0 *b*b + 2 * a1 *b + a2;\r
718     if (!n || ( (fb < eps) && (fb > -eps) )) {\r
719       fb = eps = void 0;\r
720       return b;\r
721     } else {\r
722       /*以下は収束の漸化式*/\r
723       b =  b - fb / n;\r
724       fb = a0 *b*b*b + a1 *b*b + a2*b + a3;\r
725     }\r
726   }\r
727   return b; //収束しなかった結果\r
728 };\r
729 \r
730 /*$attribute オブジェクト\r
731  * アニメーションの時間調整と、呈示値の調整を一つのオブジェクトにまとめて行うことで、\r
732  * アニメーションサンドイッチの実装をする\r
733  * $calcModeオブジェクトから継承*/\r
734 base("$calcMode").up("$attribute").mix( {\r
735   \r
736   /*アニメーションの対象となる要素。たとえば、animate要素の親要素*/\r
737   element: null,\r
738   \r
739   /*$fromオブジェクトを作るためのひな形となるオブジェクト*/\r
740   $from: base("$from").up(),\r
741   \r
742   /*attributeName属性の値*/\r
743   attrName: "",\r
744   \r
745   /*属性の名前空間*/\r
746   attrNameSpace: null,\r
747   \r
748     /*指定された属性の規定値*/\r
749   defaultValue: "",\r
750   \r
751   /*もともと属性がターゲットの要素につけられていたかどうか*/\r
752   isDefault: false,\r
753   \r
754   /*attributeType属性がCSSだったときや、autoで属性名がCSSプロパティ名だったときには、true*/\r
755   isCSS: false,\r
756   \r
757   /*計算モードdicreteやsplineなど*/\r
758   mode: "linear",\r
759   \r
760   /*指定した要素の属性値を取得するメソッド*/\r
761   getAttr: function(/*string*/ name, def) {\r
762     var nameSpace = null;\r
763     if (name.indexOf("xlink:") > -1) {\r
764       nameSpace = "http://www.w3.org/1999/xlink";\r
765     }\r
766     var s = this._ele.getAttributeNS(nameSpace, name);\r
767     if (this.element) {\r
768         var view = this.element.ownerDocument.defaultView;\r
769       if (s === "inherit") {\r
770         return view.getComputedStyle(this.element.parentNode, "").getPropertyValue(this.attrName);\r
771       } else if (s === "currentColor") {\r
772         return view.getComputedStyle(this._ele, "").getPropertyValue("color");\r
773       }\r
774     }\r
775     /*DOM Level2やIE11では、getAttributeNSメソッドは空文字列を返す。他のブラウザではnullを返すことが多い\r
776      * \r
777      * >the empty string if that attribute does not have a specified or default value\r
778      * http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-ElGetAttrNS*/\r
779     return (s || def);\r
780   },\r
781   \r
782   _ele: document.documentElement,\r
783   \r
784   /*指定された要素にvalues属性が付いているかどうかのチェックできるメソッド*/\r
785   hasAttrValues: function () {\r
786     var ele = this._ele;\r
787     if (!ele) {\r
788       return false;\r
789     } else {\r
790       return ( ele.hasAttribute("from") || ele.hasAttribute("to")\r
791          || ele.hasAttribute("by") || ele.hasAttribute("values") );\r
792     }\r
793   },\r
794   \r
795   /*後述のsetAttributeメソッドで使うキャッシュ\r
796    * これを使うことで、二度同じ値を設定せずにすむので、高速化できる*/\r
797   __cacheAttr: "",\r
798   \r
799   /*アニメーションするために、対象の属性やプロパティを変化させるメソッド*/\r
800   setAttribute: function (value) {\r
801     var attrName = this.attrName;\r
802     if (!attrName) {\r
803       return;\r
804     }\r
805     if (this.__cacheAttr === value) {\r
806       /*同じ値であれば、再設定しない*/\r
807       return;\r
808     } else {\r
809       this.__cacheAttr = value;\r
810     }\r
811     var ele = this.element;\r
812     if (this.isCSS) {\r
813       /*スタイルシートのプロパティを上書きしておく*/\r
814       ele.style.setProperty(attrName, value, "");\r
815     } else {\r
816       ele.setAttributeNS(this.attrNameSpace, attrName, value);\r
817     }\r
818     value = attrName = ele = void 0;\r
819   },\r
820   \r
821   /*setAttributeメソッドとは逆の効果で、無効化させるメソッド*/\r
822   removeAttribute: function () {\r
823     var attrName = this.attrName;\r
824     if (!attrName) {\r
825       return;\r
826     }\r
827     var ele = this.element;\r
828     if (this.isDefault) {\r
829       this.setAttribute(this.defaultValue);\r
830     } else {\r
831       /*初期段階でターゲットの要素に属性が指定されていない場合は、\r
832        * 現在の属性値を削除するだけでよい*/\r
833       ele.removeAttributeNS(this.attrNameSpace, attrName);\r
834       /*スタイルシートのプロパティも削除しておく。removePropertyでないことに注意*/\r
835       this.isCSS && ele.style.setProperty(attrName, this.defaultValue, "");\r
836     }\r
837     this.__cacheAttr = "";\r
838     value = attrName = ele = void 0;\r
839   },\r
840 \r
841   /*引数で指定した要素 ele の属性を解析して、フレームに追加する*/\r
842   push: function(/*Element Node*/ ele) {\r
843     if (!ele || !ele.hasAttribute) {\r
844       return null;\r
845     }\r
846     /*キャッシュを初期化しておく*/\r
847     this.__cacheAttr = "";\r
848     this.element = ele.parentNode || null;\r
849     var id;\r
850     if ( id = ele.getAttributeNS(null, "targetElement") ) {\r
851       this.element = ele.ownerDocument.getElementById(id);\r
852     }\r
853     /*getAttributeNSメソッドでうまくいかなかったため、NSなしで代用*/\r
854     if ( id = ele.getAttribute("xlink:href") ) {\r
855       this.element = ele.ownerDocument.getElementById(id.slice(1));\r
856     }\r
857    /*getAttrメソッドとhasAttrValuesメソッドで必要*/\r
858     this._ele = ele;\r
859     if (!this.hasAttrValues()) {\r
860       /*from属性、to、by、values属性が指定されていない場合、アニメーションの効果が出ないように調整する\r
861        *SMILアニメーションの仕様を参照\r
862        *\r
863        *>if none of the from, to, by or values attributes are specified, the animation will have no effect\r
864        *「3.2.2. Animation function values」より引用\r
865        *http://www.w3.org/TR/2001/REC-smil-animation-20010904/#AnimFuncValues\r
866       */\r
867       return null;\r
868     }\r
869     \r
870     /*属性値の設定*/\r
871     this.attrName = this.getAttr("attributeName", "");\r
872     var attrName = this.attrName;\r
873     \r
874     /*属性タイプの設定*/\r
875     var attrType = this.getAttr("attributeType", "auto");\r
876     var computedStyle = this.element && this.element.ownerDocument.defaultView.getComputedStyle(this.element, "");\r
877     if ( (attrType === "CSS")\r
878          || ( (attrType === "auto") && this.element \r
879              && computedStyle.getPropertyValue(attrName)\r
880              && !/^(width|height|transform)$/.test(attrName)\r
881             )\r
882         ) {\r
883         this.isCSS = true;\r
884     }\r
885     \r
886     /*xlink関連の属性のときは名前空間を変更しておく*/\r
887     if (attrName.indexOf("xlink") > -1) {\r
888       this.attrNameSpace = "http://www.w3.org/1999/xlink";\r
889     }\r
890     /*thiseleはターゲットとなる要素(たとえば、親要素)*/\r
891     var thisele = this.element;\r
892     if (thisele) {\r
893       /*規定値を設定しておく。属性を初期化するときに使われる*/\r
894       this._ele = thisele;\r
895       this.isDefault = thisele.hasAttributeNS(this.attrNameSpace, attrName);\r
896       this.defaultValue = this.getAttr(attrName,\r
897        computedStyle.getPropertyValue(attrName) );\r
898       this._ele = ele; /*_eleプロパティを元に戻しておく*/\r
899     }\r
900     /*eleの属性の値を、それぞれオブジェクトに割り当て*/\r
901     var $frame = base("$frame"),\r
902         begin = $frame.$begin,\r
903         frame = begin.up().mix( {\r
904                   /*targetプロパティはbeginEventなどの発火で使う*/\r
905                   target: ele,\r
906                   eventTarget: (this.element || begin.eventTarget),\r
907                   string: this.getAttr("begin", "0"),\r
908                   $activate: begin.$activate.up().mix( {\r
909                     dur: this.getAttr("dur", null),\r
910                     end: begin.$end.up().mix( {\r
911                           eventTarget: (this.element || begin.eventTarget),\r
912                           string: this.getAttr("end", null)\r
913                         } ),\r
914                     repeatCount: this.getAttr("repeatCount", null),\r
915                     repeatDur: this.getAttr("repeatDur", null),\r
916                     min: this.getAttr("min", "0"),\r
917                     max: this.getAttr("max", "indefinite")\r
918                   } )\r
919                 } ).parse();\r
920     frame.$activate.end.$begin = frame;\r
921     /*beginElementメソッドを追加*/\r
922     function eleMethod (obj, eventName) {\r
923       return (obj.string !== "indefinite") ? function(){}\r
924                         : function() {\r
925                             obj.listener( {\r
926                               /*アニメーションの開始をこのメソッドが呼ばれた時点とする*/\r
927                               timeStamp: Date.now()\r
928                             } );\r
929                             var evt = this.ownerDocument.createEvent("MouseEvents");\r
930                             evt.initMouseEvent(eventName + "Event" ,true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, this);\r
931                             this.dispatchEvent(evt);\r
932                           };\r
933     };\r
934     ele.beginElement = eleMethod(frame, "begin");\r
935     /*endElementメソッドを追加*/\r
936     ele.endElement = eleMethod(frame.$activate.end, "end");\r
937     if (frame.isResolved) {\r
938       /*開始時間が初期化されてしまうのを防ぐ*/\r
939       var cacheBegin = frame.begin;\r
940       frame.listener( {\r
941         /*アニメーションの開始をこのメソッドが呼ばれた時点とする*/\r
942         timeStamp: Date.now()\r
943       } );\r
944       frame.begin = cacheBegin;\r
945     }\r
946     /*setFrameメソッドを使ったときの、再帰スタックの使いすぎを防ぐため*/\r
947     frame.timelines = [];\r
948     begin = ele = id = void 0;\r
949     return frame;\r
950   },\r
951   \r
952   /*setValuesメソッド\r
953    * values属性やfrom属性やto属性を処理するためのメソッド\r
954    * valuesは配列、それ以外の引数は文字列\r
955    * 返り値は、values属性は配列、それ以外の属性のときは、\r
956    * 自分自身となる$attributeオブジェクトのコピーを返す*/\r
957    setValues: function(values, from, to, by) {\r
958      var $from = this.$from,\r
959          s = [this.up().mix( {\r
960                to: $from.up().mix( {\r
961                  from: $from.up()\r
962                } )\r
963              } )],\r
964          sto = s[0].to;\r
965      values = values && values.split(";");\r
966      /*from属性はオプションなので、条件には付け加えない*/\r
967      if (values && values.length) {\r
968        /*values属性が指定された場合、他の属性は無視される\r
969         * W3C仕様 SMIL アニメーション 3.2.2. アニメーション関数の値*/\r
970         s = [];\r
971        for (var i=1;i<values.length;++i) {\r
972          s.push( this.up().mix( {\r
973                to: $from.up().mix( {\r
974                  from: $from.up()\r
975                } )\r
976              } ) );\r
977          sto = s[s.length-1].to;\r
978          sto.string = values[i];\r
979          sto.from.string = values[i-1];\r
980        }\r
981      } else if (to) {\r
982        sto.string = to;\r
983        sto.from.string = from || "0";\r
984      } else if (by) {\r
985        sto.string = by;\r
986        sto.from.string = from || "0";\r
987        var toNumList = sto.call(),\r
988            fromNumList = sto.from;\r
989        for (var i=0;i<toNumList.length;++i) {\r
990          /*初期値と差分を足していく*/\r
991          toNumList[i] += fromNumList[i];\r
992        }\r
993      } else {\r
994        return null;\r
995      }\r
996      $from = sto = toNumList = fromNumList = void 0;\r
997      return s;\r
998    },\r
999    \r
1000    /*setKeyメソッド\r
1001     * 引数の要素のkeyTimes属性やkeySplines属性を処理するためのメソッド\r
1002     * 必要な他の属性処理はsetValuesメソッドに任せている*/\r
1003    setKey: function(ele) {\r
1004      this._ele = ele;\r
1005      var to = this.setValues(this.getAttr("values", null),\r
1006           this.getAttr("from", null),\r
1007           this.getAttr("to", null),\r
1008           this.getAttr("by", null) ),\r
1009          keyTimes = this.getAttr("keyTimes", null),\r
1010          keySplines = this.getAttr("keySplines", null),\r
1011          keys,\r
1012          splines = keySplines && keySplines.split(";"),\r
1013          isDiscrete = (this.mode === "discrete"),\r
1014          toiKeySplines;\r
1015     if (!isDiscrete && keyTimes && to) {\r
1016       keys = this.$from.numList.call( {\r
1017         string: keyTimes\r
1018       } );\r
1019       /*keysTime属性の最初は0でなければならない。そうでなければエラー(SVG 1.1 2ndの仕様を参照)*/\r
1020       if (keys.length && (keys[0] !== 0)) {\r
1021         return null;\r
1022       }\r
1023       /*toオブジェクトはtoとfromで一組となっているのでlengthが加算される*/\r
1024       if (keys.length && (keys.length !== (to.length+1))) {\r
1025         /*keyTimes属性とvalues属性のリストの個数が合致しない場合、アニメーションの効果がない\r
1026          * 仕様を参照 SMIL Animation 3.2.3. Animation function calculation modes\r
1027          * http://www.w3.org/TR/smil-animation/#AnimFuncCalcMode*/\r
1028         return null;\r
1029       }\r
1030       for (var i=0;i<to.length;++i) {\r
1031         to[i].keyTime = keys[i+1] - keys[i];\r
1032         if (splines) {\r
1033           toiKeySplines = this.$from.numList.call( {\r
1034             string: splines[i]\r
1035           } );\r
1036           /*空配列を返すため、nullに変えておく*/\r
1037           to[i].keySplines = toiKeySplines.length ? toiKeySplines : null;\r
1038         }\r
1039       }\r
1040     } else if (!isDiscrete && to) {\r
1041       var per = 1 / to.length;\r
1042       for (var i=0;i<to.length;++i) {\r
1043         to[i].keyTime = per;\r
1044         if (splines) {\r
1045           toiKeySplines = this.$from.numList.call( {\r
1046             string: splines[i]\r
1047           } );\r
1048           to[i].keySplines = toiKeySplines.length ? toiKeySplines : null;\r
1049         }\r
1050       }\r
1051     } else if (to) {\r
1052         /*discreteモードの処理*/\r
1053       if (keyTimes) {\r
1054         keys = this.$from.numList.call( {\r
1055           string: keyTimes\r
1056         } );\r
1057       /*keysTime属性の最初は0でなければならない。そうでなければエラー(SVG 1.1 2ndの仕様を参照)*/\r
1058         if (keys.length && (keys[0] !== 0)) {\r
1059           return null;\r
1060         }\r
1061         if (keys.length && (keys.length !== (to.length+1))) {\r
1062           return null;\r
1063         }\r
1064         for (var i=0;i<to.length;++i) {\r
1065           to[i].keyTime = keys[i+1] - keys[i];\r
1066         }\r
1067       } else {\r
1068         var per = 1 / (to.length+1);\r
1069         for (var i=0;i<to.length;++i) {\r
1070           to[i].keyTime = per;\r
1071         }\r
1072       }\r
1073       /*toオブジェクトが足らないので、一つ追加しておく*/\r
1074       to.push( to[to.length-1].up().mix( function(){\r
1075           if (!keys) {\r
1076             return;\r
1077           }\r
1078           this.keyTime = 1-keys[keys.length-1];\r
1079         } ).of( {\r
1080             call: function() {\r
1081               return function (t) {\r
1082                  return isNaN(t) ? this.string\r
1083                                  : this.to.advance(1);\r
1084               }.bind(this);\r
1085             }\r
1086           } \r
1087         ) );\r
1088     }\r
1089     if (this.mode === "paced") {\r
1090       /*ベクトル全体の距離を算出*/\r
1091       var norm = 0;\r
1092       to.forEach( function(x) {\r
1093         norm += x.to.distance(x.to.from);\r
1094       } );\r
1095       to.forEach( function(x) {\r
1096          x.norm = norm;\r
1097       } );\r
1098     }\r
1099     ele = keyTimes = keys = per = splines = void 0;\r
1100     return to;\r
1101    }\r
1102 } ).up("$setElement").mix( {\r
1103   /*to属性の値、文字列*/\r
1104   to: "",\r
1105   \r
1106   /*initメソッドで使われるアニメーション関数*/\r
1107   _setFrame: function (frame) {\r
1108     this.state = "playing";\r
1109     this.setAttribute(this.to);\r
1110   },\r
1111   \r
1112   /*アニメーション中かどうかの判別\r
1113    * 1, idling アニメがまだ始まっていない待機状態 (規定値)\r
1114    * 2, playing アニメーションの再生中\r
1115    */\r
1116   state: "idling",\r
1117   \r
1118   /*開始を設定されたタイムライン ($beginオブジェクト)*/\r
1119   timeline: base("$frame").$begin,\r
1120   \r
1121   /*_setEndFrameメソッドで終了処理をさせたいときに、呼び出されるメソッド\r
1122    * 終了処理が必要なときだけ、trueを返す*/\r
1123   checkEnd: function (frame) {\r
1124     var line = this.timeline,\r
1125         end = line.begin + line.activeTime;\r
1126     if (!line.isResolved || isNaN(end)) {\r
1127       /*未解決など問題が発生したとき*/\r
1128       line = frame = void 0;\r
1129       return false;\r
1130     } else if (\r
1131           ( ( frame < line.begin ) || ( end <= frame )\r
1132           ) && (this.state === "playing") ) {\r
1133       /*stateプロパティを書き換えることで、一度のみ、終了処理させる*/\r
1134       this.state = "idling";\r
1135       line = frame = void 0;\r
1136       return true;\r
1137     } else {\r
1138       line = frame = void 0;\r
1139       return false;\r
1140     }\r
1141   },\r
1142   \r
1143   /*アニメが終了した際の後処理*/\r
1144   _setEndFrame: function (frame) {\r
1145     /*removeの場合、アニメーションを凍結せずに、もとに戻す*/\r
1146     if (this.checkEnd(frame) && (this.fill === "remove")) {\r
1147       this.removeAttribute();\r
1148     }\r
1149   },\r
1150   \r
1151   /*アニメーションの呈示値を呼び出す関数*/\r
1152   tocall: function() {},\r
1153   \r
1154   init: function(ele) {\r
1155     var line = this.push(ele);\r
1156     if (ele && ele.getAttributeNS) {\r
1157       this._ele = ele;\r
1158       this.to = this.getAttr("to", "");\r
1159       this.fill = this.getAttr("fill", "remove");\r
1160     }\r
1161     var thisele = this.element;\r
1162     if (line && thisele) {\r
1163       this.timeline = line;\r
1164       /*ラインの中に、属性処理をするためのラインを追加*/\r
1165       line.addLine(\r
1166        { setFrame: this._setFrame.bind(this),\r
1167          begin: 1,\r
1168          activeTime: 1\r
1169        }\r
1170       );\r
1171       base("$frame").$endFrame.addLine(\r
1172         { setFrame: this._setEndFrame.bind(this),\r
1173           begin: 1,\r
1174           activeTime: 1\r
1175         }\r
1176       );\r
1177     }\r
1178     /*アニメーションが再起動する可能性もあるため、stateプロパティはここで初期化*/\r
1179     this.state = "idling";\r
1180     line = thisele = void 0;\r
1181   }\r
1182 }).up("$animateElement").mix( {\r
1183   /*アニメ関数の配列*/\r
1184   funcs: [],\r
1185 \r
1186   /*進捗率advanceから、呈示値を求める*/\r
1187   tocall: function(advance) {\r
1188     var tf = this.funcs;\r
1189     if (this.mode !== "discrete") {\r
1190       for (var i=0;i<tf.length;++i) {\r
1191         var tfi = tf[i];\r
1192         /*keyTime(keyTimes属性で指定されたような値)で実行するかどうかを判別*/\r
1193         if (tfi.endKeyTime >= advance) {\r
1194           return tfi(advance - tfi.startKeyTime);\r
1195         }\r
1196       }\r
1197     } else {\r
1198       var result = "";\r
1199       for (var i=0;i<tf.length;++i) {\r
1200         var tfi = tf[i];\r
1201         if (advance >= tfi.startKeyTime) {\r
1202           result = tfi(advance);\r
1203         }\r
1204       }\r
1205       advance = tf = tfi = void 0;\r
1206       return result;\r
1207     }\r
1208     tf = i = tfi = void 0;\r
1209     return "";\r
1210   },\r
1211   \r
1212   _setFrame: function(currentTime) {\r
1213     /*durationは単純継続時間\r
1214      *advanceは継続時間内での、進捗率\r
1215      *  仕様を参照 http://www.w3.org/TR/smil-animation/#AnimFuncValues\r
1216      *進捗率advanceは、durationと進捗フレーム数とを割った余り(REMAINDER)で算出する\r
1217      * 仕様を参照 SMIL Animation 3.6.2 Interval timing\r
1218      * http://www.w3.org/TR/2001/REC-smil-animation-20010904/#IntervalTiming*/\r
1219     var line = this.timeline,\r
1220         duration = line.simpleDuration,\r
1221         /*単純継続時間が不定の場合、補間はせずに初期値が採用されるため、advanceは0となる\r
1222          * 仕様を参照 SMIL Animation 3.2.2. Animation function values のInterpolation and indefinite simple durations\r
1223          * http://www.w3.org/TR/2001/REC-smil-animation-20010904/#AnimFuncValues*/\r
1224         advance = duration ? ( (currentTime - line.begin) % duration ) / duration\r
1225                     : 0;\r
1226     this.setAttribute(this.tocall(advance));\r
1227     this.state = "playing";\r
1228     line = duration = advance = void 0;\r
1229   },\r
1230   \r
1231   /*_setEndFrameメソッドは、終了処理と凍結作業をするときに、falseを返す*/\r
1232   _setEndFrame: function(frame) {\r
1233     /*上書きされたメソッドを呼び出してアニメーションの凍結作業をする*/\r
1234     if (!this.checkEnd(frame)) {\r
1235       return;\r
1236     }\r
1237     if (this.fill === "freeze") {\r
1238       var line = this.timeline,\r
1239           duration = line.simpleDuration;\r
1240       if (duration) {\r
1241         var advance = ( line.activeTime % duration ) / duration;\r
1242         /*例外が発生するため、進捗率が1を超えないように処理*/\r
1243         advance = (advance > 1) ? 1 : advance;\r
1244          /*活動継続時間と単純継続時間が一致すると、余りは0となるため以下の処理*/\r
1245         advance = advance || 1;\r
1246       } else {\r
1247         advance = 0;\r
1248       }\r
1249       this.setAttribute(this.tocall(advance));\r
1250       line = duration = advance = void 0;\r
1251     } else {\r
1252       this.removeAttribute();\r
1253     }\r
1254   },\r
1255   \r
1256   /*getAttrメソッドをオーバライド*/\r
1257   getAttr: function (name, def) {\r
1258     var s= this.$attribute.getAttr.apply(this, arguments);\r
1259     if ((name === "from") && !s && this.defaultValue) {\r
1260       /*from属性がない場合、対象要素の既定値を返す*/\r
1261       return this.defaultValue;\r
1262     }\r
1263     return s;\r
1264   },\r
1265   \r
1266   _keywords: {\r
1267     aliceblue:    [240,248,255],\r
1268     antiquewhite: [250,235,215],\r
1269     aqua:         [0,255,255],\r
1270     aquamarine:   [127,255,212],\r
1271     azure:        [240,255,255],\r
1272     beige:        [245,245,220],\r
1273     bisque:       [255,228,196],\r
1274     black:        [0,0,0],\r
1275     blanchedalmond:[255,235,205],\r
1276     blue:         [0,0,255],\r
1277     blueviolet:   [138,43,226],\r
1278     brown:        [165,42,42],\r
1279     burlywood:    [222,184,135],\r
1280     cadetblue:    [95,158,160],\r
1281     chartreuse:   [127,255,0],\r
1282     chocolate:    [210,105,30],\r
1283     coral:        [255,127,80],\r
1284     cornflowerblue:[100,149,237],\r
1285     cornsilk:     [255,248,220],\r
1286     crimson:      [220,20,60],\r
1287     cyan:         [0,255,255],\r
1288     darkblue:     [0,0,139],\r
1289     darkcyan:     [0,139,139],\r
1290     darkgoldenrod:[184,134,11],\r
1291     darkgray:     [169,169,169],\r
1292     darkgreen:    [0,100,0],\r
1293     darkgrey:     [169,169,169],\r
1294     darkkhaki:    [189,183,107],\r
1295     darkmagenta:  [139,0,139],\r
1296     darkolivegreen:[85,107,47],\r
1297     darkorange:    [255,140,0],\r
1298     darkorchid:   [153,50,204],\r
1299     darkred:      [139,0,0],\r
1300     darksalmon:   [233,150,122],\r
1301     darkseagreen: [143,188,143],\r
1302     darkslateblue:[72,61,139],\r
1303     darkslategray:[47,79,79],\r
1304     darkslategrey:[47,79,79],\r
1305     darkturquoise:[0,206,209],\r
1306     darkviolet:   [148,0,211],\r
1307     deeppink:     [255,20,147],\r
1308     deepskyblue:  [0,191,255],\r
1309     dimgray:      [105,105,105],\r
1310     dimgrey:      [105,105,105],\r
1311     dodgerblue:   [30,144,255],\r
1312     firebrick:    [178,34,34],\r
1313     floralwhite:  [255,250,240],\r
1314     forestgreen:  [34,139,34],\r
1315     fuchsia:      [255,0,255],\r
1316     gainsboro:    [220,220,220],\r
1317     ghostwhite:   [248,248,255],\r
1318     gold:         [255,215,0],\r
1319     goldenrod:    [218,165,32],\r
1320     gray:         [128,128,128],\r
1321     grey:         [128,128,128],\r
1322     green:        [0,128,0],\r
1323     greenyellow:  [173,255,47],\r
1324     honeydew:     [240,255,240],\r
1325     hotpink:      [255,105,180],\r
1326     indianred:    [205,92,92],\r
1327     indigo:       [75,0,130],\r
1328     ivory:        [255,255,240],\r
1329     khaki:        [240,230,140],\r
1330     lavender:     [230,230,250],\r
1331     lavenderblush:[255,240,245],\r
1332     lawngreen:    [124,252,0],\r
1333     lemonchiffon: [255,250,205],\r
1334     lightblue:    [173,216,230],\r
1335     lightcoral:   [240,128,128],\r
1336     lightcyan:    [224,255,255],\r
1337     lightgoldenrodyellow:[250,250,210],\r
1338     lightgray:    [211,211,211],\r
1339     lightgreen:   [144,238,144],\r
1340     lightgrey:    [211,211,211],\r
1341     lightpink:    [255,182,193],\r
1342     lightsalmon:  [255,160,122],\r
1343     lightseagree: [32,178,170],\r
1344     lightskyblue: [135,206,250],\r
1345     lightslategray:[119,136,153],\r
1346     lightslategrey:[119,136,153],\r
1347     lightsteelblue:[176,196,222],\r
1348     lightyellow:  [255,255,224],\r
1349     lime:         [0,255,0],\r
1350     limegreen:    [50,205,50],\r
1351     linen:        [250,240,230],\r
1352     magenta:      [255,0,255],\r
1353     maroon:       [128,0,0],\r
1354     mediumaquamarine:[102,205,170],\r
1355     mediumblue:    [0,0,205],\r
1356     mediumorchid:  [186,85,211],\r
1357     mediumpurple:  [147,112,219],\r
1358     mediumseagreen:[60,179,113],\r
1359     mediumslateblue:[123,104,238],\r
1360     mediumspringgreen:[0,250,154],\r
1361     mediumturquoise:[72,209,204],\r
1362     mediumvioletred:[199,21,133],\r
1363     midnightblue:  [25,25,112],\r
1364     mintcream:     [245,255,250],\r
1365     mistyrose:     [255,228,225],\r
1366     moccasin:      [255,228,181],\r
1367     navajowhite:   [255,222,173],\r
1368     navy:          [0,0,128],\r
1369     oldlace:       [253,245,230],\r
1370     olive:         [128,128,0],\r
1371     olivedrab:     [107,142,35],\r
1372     orange:        [255,165,0],\r
1373     orangered:     [255,69,0],\r
1374     orchid:        [218,112,214],\r
1375     palegoldenrod: [238,232,170],\r
1376     palegreen:     [152,251,152],\r
1377     paleturquoise: [175,238,238],\r
1378     palevioletred: [219,112,147],\r
1379     papayawhip:    [255,239,213],\r
1380     peachpuff:     [255,218,185],\r
1381     peru:          [205,133,63],\r
1382     pink:          [255,192,203],\r
1383     plum:          [221,160,221],\r
1384     powderblue:    [176,224,230],\r
1385     purple:        [128,0,128],\r
1386     red:           [255,0,0],\r
1387     rosybrown:     [188,143,143],\r
1388     royalblue:     [65,105,225],\r
1389     saddlebrown:   [139,69,19],\r
1390     salmon:        [250,128,114],\r
1391     sandybrown:    [244,164,96],\r
1392     seagreen:      [46,139,87],\r
1393     seashell:      [255,245,238],\r
1394     sienna:        [160,82,45],\r
1395     silver:        [192,192,192],\r
1396     skyblue:       [135,206,235],\r
1397     slateblue:     [106,90,205],\r
1398     slategray:     [112,128,144],\r
1399     slategrey:     [112,128,144],\r
1400     snow:          [255,250,250],\r
1401     springgreen:   [0,255,127],\r
1402     steelblue:     [70,130,180],\r
1403     tan:           [210,180,140],\r
1404     teal:          [0,128,128],\r
1405     thistle:       [216,191,216],\r
1406     tomato:        [255,99,71],\r
1407     turquoise:     [64,224,208],\r
1408     violet:        [238,130,238],\r
1409     wheat:         [245,222,179],\r
1410     white:         [255,255,255],\r
1411     whitesmoke:    [245,245,245],\r
1412     yellow:        [255,255,0],\r
1413     yellowgreen:   [154,205,50]\r
1414   },\r
1415   \r
1416   setAdd: function (ele, to) {\r
1417     /*additive属性がsum (加法アニメーション)の場合*/\r
1418     if (ele.getAttributeNS(null, "additive") === "sum") {\r
1419       var attrValue = ele.parentNode.getAttributeNS(null, this.attrName);\r
1420       ele.addEventListener("beginEvent", function(evt) {\r
1421         to.forEach( function(x) {\r
1422           x.to.setAdditive(attrValue);\r
1423         } )\r
1424       }, false);\r
1425     }\r
1426   },\r
1427   setAccum: function (ele, to) {\r
1428     /*accumulate属性がsum (蓄積アニメーション)の場合*/\r
1429     if (ele.getAttributeNS(null, "accumulate") === "sum") {\r
1430       ele.addEventListener("repeatEvent", function(evt) {\r
1431         to.forEach( function(x) {\r
1432           x.to.call();\r
1433           x.to.setAccumulate(evt.detail);\r
1434         } )\r
1435       }, false);\r
1436     }\r
1437   },\r
1438   \r
1439   /*displayなど補間をしなくてもよい属性への対処するためのメソッド*/\r
1440   setString: function() {\r
1441     if (/^(?:display|class|edgeMode|(gradient|marker|pattern|maskContent|mask|patternContent|primitive)Units|in|in2|method|mode|operator|preserveAspectRatio|result|spacing|spreadMethod|stitchTiles|target|type|xlink:href|yChannelSelector|color-interpolation|(clip|fill)-rule|cursor|filter|font-(family|stretch|style|variant)|image-rendering|marker-(end|mid|start)|mask|overflow|pointer-events|shape-rendering|stroke-(linecap|linejoin)|text-(anchor|decoration|rendering)|visibility)$/.test(this.attrName) ) {\r
1442       this.mode = "discrete";\r
1443     }\r
1444   },\r
1445   \r
1446   /*小数点以下の桁数を指定するため、setValuesメソッドをオーバライドする*/\r
1447   degits: 1,\r
1448   setValues: function() {\r
1449     var s = this.$attribute.setValues.apply(this, arguments),\r
1450         degits = this.degits;\r
1451     s && s.forEach(function(x) {\r
1452       x.to.degit = degits;\r
1453     } );\r
1454     degits = void 0;\r
1455     return s;\r
1456   }\r
1457 \r
1458 /*initメソッドに追加処理\r
1459  * onメソッドについては、base.jsを参照のこと*/\r
1460 } ).on ("init", function(ele) {\r
1461   var isColor = /^(?:fill|stroke|stop-color|color)$/.test(this.attrName);\r
1462   if (isColor) {\r
1463     /*通常は、小数点以下の桁数を既定値の1桁とする\r
1464      *RGB形式では補間に、小数を使わないため、0桁に設定\r
1465      * (なお、この作業は、setKeyメソッドの前に済ませておく必要がある)*/\r
1466     this.degits = 0;\r
1467   }\r
1468   var to, \r
1469       keyTime = 0,\r
1470       /*関数toRGBはrgbColor形式への変換処理で使う*/\r
1471       toRGB = function(x) { return x; };\r
1472   if (ele) {\r
1473     this.mode = ele.getAttributeNS(null, "calcMode") || "linear";\r
1474     this.setString();\r
1475     to = this.setKey(ele);\r
1476   }\r
1477   if (isColor) {\r
1478     /*#から始まる文字列を、rgb(.., .., ..,)形式へと変換するための関数*/\r
1479     var keywords = this._keywords;\r
1480     toRGB = function(rgbColor) {\r
1481            var keyword = keywords[rgbColor];\r
1482            if (keyword) {\r
1483              return "rgb(" + keyword.join(", ") + ")";\r
1484            }\r
1485            if (rgbColor && (rgbColor[0] === "#")) {  //#を含む場合\r
1486               var s = "rgb(",\r
1487                   _parseInt = parseInt;\r
1488               if (rgbColor.length < 5) {\r
1489                 var r = rgbColor[1],\r
1490                     g = rgbColor[2],\r
1491                     b = rgbColor[3],\r
1492                 rgbColor = "#" + r + r + g + g + b + b;\r
1493               }\r
1494               rgbColor.match(/\#(\w{2})(\w{2})(\w{2})/);\r
1495               s += _parseInt(RegExp.$1, 16)\r
1496                 + ", "\r
1497                 + _parseInt(RegExp.$2, 16)\r
1498                 + ", "\r
1499                 + _parseInt(RegExp.$3, 16)\r
1500                 + ")";\r
1501               r = g = b = void 0;\r
1502               return s;\r
1503            }\r
1504            return rgbColor;\r
1505         };\r
1506   }\r
1507   if (to) {\r
1508     this.funcs = to.map( function(x) {\r
1509       x.to.string = toRGB(x.to.string);\r
1510       x.to.from.string = toRGB(x.to.from.string);\r
1511       var s = x.call();\r
1512       /*x.keyTimeプロパティは区間を示しているため、区切り時刻に変換しておく\r
1513        * startKeyTimeプロパティは区間のスタート時点\r
1514        * endKeyTimeプロパティは区間のエンド地点*/\r
1515       s.startKeyTime = keyTime;\r
1516       keyTime = s.endKeyTime = keyTime + x.keyTime;\r
1517       return s;\r
1518     } )\r
1519      .filter( function(s) {\r
1520        if (!this.timeline.isResolved) {\r
1521          /*begin属性などにイベントを設定していた(未解決の)場合、後のs(0.1)がうまく作動せず、\r
1522           * 例外を出してしまうため、ここで返しておく*/\r
1523          return true;\r
1524        }\r
1525        /*splineモードで、かつ、アニメーションが無効な関数の場合は配列から外しておく\r
1526         * 無効な場合に関しては、$calcModeオブジェクトのcallメソッドを参照*/\r
1527        return (this.mode !== "spline") || !s.isNotAnimate;\r
1528     }, this );\r
1529     this.setAdd(ele, to);\r
1530     this.setAccum(ele, to);\r
1531   }\r
1532   keywords = toRGB = isColor = void 0;\r
1533 } )\r
1534 /*$animateTranformElementオブジェクト\r
1535  * animateTransform要素に関連するオブジェクト*/\r
1536  .up("$animateTransformElement")\r
1537  .mix({\r
1538    /*__transformListで何番目のアイテムかを示すプロパティ*/\r
1539    numberOfList: -1,\r
1540    \r
1541    /*type属性の値*/\r
1542    type: "translate",\r
1543    \r
1544    /*attributeName属性の値はtransformで固定*/\r
1545    attrName: "transform",\r
1546    \r
1547    /*明確にisCSSプロパティを設定しておくことで、プロトタイプチェーンを使わせず最適化*/\r
1548    isCSS: false,\r
1549    \r
1550    /*additive属性の値がsumのときにtrue*/\r
1551    isSum: false,\r
1552    \r
1553    /*transform属性の値に使われる数値は精密であることが求められるため、\r
1554     *小数点以下の桁数を決めるdegitsプロパティの値も大きくしておく*/\r
1555    degits: 15,\r
1556    \r
1557    /*tocallメソッド(後述の$motionElementオブジェクトも含む)で使うメソッド\r
1558     * __transformListの値を結合して、文字列として返す*/\r
1559    joinList: function(s) {\r
1560      var playList = this.element.__transformList;\r
1561      for (var i=0;i<playList.length;++i) {\r
1562        var item = playList[i],\r
1563            value = item.value;\r
1564        if (item.isSum) {\r
1565          s += " " + value;\r
1566        } else if (item.isPlaying) {\r
1567          /*他のanimateTransform要素がadditive属性の値にreplaceをすでに設定していた、\r
1568           *かつ、その要素がアニメーション再生中の場合はsを初期化*/\r
1569          s = value;\r
1570        }\r
1571      }\r
1572      return s.trim();\r
1573    },\r
1574    \r
1575    /*$animateElementオブジェクトのtocallメソッドをオーバライド*/\r
1576    tocall: function (advance) {\r
1577      if (this.numberOfList < 0) {\r
1578        throw new Error("Number of The List Error");\r
1579      }\r
1580      var currentItem = this.element.__transformList[this.numberOfList];\r
1581      currentItem.value = this.type+ "(" +this.$animateElement.tocall.call(this, advance)+ ")";\r
1582      currentItem.isPlaying = true;\r
1583      currentItem.isSum = this.isSum;\r
1584      return this.joinList(this.defaultValue || "");\r
1585    },\r
1586    \r
1587    /*後の_setFrameメソッドで使うダミー*/\r
1588    __setAttribute: function(){},\r
1589    \r
1590    _setFrame: function(currentFrame) {\r
1591      /*__transformListの中で、自分より後の項目に再生中のものがあれば、\r
1592       *アニメーションを再生させないで、後に続く項目に任せる*/\r
1593      var list = this.element.__transformList,\r
1594          isPostActive = false,\r
1595          length = list.length;\r
1596      if ( (length !== 1) && (this.numberOfList < (length - 1) ) ) {\r
1597        /*リストの項目が一つだけであったり、自分自身が最後尾であれば、アニメーションを再生する\r
1598         * また、後に続く項目で再生中のものがあったら、今回は再生しない*/\r
1599        for (var i=this.numberOfList+1;i<length;++i) {\r
1600          if (list[i].isPlaying) {\r
1601            isPostActive = true;\r
1602          }\r
1603        }\r
1604      }\r
1605      /*__setAttributeはダミーなので、アニメーションが再生されない*/\r
1606      this.setAttribute = isPostActive ? this.__setAttribute\r
1607                                     : this.$animateElement.setAttribute;\r
1608      /*上書きされたメソッドを呼び出す*/\r
1609      this.$animateElement._setFrame.call(this, currentFrame);\r
1610    },\r
1611    \r
1612    _setEndFrame: function(currentFrame) {\r
1613      var list = this.element.__transformList;\r
1614      if (!this.checkEnd(currentFrame) || (this.fill !== "remove") || !list) {\r
1615        return;\r
1616      }\r
1617      if (!this.isSum) {\r
1618        /*凍結処理をしないで、かつ、元の状態に戻して、効果が出ないようにする*/\r
1619        list[this.numberOfList]\r
1620         && (list[this.numberOfList].isPlaying = false);\r
1621         for (var i = 0;i<list.length;++i) {\r
1622           var listi = list[i];\r
1623           if (listi.isPlaying || !listi.isRemove) {\r
1624             /*他のanimateTransform要素が活性化しているか、凍結処理が必要ならば、\r
1625              * 属性の初期化はしない*/\r
1626             return;\r
1627           }\r
1628         }\r
1629        this.removeAttribute();\r
1630      } else {\r
1631        /*凍結処理をしないで、かつ、効果を出すが、変形させないようにする*/\r
1632        list[this.numberOfList]\r
1633         && (list[this.numberOfList].value = "translate(0)");\r
1634      }\r
1635    },\r
1636    \r
1637     /*setAddメソッドのオーバライド\r
1638      * additive属性のsumに対する振る舞いが異なるため*/\r
1639     setAdd: function() {},\r
1640   })\r
1641  .on("init", function (ele) {\r
1642    if (!ele || !ele.parentNode) {\r
1643      return;\r
1644    }\r
1645    this.getAttr = this.$attribute.getAttr;\r
1646    this.type = this.getAttr("type", "translate");\r
1647    this.attrName = "transform";\r
1648    var parent = this.element;\r
1649    this.isDefault = parent.hasAttributeNS(null, "transform");\r
1650    this.defaultValue = parent.getAttributeNS(null, "transform") || "";\r
1651    this.isSum = (this.getAttr("additive", "replace") === "sum");\r
1652    if (!parent.__transformList) {\r
1653      parent.__transformList = [];\r
1654      this.numberOfList = -1;\r
1655    }\r
1656    if (this.hasAttrValues()\r
1657     && (this.numberOfList < 0) ) {\r
1658      /*もし、今まで、このオブジェクトで、initメソッドを実行していなければ*/\r
1659      this.numberOfList = parent.__transformList.length;\r
1660      /*isPlayingプロパティはアニメーション再生終了後でもtrueとなるので注意*/\r
1661      parent.__transformList.push( {isPlaying: false,\r
1662                                    value: "translate(0)",\r
1663                                    isSum: this.isSum,\r
1664                                    isRemove: (this.fill === "remove")\r
1665                                   } );\r
1666    }\r
1667   } )\r
1668   .up("$motionElement")\r
1669   .mix( function() {\r
1670     /*setRFrameメソッドを再定義*/\r
1671     this._setFrame = this.$animateElement._setFrame;\r
1672   })\r
1673   .mix( {\r
1674     numberOfList: -1,\r
1675     mode: "paced",\r
1676         \r
1677     /*hasAttrValuesメソッドのオーバライド\r
1678      * path属性に対応させるため*/\r
1679     hasAttrValues: function () {\r
1680       if (this.$attribute.hasAttrValues.call(this)) {\r
1681         return true;\r
1682       } else {\r
1683         return (this._ele.hasAttribute("path")\r
1684                  || this._ele.getElementsByTagNameNS(this.path.namespaceURI, "mpath").length);\r
1685       }\r
1686     },\r
1687     \r
1688     path: document.createElementNS("http://www.w3.org/2000/svg", "path"),\r
1689     \r
1690     rotate: "0",\r
1691     \r
1692     /*tocallメソッドのオーバライド*/\r
1693     tocall: function(advance) {\r
1694       /*モーションは仕様の関係上、かならず、CTMの一番初めに置かれ、前置積となる\r
1695      * なお、__transformListプロパティはtransform属性の値であり、CTMを表現する。\r
1696      * 必ず、CTMの一番目に設定する*/\r
1697       return ("translate(" +this.$animateElement.tocall.call(this, advance)+ ") "\r
1698               + this.joinList(this.defaultValue || "")).trim();\r
1699     },\r
1700     \r
1701     /*図の現在の角度rを求めて、rotate(rの文字列(最後に括弧はいらない)で返すメソッド*/\r
1702     getRotate: function(path, advanceLength, rotate) {\r
1703       /*パスセグメントの数値を求めてから、動いている図形の傾き角度r(ラジアンではなく度数)を算出する*/\r
1704       var length = path.getPathSegAtLength(advanceLength),\r
1705           seg = path.pathSegList.getItem(length),\r
1706           command = seg.pathSegTypeAsLetter;\r
1707       if (command === "M") {\r
1708         /*次のセグメントがどのコマンドによるかで、計算方式が違う*/\r
1709         var nextSeg = path.pathSegList.getItem(length+1),\r
1710             nextCommand = nextSeg.pathSegTypeAsLetter;\r
1711         if (nextCommand === "M") {\r
1712           return "";\r
1713         } else if (nextCommand === "L") {\r
1714           return ") rotate(" +(Math.atan2(nextSeg.y-seg.y, nextSeg.x-seg.x)/Math.Pi*180 + rotate)+ "";\r
1715         } else if (nextCommand === "C") {\r
1716           return ") rotate(" +(Math.atan2(nextSeg.y1-seg.y, nextSeg.x1-seg.x)/Math.Pi*180 + rotate)+ "";\r
1717         }\r
1718       } else if ((command === "L") && (length-1 >= 0)) {\r
1719         var preSeg = path.pathSegList.getItem(length-1);\r
1720         return ") rotate(" +(Math.atan2(seg.y-preSeg.y, seg.x-preSeg.x)/Math.Pi*180 + rotate)+ "";\r
1721       } else if (command === "C") {\r
1722         /*3次ベジェ曲線を微分する方法はニュートン法など数値解析の必要があるので、\r
1723          * 以下の通り、別の方法を採用する。\r
1724          * 現在位置から一歩進んだ曲線上の点Bをとり、それを、現在の点Aと結んで線分ABとしたとき、\r
1725          * その直線の傾きからおおよその角度を求める*/\r
1726          var point = path.getPointAtLength(advanceLength),\r
1727              x = point.x,\r
1728              y = point.y;\r
1729          /*一歩進んだ点*/\r
1730          point = path.getPointAtLength(advanceLength+1);\r
1731          console.log(Math.PI);\r
1732          return ") rotate(" +(Math.atan2(point.y-y, point.x-x)/Math.PI*180 + rotate)+ "";\r
1733       }\r
1734     },\r
1735     \r
1736     /*$animateElement.tocallメソッドを置き換えるためのメソッド\r
1737      * mpath要素が指定されたときか、path属性のときにのみ使われる*/\r
1738     _tocallForPath: function(advance) {\r
1739       var path = this.path,\r
1740           advanceLength = advance * path.getTotalLength();\r
1741       /*全体の距離から、現在進めている距離を算出して、そこから、現在点を導き出す*/\r
1742       var point = path.getPointAtLength(advanceLength),\r
1743           rotate = 0; //追加すべき角度\r
1744       if (this.rotate === "0") {\r
1745         return point.x+ "," +point.y;\r
1746       } else if (this.rotate === "auto") {\r
1747         rotate = 0;\r
1748       } else if (this.rotate === "auto-reverse") {\r
1749         rotate = 180;\r
1750       } else {\r
1751         rotate = +this.rotate;\r
1752       }\r
1753       return point.x+ "," +point.y + this.getRotate(path, advanceLength, rotate);\r
1754     }\r
1755   } )\r
1756   .on("init", function (ele) {\r
1757     if (!ele || !ele.parentNode) {\r
1758      return;\r
1759     }\r
1760     /*type属性で変更されないように($animateTransformElementのinitメソッドを参照のこと)*/\r
1761     this.type = "translate";\r
1762     /*isSumプロパティは常にtrueにしておく。animateTransform要素とは挙動が違うため\r
1763      * また、$aniamteTransformElementのtocallメソッド参照*/\r
1764     this.isSum = true;\r
1765     this.mode = this.getAttr("mode", "paced");\r
1766     this.rotate = this.getAttr("rotate", "0");\r
1767     this.path = this.path.cloneNode(true);\r
1768     var mpath = ele.getElementsByTagNameNS(this.path.namespaceURI, "mpath");\r
1769     var list = this.element.__transformList;\r
1770     /*$animateは後で、プロパティを書き換えるために使う。tocallメソッドも参照*/\r
1771     var $animate = this.$animateElement;\r
1772     if (mpath.length) {\r
1773       var p = ele.ownerDocument.getElementById(\r
1774         mpath[0].getAttributeNS("http://www.w3.org/1999/xlink", "href").slice(1)\r
1775       );\r
1776       p && this.path.setAttributeNS(null, "d", p.getAttributeNS(null, "d"));\r
1777       this.$animateElement = $animate.up().mix ( {tocall: this._tocallForPath} );\r
1778     } else if (ele.hasAttributeNS(null, "path")) {\r
1779       this.path.setAttributeNS(null, "d", ele.getAttributeNS(null, "path"));\r
1780       this.$animateElement = $animate.up().mix ( {tocall: this._tocallForPath} );\r
1781     }\r
1782   } );\r
1783 \r
1784 /*$svgEventオブジェクトは、SVGEvent発火を監視するためのオブジェクト*/\r
1785 base("$frame").up("$svgEvent").mix( {\r
1786   /*イベントのスケジュール記録*/\r
1787   first: null,\r
1788   \r
1789   /*タイムラインの最後のキャッシュ*/\r
1790   lastTimeLine: null,\r
1791   \r
1792   /*setTimeTable メソッドはスケジュールの記録をつけるためのメソッド*/\r
1793   setTimeTable: function () {\r
1794     var timelines = this.timelines;\r
1795       for (var i=0, obj = null;i<timelines.length;++i) {\r
1796         if (!timelines[i].target || !timelines[i].isResolved) {\r
1797           /*一度スケジュールを作成して実行したり、未解決だったタイムラインは、処理しない*/\r
1798           continue;\r
1799         }\r
1800         /*タイムラインから、beginEventとendEventを発火するスケジュールを作成*/\r
1801         var timeline = timelines[i],\r
1802             begin = timeline.begin,\r
1803             target = timeline.target,\r
1804             simpleDur = timeline.simpleDuration,\r
1805             activeTime = timeline.activeTime;\r
1806         var first = {\r
1807               frame: begin,\r
1808               eventType: "begin",\r
1809               target: target,\r
1810               next: {\r
1811                 frame: begin+activeTime,\r
1812                 eventType: "end",\r
1813                 target: target,\r
1814                 next: null\r
1815               }\r
1816             };\r
1817         if (obj) {\r
1818           obj.next.next = first;\r
1819         } else if (!this.first) {\r
1820           /*firstプロパティがすでにある場合、書き換えない*/\r
1821           this.first = first;\r
1822         } else {\r
1823           /*firstプロパティがある場合、そのリストのリンクをたどっていって、\r
1824            * 最後の尾を変数firstとつなげる*/\r
1825           var fst = this.first;\r
1826           while(fst.next) {\r
1827             fst = fst.next;\r
1828           }\r
1829           fst.next = first;\r
1830         }\r
1831         obj = first;\r
1832         if (simpleDur && (activeTime !== simpleDur)) {\r
1833           /*活動継続時間と単純持続時間が異なるとき、repeatイベントを設定\r
1834            * ただし、repeatイベントはendイベントが発生する前に起きるものと仮定*/\r
1835           for (var a = first, m= begin + simpleDur, n=1;m < begin + activeTime; m+=simpleDur, ++n) {\r
1836             a.next = {\r
1837               frame: m,\r
1838               eventType: "repeat",\r
1839               target: target,\r
1840               /*リピートの回数 (n >= 1)*/\r
1841               count: n,\r
1842               next: a.next\r
1843             };\r
1844             a = a.next;\r
1845           }\r
1846         }\r
1847         /*一度、スケジュールを作っておいたタイムラインは次回から処理しないようにする*/\r
1848         timeline.target = null;\r
1849       }\r
1850     timelines = obj = first = begin = target = simpleDur = activeTime = void 0;\r
1851   },\r
1852   \r
1853   $frame: base("$frame"),\r
1854   \r
1855   setFrame: function (num) {\r
1856     var timelines = this.timelines,\r
1857         lastTimeLine = timelines[timelines.length-1],\r
1858         s = this.$frame.setFrame(num);\r
1859     /*キャッシュのlastTimeLineプロパティを使って、再びスケジュールの計算をさせないようにする*/\r
1860     if (this.lastTimeLine !== lastTimeLine) {\r
1861       this.lastTimeLine = lastTimeLine;\r
1862       this.setTimeTable();\r
1863     }\r
1864     /*スケジュールに記録しておいたものを実行して、イベントを発火\r
1865      * また、発火した場合は記録から取り除いて、次回から再び発火しないようにする*/\r
1866     var obj = this.first,\r
1867         cobj = obj,\r
1868         floor = Math.floor;\r
1869     while(obj) {\r
1870       var frame = obj.frame,\r
1871           target = obj.target\r
1872           detail = 0;\r
1873       if (frame <= num) {\r
1874         /*IE11ではSVGEventsやDOMEventsを使うと問題が起きるため、MouseEventsで代用する*/\r
1875         if (obj.eventType === "repeat") {\r
1876           /*detailは何回リピートしたか*/\r
1877           detail = obj.count;\r
1878         }\r
1879         /*ポインタの連結を変更することで、リストからobj を除去\r
1880          * 一度除去したものはイベントを発生させない*/\r
1881         cobj.next = obj.next;\r
1882         if (this.first === obj) {\r
1883           cobj = obj.next;\r
1884           this.first = cobj;\r
1885         } else {\r
1886           cobj = obj;\r
1887         }\r
1888         var evt = target.ownerDocument.createEvent("MouseEvents");\r
1889         evt.initMouseEvent(obj.eventType+"Event" ,true, true, window, detail, 0, 0, 0, 0, false, false, false, false, 0, target);\r
1890         target.dispatchEvent(evt);\r
1891       } else {\r
1892         /*next プロパティを書き換えるためのobj オブジェクトのキャッシュ*/\r
1893         cobj = obj;\r
1894       }\r
1895       obj = obj.next;\r
1896     }\r
1897     obj = num = first = frame = target = cobj = detail = void 0;\r
1898     return s;\r
1899   }\r
1900 } );\r
1901 \r
1902 function getDocument() \r
1903 {\r
1904   var svg = document.getElementsByTagName("object"),\r
1905       svgns = "http://www.w3.org/2000/svg";\r
1906   if (svg) {\r
1907     for (var i=0;i<svg.length;++i) {\r
1908       getElement( svg[i].getSVGDocument() );\r
1909     }\r
1910   }\r
1911   /*SVG文書から呼び出されたときも処理する*/\r
1912   getElement(document);\r
1913   /*idはアニメの中止ハンドル*/\r
1914   var id = __step(),\r
1915       idstop = function() {\r
1916         /*アニメーションを中止する関数*/\r
1917         window.cancelAnimationRequest && cancelAnimationRequest(id);\r
1918       };\r
1919   base("$frame").on("pauseAnimation", idstop);\r
1920   window.addEventListener("unload", idstop);\r
1921   \r
1922   /*文書からアニメーション関連要素を取り出して、オブジェクトを初期化*/\r
1923   function getElement (svgDoc) {\r
1924       var $set = base("$calcMode").$attribute.$setElement,\r
1925           $animate = $set.$animateElement;\r
1926       init($set, "set");\r
1927       init($animate, "animate");\r
1928       init($animate, "animateColor");\r
1929       init($animate.$animateTransformElement, "animateTransform");\r
1930       init($animate.$animateTransformElement.$motionElement, "animateMotion");\r
1931         /*リンクのハッシュ読み取りで、ハイパーリンクのイベント処理\r
1932        * たとえば、a要素のxlink:href="#hoge"で、<animate id="hoge"のとき、\r
1933        * animate要素がハイパーリンク作動と同時に動くようになる\r
1934        * \r
1935        * ただし、SMIL アニメーションの仕様では、\r
1936        * animate要素の開始時刻まで、時を進める操作をするだけ*/\r
1937        svgDoc.defaultView.addEventListener("hashchange", function() {\r
1938            var hash = svgDoc.defaultView.location.hash.slice(1);\r
1939            svgDoc.getElementById(hash).beginElement();\r
1940          });\r
1941 \r
1942       function init (obj, name) {\r
1943         var eles = svgDoc.getElementsByTagNameNS(svgns, name)\r
1944         for (var i=0;i<eles.length;++i) {\r
1945           obj.up().init(eles.item(i));\r
1946         }\r
1947         eles = obj = void 0;\r
1948       };\r
1949   };\r
1950 }\r
1951 \r
1952 window.addEventListener && window.addEventListener("load", getDocument);\r
1953 \r
1954 function __step() {\r
1955 /*EdgeはhasFeatureメソッドでtrueを返す*/\r
1956 if (!document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Animation", "1.1")\r
1957     || window.navigator.userAgent.toLowerCase().indexOf("edge")) {\r
1958   if (window.requestAnimationFrame && requestAnimationFrame) {\r
1959     /*IE11などSMILアニメーションに対応していないブラウザ用*/\r
1960     /*cancelはアニメーションの中止ハンドル*/\r
1961     var cancel = {\r
1962        handle: null\r
1963       };\r
1964     (function(frame) {\r
1965       var $frame = base("$frame"),\r
1966           $f = $frame.$svgEvent,\r
1967           _cancel = cancel; /*cancelのエイリアス*/\r
1968       _cancel.handle = requestAnimationFrame(step);\r
1969       function step() {\r
1970         if (!$frame.isPaused) {\r
1971           frame++;\r
1972           try {\r
1973             $f.setFrame(frame);\r
1974             $f.$endFrame.setFrame(frame);\r
1975           } catch(e) {\r
1976           }\r
1977           _cancel.handle = requestAnimationFrame(step);\r
1978         }\r
1979       };\r
1980     })(-1);\r
1981     return cancel;\r
1982   } else {\r
1983     setInterval( (function(frame) {\r
1984       var $f = base("$frame").$svgEvent;\r
1985       return function () {\r
1986         frame++;\r
1987         $f.setFrame(frame);\r
1988       };\r
1989     })(-1), 1 );\r
1990   }\r
1991 }\r
1992 }\r
1993 //#endif // _SMIL_IDL_\r