1 /*SIE-SVG without Plugin under LGPL2.1 & GPL2.0 & Mozilla Public License
\r
2 *公式ページは http://sie.sourceforge.jp/
\r
3 *利用方法は <script defer="defer" type="text/javascript" src="sie.js"></script>
\r
4 *http://sie.sourceforge.jp/
\r
5 *Usage: <script defer="defer" type="text/javascript" src="sie.js"></script>
\r
7 /* ***** BEGIN LICENSE BLOCK *****
\r
8 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
\r
10 * The contents of this file are subject to the Mozilla Public License Version
\r
11 * 1.1 (the "License"); you may not use this file except in compliance with
\r
12 * the License. You may obtain a copy of the License at
\r
13 * http://www.mozilla.org/MPL/
\r
15 * Software distributed under the License is distributed on an "AS IS" basis,
\r
16 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
\r
17 * for the specific language governing rights and limitations under the
\r
20 * The Original Code is the Mozilla SVG Cairo Renderer project.
\r
22 * The Initial Developer of the Original Code is IBM Corporation.
\r
23 * Portions created by the Initial Developer are Copyright (C) 2004
\r
24 * the Initial Developer. All Rights Reserved.
\r
26 * Parts of this file contain code derived from the following files(s)
\r
27 * of the Mozilla SVG project (these parts are Copyright (C) by their
\r
28 * respective copyright-holders):
\r
29 * layout/svg/renderer/src/libart/nsSVGLibartBPathBuilder.cpp
\r
31 * Contributor(s):DHRNAME revulo
\r
33 * Alternatively, the contents of this file may be used under the terms of
\r
34 * either of the GNU General Public License Version 2 or later (the "GPL"),
\r
35 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
\r
36 * in which case the provisions of the GPL or the LGPL are applicable instead
\r
37 * of those above. If you wish to allow use of your version of this file only
\r
38 * under the terms of either the GPL or the LGPL, and not to allow others to
\r
39 * use your version of this file under the terms of the MPL, indicate your
\r
40 * decision by deleting the provisions above and replace them with the notice
\r
41 * and other provisions required by the GPL or the LGPL. If you do not delete
\r
42 * the provisions above, a recipient may use your version of this file under
\r
43 * the terms of any one of the MPL, the GPL or the LGPL.
\r
45 * ***** END LICENSE BLOCK ***** */
\r
47 * Copyright (c) 2000 World Wide Web Consortium,
\r
48 * (Massachusetts Institute of Technology, Institut National de
\r
49 * Recherche en Informatique et en Automatique, Keio University). All
\r
50 * Rights Reserved. This program is distributed under the W3C's Software
\r
51 * Intellectual Property License. This program is distributed in the
\r
52 * hope that it will be useful, but WITHOUT ANY WARRANTY; without even
\r
53 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
\r
55 * See W3C License http://www.w3.org/Consortium/Legal/ for more details.
\r
64 #pragma prefix "dom.w3c.org"
\r
67 typedef dom::DOMString DOMString;
\r
69 /*ElementTimeControlはSVGAnimationElementに統合させる。
\r
72 function ElementTimeControl(ele) {
\r
74 /*_startと_endプロパティはミリ秒数を収納する。
\r
75 *_startはアニメ開始時の秒数のリスト。_finishはアニメ終了時の秒数のリスト。
\r
76 *なお、文書読み込み終了時(アニメ開始時刻)の秒数を0とする。
\r
79 this._finish = null;
\r
81 ElementTimeControl.prototype = {
\r
82 /*void*/ beginElement : function() {
\r
83 var ttd = this.ownerDocument, evt = ttd.createEvent("TimeEvents");
\r
84 evt.initTimeEvent("beginEvent", ttd.defaultView, 0);
\r
85 this.dispatchEvent(evt);
\r
87 /*void*/ endElement : function() {
\r
88 var ttd = this.ownerDocument, evt = ttd.createEvent("TimeEvents");
\r
89 evt.initTimeEvent("endEvent", ttd.defaultView, 0);
\r
90 this.dispatchEvent(evt);
\r
92 /*void*/ beginElementAt : function(/*float*/ offset) {
\r
93 var ntc = this.ownerDocument.documentElement.getCurrentTime(),
\r
94 start = this._start || [];
\r
95 for (var i=0,sli=start.length;i<sli;++i) {
\r
96 if (start[i] === (offset+ntc)) {
\r
97 ntc = start = offset = void 0;
\r
101 start.push(offset + ntc);
\r
102 this._start = start;
\r
104 /*void*/ endElementAt : function(/*float*/ offset) {
\r
105 var ntc = this.ownerDocument.documentElement.getCurrentTime(),
\r
106 fin = this._finish || [];
\r
107 for (var i=0,fli=fin.length;i<fli;++i) {
\r
108 if (fin[i] === (offset+ntc)) {
\r
109 ntc = fin = offset = void 0;
\r
113 fin.push(offset + ntc);
\r
114 this._finish = fin;
\r
118 base("$event").up("TimeEvents").mix( {
\r
119 /*readonly attribute views::AbstractView this.view;*/
\r
120 /*readonly attribute long*/ detail: 0,
\r
121 /*void*/ initTimeEvent: function(/*DOMString*/ typeArg,
\r
122 /*views::AbstractView*/ viewArg,
\r
123 /*long*/ detailArg) {
\r
124 this.type = typeArg;
\r
125 this.view = viewArg;
\r
126 this.detail = detailArg;
\r
133 base("$frame").mix ( {
\r
134 /*フレームレート。1ミリ秒何フレームか。計算を省略するためミリ秒使用*/
\r
137 /*タイムラインのリスト (時間区間の設定ができる)*/
\r
140 /*開始フレーム数。アニメーションの開始条件となる
\r
141 * 単位はフレーム数であって、秒数ではない*/
\r
144 /*開始時刻 (文書が読み込まれたときにDate.nowなどで取得)
\r
145 * 単位はミリ秒数であって、フレーム数ではない*/
\r
148 /*活動継続時間 (Active Duration)のフレーム数。アニメーションの継続条件となる
\r
149 * 単位はフレーム数であって、秒数ではない*/
\r
150 activeTime: Number.MAX_VALUE,
\r
159 * フレーム数を数値num まで進めるか、戻す*/
\r
160 setFrame: function( /*number*/ num) {
\r
161 if((num < this.begin) || (num >= (this.begin+this.activeTime))) {
\r
164 this.currentFrame = num;
\r
165 var timelines = this.timelines;
\r
166 for (var i=0;i<timelines.length;++i) {
\r
167 timelines[i].setFrame(num);
\r
172 * タイムラインを追加したあと、trueを返す
\r
173 * ただし、引数objのobj.beginとobj.activeTimeが定まっていない場合はfalseを返す*/
\r
174 addLine: function( /*$frame*/ obj ) {
\r
175 if(!obj || (!obj.begin && (obj.begin !== 0))
\r
176 || (!obj.activeTime && (obj.activeTime !== 0)) ) {
\r
177 /*どちらのプロパティも未確認の場合、タイムラインは追加されない*/
\r
180 if ( this.timelines.indexOf(obj) >= 0 ) {
\r
181 this.removeLine(obj);
\r
183 this.timelines.push( obj );
\r
184 /*ランクをソートしておくことで、タイムラインを実行する順序を決める
\r
185 * 主に、begin用のタイムラインの前に、end用のタイムラインが実行されるのを防ぐのが目的*/
\r
186 this.timelines.sort( function (a, b) {
\r
187 return a.rank - b.rank;
\r
193 * 指定されたタイムラインのオブジェクトを、リストから削除する*/
\r
194 removeLine: function( /*$frame*/ timeline ) {
\r
195 var list = this.timelines,
\r
196 j = list.indexOf(timeline);
\r
198 list.splice(j, 1); //Arrayのspliceを利用して、リストからtimelineを排除
\r
202 } ).mix( function($frame) {
\r
205 $frame.up("$begin").mix( {
\r
207 /*開始時刻やタイミングが書かれた文字列*/
\r
210 /*イベントやindefinteで未解決かどうか*/
\r
214 eventTarget: document.documentElement,
\r
216 /*現在のフレーム数を改めて初期化*/
\r
219 /*イベント同期で使う時間差のフレーム数*/
\r
222 /*repeat(1)など文字列内に書かれたリピート回数*/
\r
225 /*accessKey(a)の"a"などキーイベントの対象となる文字列*/
\r
230 trim: function(str) {
\r
231 /*strがString型以外のときは必ずエラーを出す*/
\r
232 return str.replace(/[\s\n]+/g, "");
\r
236 * 引数に渡された文字列から、ミリ秒単位に変換した時間を、解析して返す*/
\r
237 offset: function(str) {
\r
239 var plusminus = str.charAt(0),
\r
240 /*parseFloatのエイリアス*/
\r
241 _float = parseFloat,
\r
242 s = _float( str.match(/[\d.]+ms$/) || "0") + sec() + min() + h();
\r
243 if (plusminus === "-") {
\r
246 plusminus = _float = sec = min = h = void 0;
\r
249 /*00:00:0と00:0と、0sなどの文字列をミリ秒へ変換*/
\r
251 return str2num( 1000, /[\d.]+s$/, /[\d.]+$/ );
\r
254 return str2num( 60000, /[\d.]+min$/, /\d\d:[^:]+$/ );
\r
257 return str2num( 3600000, /\d+:\d\d:/, /[\d.]+h$/ );
\r
259 function str2num(s, /*RegExp*/ a, /*RegExp*/ b) {
\r
260 return s*( _float(str.match(a) || "0") || _float(str.match(b) || "0") );
\r
265 * 引数の文字列から、idとイベントに相当する文字列のプロパティを持ったオブジェクトを返す
\r
266 * idがない場合や、イベントがない場合は空文字列を該当のプロパティに入れる*/
\r
267 event: function(str) {
\r
269 if (/[\+\-]/.test(str)) {
\r
270 /*数値がある場合は切り取っておく*/
\r
271 str = str.slice(0, str.search(/[\+\-]/));
\r
273 if (str.indexOf(".") > -1) {
\r
274 /*ドットが見つかった場合、IDとイベントに分けておく*/
\r
275 var ide = str.split(".");
\r
276 /* エラーが起きて、idが空文字列ならば、evtも空文字列。逆も然り*/
\r
278 id: (ide[1] && ide[0]),
\r
279 event: (ide[0] && ide[1])
\r
290 * stringプロパティを解析して、フレーム数を算出し、結果を$frame.beginプロパティに出力
\r
291 * また、イベントリスナーに登録をしておく*/
\r
292 parse: function() {
\r
294 var str = this.trim(this.string),
\r
295 plusminus = str.search(/[\+\-]/),
\r
298 if (plusminus > 0) {
\r
299 /*Event-Value +/- Clock-Value の場合*/
\r
300 this.begin = this.offset( str.slice(plusminus) );
\r
301 event = this.event(str);
\r
302 } else if ( /[^\+\-\d]/.test(str.charAt(0)) ) {
\r
303 /*Event-Valuen のみの場合*/
\r
304 event = this.event(str);
\r
306 /*+/- Clock-Value のみの場合*/
\r
307 this.begin = this.offset( str );
\r
308 /*イベントもindefiniteもないので、解決済みと考える*/
\r
309 this.isResolved = true;
\r
311 /*もしもあれば、リピートの回数を求める*/
\r
312 this.repeat = /repeat\((\d+)\)/.test(str) ? +RegExp.$1 : 0;
\r
313 /*もしもあれば、押されるはずのキーを求める*/
\r
314 this.accessKey = /accessKey\(([^\)]+?)\)/.test(str) ? RegExp.$1 : "";
\r
315 this.begin = Math.floor( this.begin * this.fpms);
\r
317 ele = event.id ? this.eventTarget.ownerDocument.getElementById(event.id)
\r
318 : this.eventTarget;
\r
319 /*イベントの時間差を設定しておく*/
\r
320 this.eventOffset = this.begin;
\r
321 if (this.repeat > 0) {
\r
322 ele && ele.addEventListener("repeatEvent", (function(evt) {
\r
323 if (evt.detail === this.repeat) {
\r
324 this.listener(evt);
\r
325 } }).bind(this), true);
\r
326 } else if (this.accessKey) {
\r
327 document.documentElement.addEventListener("keydown", (function(evt) {
\r
328 if (evt.char === this.accessKey) {
\r
329 this.listener(evt);
\r
330 } }).bind(this), false);
\r
332 var evtName = /^(?:begin|end|repeat)$/.test(event.event) ? event.event + "Event"
\r
334 ele && ele.addEventListener(evtName, this.listener.bind(this), false);
\r
338 * すでに、フレームオブジェクトのタイムラインには登録済みなので、
\r
339 * フレームではなく、自分独自のタイムラインに登録しておけばよい*/
\r
340 this.$frame = this;
\r
342 s = event = str = plusminus = ele = void 0;
\r
346 /*イベントのリスナーとして、parseメソッドで使う*/
\r
347 listener: function(evt) {
\r
348 evt = evt || { timeStamp: this.startTime };
\r
349 if (!evt.timeStamp && (evt.timeStamp !== 0)) {
\r
352 /*イベントのリスナーが遅かった場合の、誤差の演算をしておく*/
\r
353 this.begin = this.eventOffset + this.$frame.currentFrame - Math.floor( (Date.now() - evt.timeStamp) * this.fpms );
\r
354 var s = this.$activate;
\r
355 s.begin = this.begin;
\r
356 this.activeTime = s.call() || Number.MAX_VALUE;
\r
357 this.simpleDuration = s.simpleDur;
\r
359 this.$frame.addLine(this);
\r
363 * 活動継続時間などを計算するための計算実体
\r
364 * $begin オブジェクトからの継承*/
\r
365 } ).up("$activate").of( {
\r
367 /*単純継続時間のパースされる前の文字列*/
\r
370 /*活動をストップさせるためのオブジェクト*/
\r
371 end: $frame.$begin.up("$end"),
\r
379 /*単純継続時間 (単位はフレーム数)*/
\r
380 simpleDur: function() {
\r
381 return ( (this.dur === "indefinite") || !this.dur ) ?
\r
383 : Math.floor(this.offset(this.dur) * this.fpms) ;
\r
387 * 最小値 <= 活動継続時間 とならなければならない*/
\r
391 * 活動継続時間 <= 最大値 とならなければならない*/
\r
394 /*解決した(計算する)ときの時間*/
\r
395 resolvedTime: function() {
\r
400 * base.jsのofメソッドを活用して、関数型っぽい処理をする
\r
402 * 計算方法はSMILアニメーション 3.3.4節を参照
\r
403 * http://www.w3.org/TR/smil-animation/#ComputingActiveDur
\r
406 var ind = "indefinite",
\r
407 dur = this.simpleDur,
\r
408 isIndefRepeatCount = (this.repeatCount === ind),
\r
409 isIndefRepeatDur = (this.repeatDur === ind),
\r
410 isIndefEnd = (this.end === ind),
\r
411 isDur = dur || (dur === 0),
\r
412 isEnd = this.end || (this.end === 0),
\r
413 isRepeatCount = this.repeatCount || (this.repeatCount === 0),
\r
414 isRepeatDur = this.repeatDur || (this.repeatDur === 0),
\r
416 min = Math.floor(this.offset(this.min) * this.fpms),
\r
417 max = (this.max === ind) ? null : Math.floor(this.offset(this.max) * this.fpms),
\r
422 if (isDur && this.repeatCount && !isIndefRepeatCount) {
\r
423 actList.push( dur * this.repeatCount );
\r
425 if (isRepeatDur && !isIndefRepeatDur) {
\r
426 actList.push( Math.floor( this.offset(this.repeatDur) * this.fpms) );
\r
428 if (isEnd && !isIndefEnd) {
\r
429 actList.push( this.end - this.begin );
\r
431 if (isDur && !isRepeatCount && !isRepeatDur) {
\r
432 /*repeatCountやrepeatDur属性が指定されていない場合*/
\r
433 actList.push( dur );
\r
436 /*長くなるため、インライン関数を活用
\r
437 * indef関数は活動継続時間が不定かどうか、もし、不定なら真を返す*/
\r
441 } else if (isEnd) {
\r
444 return !!( (!isDur && !isRepeatDur)
\r
445 || (isIndefRepeatCount && !isRepeatDur)
\r
446 || (isIndefRepeatDur && !isRepeatCount)
\r
447 || (isIndefRepeatCount && isIndefRepeatDur)
\r
451 ind = dur = isIndefRepeatCount = isIndefRepeatDurindef = isDur = isEnd = isRepeatDur = isRepeatCount = indef = void 0;
\r
453 if (actList.length === 1) {
\r
455 } else if(actList.length > 1) {
\r
456 /*属性が競合するときは、最小値をとること (SMILアニメーション 3.3.4節)*/
\r
457 s = Math.min.apply(Math, actList);
\r
461 if ( max && (min > max) ) {
\r
464 min && (min > s) && (s = min);
\r
465 max && (max < s) && (s = max);
\r
469 $frame.$begin.$end.of( {
\r
471 if (!this.string) {
\r
474 this.parse(this.string);
\r
475 return this.isResolved ? this.begin
\r
480 listener: function(evt) {
\r
481 evt = evt || { timeStamp: this.startTime };
\r
482 if (!evt.timeStamp && (evt.timeStamp !== 0)) {
\r
485 if (this.begin <= 0) {
\r
487 this.removeLine(this.$begin);
\r
489 this.begin = this.eventOffset + this.$frame.currentFrame - Math.floor( (Date.now() - evt.timeStamp) * this.fpms );
\r
490 var s = this.$begin.$activate;
\r
491 s.end = this.begin;
\r
492 /*未解決だったendの値が、イベントの発生により、解決して再定義されたとき、
\r
493 * $activateオブジェクトを使って活動継続時間を再計算する*/
\r
494 this.$begin.activeTime = s.call();
\r
500 * 呈示値 (presentation value)の計算をする。値そのものを返すための計算実体*/
\r
501 base("$from").of( {
\r
505 /*呈示値の数値の部分だけを抜き出した配列を返す*/
\r
506 numList: function() {
\r
507 var s = this.string.match(/[\-\+]?[\d\.]+(?:[eE][\-\+]?[\d\.]+)?/g)
\r
510 /*mapメソッドで代用してもよい*/
\r
511 for (var i=0;i<s.length;++i) {
\r
512 s[i] = parseFloat(s[i]);
\r
518 /*呈示値の文字部分だけを抜き出した配列を返す*/
\r
519 strList: function() {
\r
520 /*replaceメソッドで1E-10などの対策*/
\r
521 return this.string.replace(/\d[eE][\-\+\d]/g, "")
\r
522 .match(/[^\d\-\+\.]+/g);
\r
525 from: base("$from").up().mix( {
\r
529 /*$toオブジェクトにこのオブジェクトを適用させる関数*/
\r
531 if (this.numList.length) {
\r
534 tu = this.underlying;
\r
535 for (var i=0;i<this.numList.length;++i) {
\r
537 additive[i] = accumlate[i] = 0;
\r
539 tu.additive = additive;
\r
540 tu.accumlate = accumlate;
\r
542 /*文字部分の配置パターンは4通りあるので、ここでstrListを処理
\r
545 * (3) a 0 a (ノーマルパターン)
\r
547 * これらのパターンのうち、(1)(2)(4)を(3)のパターンに統一したのが以下の処理*/
\r
548 /*文字列が1aのように、数値で始まるかどうか。始まったら真*/
\r
549 if (!this.string || !this.numList.length || !this.strList) {
\r
550 return this.numList;
\r
552 var isNormal = (this.numList.length < this.strList.length);
\r
553 if (/^[\-\+]?[\d\.]/.test(this.string) && !isNormal) {
\r
554 /*文字列が1aのように、数値で始まる場合*/
\r
555 this.strList.unshift("");
\r
557 if (/\d$/.test(this.string) && !isNormal) {
\r
558 /*文字列がa1のように、数値で終わる場合*/
\r
559 this.strList.push("");
\r
561 return this.numList;
\r
566 /*advanceメソッドで使われる有効数字の桁数 (小数点の桁数を決めるときに使う)*/
\r
569 /*additve属性やaccumlate属性が設定された、累積アニメーションか、加法アニメーションで使われる*/
\r
576 * アニメーションの進行具合を示す進捗率 t (0 <= t <= 1)をもとに、現在の呈示値を算出するためのもの
\r
577 * callメソッドが前もって呼び出されていることが前提となる*/
\r
578 advance: function(t) {
\r
579 if ( (t < 0) || (1 < t)) {
\r
580 throw new Error("An Invalid Number Error");
\r
582 if (!this.string || !this.from.length) {
\r
586 numList = this.numList,
\r
587 strList = this.strList,
\r
588 fromNumList = this.from,
\r
590 underlying = this.underlying,
\r
591 additive = underlying.additive,
\r
592 accumlate = underlying.accumlate;
\r
594 for (var i=0,nuli=numList.length;i<nuli;++i) {
\r
595 /*原点Oを(0,0,...0)とおく
\r
596 *$fromと$toを、原点Oからの二つのベクトル (n次空間のベクトル)、ベクトルOFとベクトルOTと考える
\r
597 *$fromと$toの二つの端の点FとTを結ぶ線分を、t : 1-t で内分する点をPとおく
\r
598 * このときのベクトルOPを求めたのが以下の式*/
\r
599 str += ( t * numList[i] + (1 - t) * fromNumList[i] + additive[i] + accumlate[i]).toFixed(deg);
\r
600 strList && ( str += strList[i+1] );
\r
602 /*文字列はcallメソッドにより、a0aのパターンになっているので、aの部分を追加*/
\r
603 str = (strList ? strList[0] : "") + str;
\r
604 numList = strList = fromNumList = i = nuli = deg = underlying = additive = accumlate = void 0;
\r
609 * fromベクトルから自分自身のベクトルへの距離 (ノルム)の数値を返す。callメソッドを使うので注意すること*/
\r
610 distance: function(from) {
\r
614 var toList = this.call(),
\r
615 fromList = from.call ? from.call() : from,
\r
617 if (!toList || !fromList) {
\r
620 for (var i=0, tli=toList.length; i<tli; ++i) {
\r
621 s += (toList[i] - fromList[i])*(toList[i] - fromList[i]);
\r
623 return Math.sqrt(s);
\r
627 .up("$to").from = null;
\r
631 base("$calcMode").mix({
\r
635 var tkey = this.keyTime;
\r
636 if ( (tkey === 0) && t) {
\r
638 } else if (!tkey || !isFinite(tkey) ) {
\r
639 return this.string;
\r
642 t = (t > 1) ? Math.floor(t) : t;
\r
645 return isNaN(t) ? this.string
\r
646 : this.to.advance(t);
\r
650 /*計算モード (calcMode属性の値)*/
\r
654 * たとえば、"0, 0.5, 0.7, 1"の場合、時間の区間はそれぞれ、0.5 (=0.5-0) 0.2 (=0.7-0.5) 0.3 (=1-0.7)である
\r
655 * このうち、どれか一つが値として入力される*/
\r
658 /*keySpline属性の値を設定*/
\r
667 /*与えられたアニメーションの進捗率を使った時間の圧縮率を計算して呈示値を返すための関数を作る*/
\r
669 var f = this._f.bind(this);
\r
670 if (this.mode === "linear") {
\r
673 } else if (this.mode === "paced") {
\r
674 /*keyTimes属性は無視され、ベクトルの距離の割合から計算される*/
\r
675 this.keyTime = this.to.distance(this.to.from) / this.norm;
\r
677 } else if (this.mode === "spline") {
\r
678 var tk = this.keySplines,
\r
679 /*必ず関数を返すようにするため、円周率を返す関数tfを返して、nullの代わりとする*/
\r
686 for (var i=0,tki = NaN;i<tk.length;++i) {
\r
691 if ( (tki < 0) || (1 < tki)) {
\r
708 _newton = Math.qubicnewton; //高速化のためのエイリアス
\r
709 if ( ( (x2 === 0) || (x2 === 1) )
\r
711 && ( (x3 === 1) || (x3 === 0) )
\r
713 /*linearモードと同じ効果 (収束ではない可能性を考慮)*/
\r
717 var tkey = this.keyTime;
\r
718 if (tkey || isFinite(tkey) ) {
\r
719 /*keyTimeから時間の収縮率を3次ベジェ曲線に適用しておく*/
\r
727 tkey = tk = x2 = y2 = x3 = y3 = x4 = y4 = void 0;
\r
728 return function (x) {
\r
730 *x = (x4-3*(x3-x2)-x1)*t*t*t + 3*(x3-2*x2+x1)*t*t + 3*(x2-x1)*t + x1
\r
731 *y = (y4-3*(y3-y2)-y1)*t*t*t + 3*(y3-2*y2+y1)*t*t + 3*(y2-y1)*t + y1
\r
733 * スプラインモードの場合、x1 = y1 = 0, x4 = y4 = 1
\r
734 * ベジェ曲線のxの式が三次方程式であるため、その解 t から、ベジェ曲線の y を求める
\r
735 * なお、ニュートン法の初期値はxとする
\r
736 * なぜなら、xの式をみると、xが増加傾向となるスプラインモードでは、係数が負となる可能性が低いため*/
\r
737 var t = _newton(Ax, Bx, Cx, -x, x);
\r
738 return f(Ay*t*t*t + By*t*t + Cy*t);
\r
740 } else if (this.mode === "discrete") {
\r
741 return function (t) {
\r
742 return isNaN(t) ? this.string
\r
743 : this.to.from.string;
\r
747 } ).to = base("$from").$to;
\r
750 /*ニュートン法により、三次方程式 a0x^3 + a1x^2 + a2x + a3 の解を求める
\r
752 Math.qubicnewton = function(a0, a1, a2, a3, b) {
\r
753 var eps = 1e-10, //収束誤差
\r
754 fb = a0 *b*b*b + a1 *b*b + a2*b + a3; //方程式の結果
\r
759 for (var i=0;i<100;++i) {
\r
760 /*数値nは与えられた三次方程式を微分したもの*/
\r
761 var n = 3* a0 *b*b + 2 * a1 *b + a2;
\r
762 if (!n || ( (fb < eps) && (fb > -eps) )) {
\r
768 fb = a0 *b*b*b + a1 *b*b + a2*b + a3;
\r
771 return b; //収束しなかった結果
\r
774 /*$attribute オブジェクト
\r
775 * アニメーションの時間調整と、呈示値の調整を一つのオブジェクトにまとめて行うことで、
\r
776 * アニメーションサンドイッチの実装をする
\r
777 * $calcModeオブジェクトから継承*/
\r
778 base("$calcMode").up("$attribute").mix( {
\r
780 /*アニメーションの対象となる要素。たとえば、animate要素の親要素*/
\r
783 /*$fromオブジェクトを作るためのひな形となるオブジェクト*/
\r
784 $from: base("$from").up(),
\r
786 /*指定した要素の属性値を取得するメソッド*/
\r
787 _getAttr: function(/*string*/ name, def) {
\r
788 var nameSpace = null;
\r
789 if (name.indexOf("xlink:") > -1) {
\r
790 nameSpace = "http://www.w3.org/1999/xlink";
\r
792 /*DOM Level2やIE11では、getAttributeNSメソッドは空文字を返す。他のブラウザではnullを返すことが多い
\r
794 * >the empty string if that attribute does not have a specified or default value
\r
795 * http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-ElGetAttrNS*/
\r
796 return (this._ele.getAttributeNS(nameSpace, name) || def);
\r
799 _ele: document.documentElement,
\r
801 /*引数で指定した要素 ele の属性を解析して、フレームに追加する*/
\r
802 push: function(/*Element Node*/ ele) {
\r
803 if (!ele || !ele.hasAttribute) {
\r
806 this.element = ele.parentNode || null;
\r
808 if ( id = ele.getAttributeNS(null, "targetElement") ) {
\r
809 this.element = ele.ownerDocument.getElementById(id);
\r
811 /*getAttributeNSメソッドでうまくいかなかったため、NSなしで代用*/
\r
812 if ( id = ele.getAttribute("xlink:href") ) {
\r
813 this.element = ele.ownerDocument.getElementById(id.slice(1));
\r
815 if (!( ele.hasAttribute("from") || ele.hasAttribute("to")
\r
816 || ele.hasAttribute("by") || ele.hasAttribute("values") ) ) {
\r
817 /*from属性、to、by、values属性が指定されていない場合、アニメーションの効果が出ないように調整する
\r
820 *>if none of the from, to, by or values attributes are specified, the animation will have no effect
\r
821 *「3.2.2. Animation function values」より引用
\r
822 *http://www.w3.org/TR/2001/REC-smil-animation-20010904/#AnimFuncValues
\r
826 /*_getAttrメソッドで必要*/
\r
828 /*eleの属性の値を、それぞれオブジェクトに割り当て*/
\r
829 var $frame = base("$frame"),
\r
830 begin = $frame.$begin,
\r
831 frame = begin.up().mix( {
\r
832 /*targetプロパティはbeginEventなどの発火で使う*/
\r
834 eventTarget: (this.element || begin.eventTarget),
\r
835 string: this._getAttr("begin", "0"),
\r
836 $activate: begin.$activate.up().mix( {
\r
837 dur: this._getAttr("dur", null),
\r
838 end: begin.$end.up().mix( {
\r
839 eventTarget: (this.element || begin.eventTarget),
\r
840 string: this._getAttr("end", null)
\r
842 repeatCount: this._getAttr("repeatCount", null),
\r
843 repeatDur: this._getAttr("repeatDur", null),
\r
844 min: this._getAttr("min", "0"),
\r
845 max: this._getAttr("max", "indefinite")
\r
848 frame.$activate.end.$begin = frame;
\r
849 if (frame.isResolved) {
\r
850 /*開始時間が初期化されてしまうのを防ぐ*/
\r
851 var cacheBegin = frame.begin;
\r
853 /*アニメーションの開始をこのメソッドが呼ばれた時点とする*/
\r
854 timeStamp: Date.now()
\r
856 frame.begin = cacheBegin;
\r
858 /*setFrameメソッドを使ったときの、再帰スタックの使いすぎを防ぐため*/
\r
859 frame.timelines = [];
\r
860 begin = ele = id = void 0;
\r
865 * values属性やfrom属性やto属性を処理するためのメソッド
\r
866 * valuesは配列、それ以外の引数は文字列
\r
867 * 返り値は、values属性は配列、それ以外の属性のときは、
\r
868 * 自分自身となる$attributeオブジェクトのコピーを返す*/
\r
869 setValues: function(values, from, to, by) {
\r
870 var $from = this.$from,
\r
871 s = [this.up().mix( {
\r
872 to: $from.up().mix( {
\r
877 values = values && values.split(";");
\r
878 /*from属性はオプションなので、条件には付け加えない*/
\r
879 if (values && values.length) {
\r
880 /*values属性が指定された場合、他の属性は無視される
\r
881 * W3C仕様 SMIL アニメーション 3.2.2. アニメーション関数の値*/
\r
883 for (var i=1;i<values.length;++i) {
\r
884 s.push( this.up().mix( {
\r
885 to: $from.up().mix( {
\r
889 sto = s[s.length-1].to;
\r
890 sto.string = values[i];
\r
891 sto.from.string = values[i-1];
\r
895 sto.from.string = from || "0";
\r
898 sto.from.string = from || "0";
\r
899 var toNumList = sto.call(),
\r
900 fromNumList = sto.from;
\r
901 for (var i=0;i<toNumList.length;++i) {
\r
903 toNumList[i] += fromNumList[i];
\r
908 $from = sto = toNumList = fromNumList = void 0;
\r
913 * 引数の要素のkeyTimes属性やkeySplines属性を処理するためのメソッド
\r
914 * 必要な他の属性処理はsetValuesメソッドに任せている*/
\r
915 setKey: function(ele) {
\r
917 var to = this.setValues(this._getAttr("values", null),
\r
918 this._getAttr("from", null),
\r
919 this._getAttr("to", null),
\r
920 this._getAttr("by", null) ),
\r
921 keyTimes = this._getAttr("keyTimes", null),
\r
922 keySplines = this._getAttr("keySplines", null),
\r
924 splines = keySplines && keySplines.split(";"),
\r
925 isDiscrete = (this.mode === "discrete"),
\r
927 if (!isDiscrete && keyTimes && to) {
\r
928 keys = this.$from.numList.call( {
\r
931 /*toオブジェクトはtoとfromで一組となっているのでlengthが加算される*/
\r
932 if (keys.length && (keys.length !== (to.length+1))) {
\r
933 /*keyTimes属性とvalues属性のリストの個数が合致しない場合、アニメーションの効果がない
\r
934 * 仕様を参照 SMIL Animation 3.2.3. Animation function calculation modes
\r
935 * http://www.w3.org/TR/smil-animation/#AnimFuncCalcMode*/
\r
938 for (var i=0;i<to.length;++i) {
\r
939 to[i].keyTime = keys[i+1] - keys[i];
\r
941 toiKeySplines = this.$from.numList.call( {
\r
944 /*空配列を返すため、nullに変えておく*/
\r
945 to[i].keySplines = toiKeySplines.length ? toiKeySplines : null;
\r
948 } else if (!isDiscrete && to) {
\r
949 var per = 1 / to.length;
\r
950 for (var i=0;i<to.length;++i) {
\r
951 to[i].keyTime = per;
\r
953 toiKeySplines = this.$from.numList.call( {
\r
956 to[i].keySplines = toiKeySplines.length ? toiKeySplines : null;
\r
962 keys = this.$from.numList.call( {
\r
965 if (keys.length && (keys.length !== (to.length+1))) {
\r
968 for (var i=0;i<to.length;++i) {
\r
969 to[i].keyTime = keys[i];
\r
972 var per = 1 / (to.length+1);
\r
973 for (var i=0;i<to.length;++i) {
\r
974 to[i].keyTime = per;
\r
977 /*toオブジェクトが足らないので、一つ追加しておく*/
\r
978 to.push( to[to.length-1].up().of( {
\r
980 return function (t) {
\r
981 return isNaN(t) ? this.string
\r
987 if (this.mode === "paced") {
\r
990 to.forEach( function(x) {
\r
991 norm += x.to.distance(x.to.from);
\r
993 to.forEach( function(x) {
\r
997 ele = keyTimes = keys = per = splines = void 0;
\r
1000 } ).up("$setElement").mix( {
\r
1004 /*attributeName属性の値*/
\r
1010 /*もともと属性がターゲットの要素につけられていたかどうか*/
\r
1014 attrNameSpace: null,
\r
1016 /*initメソッドで使われるアニメーション関数*/
\r
1017 _setFrame: function (frame) {
\r
1018 this.element.setAttributeNS(this.attrNameSpace, this.attrName, this.to);
\r
1021 /*アニメーションが終了したかどうか*/
\r
1024 /*開始を設定されたタイムライン ($beginオブジェクト)*/
\r
1025 timeline: base("$frame").$begin,
\r
1028 * 終了した後は、ひたすらtrueを値として返す*/
\r
1029 _setEndFrame: function(frame) {
\r
1030 var line = this.timeline;
\r
1031 if (( frame < (line.begin + line.activeTime) ) || this.isEnd) {
\r
1032 /*アニメーションが終了間近でなければ凍結の処理をしない*/
\r
1033 line = frame = void 0;
\r
1035 } else if (!isNaN(line.begin + line.activeTime)) {
\r
1036 /*イベントが設定されていないか、解決済みである場合*/
\r
1037 this.isEnd = true;
\r
1038 /*removeの場合、アニメーションを凍結せずに、もとに戻す*/
\r
1039 if ((this.fill === "remove") && this.isDefault) {
\r
1040 this.element.setAttributeNS(this.attrNameSpace, this.attrName, this.defaultValue);
\r
1041 } else if (this.fill === "remove"){
\r
1042 this.element.removeAttributeNS(this.attrNameSpace, this.attrName);
\r
1044 line = frame = void 0;
\r
1049 /*アニメーションの呈示値を呼び出す関数*/
\r
1050 _tocall: function() {},
\r
1052 init: function(ele) {
\r
1053 var line = this.push(ele);
\r
1054 if (ele && ele.getAttributeNS) {
\r
1056 this.to = this._getAttr("to", "");
\r
1057 this.attrName = this._getAttr("attributeName", "");
\r
1058 this.fill = this._getAttr("fill", "remove");
\r
1060 var thisele = this.element;
\r
1061 if (line && thisele) {
\r
1062 this.timeline = line;
\r
1063 this._ele = thisele;
\r
1064 if (this.attrName.indexOf("xlink") > -1) {
\r
1065 this.attrNameSpace = "http://www.w3.org/1999/xlink";
\r
1067 this.isDefault = thisele.hasAttributeNS(this.attrNameSpace, this.attrName);
\r
1068 this.defaultValue = this._getAttr(this.attrName,
\r
1069 thisele.ownerDocument.defaultView.getComputedStyle(thisele, "").getPropertyValue(this.attrName) );
\r
1070 /*ラインの中に、属性処理をするためのラインを追加*/
\r
1072 { setFrame: this._setFrame.bind(this),
\r
1078 base("$frame").addLine(
\r
1079 { setFrame: this._setEndFrame.bind(this),
\r
1082 rank: Number.MAX_VALUE //最低ランクにすることで、一番最後にタイムラインを実行させる
\r
1086 /*アニメーションが再起動する可能性もあるため、isEndプロパティはここで初期化*/
\r
1087 this.isEnd = false;
\r
1088 line = thisele = void 0;
\r
1090 }).up("$animateElement").mix( {
\r
1094 /*進捗率advanceから、呈示値を求める*/
\r
1095 _tocall: function(advance) {
\r
1096 var tf = this.funcs;
\r
1097 for (var i=0;i<tf.length;++i) {
\r
1099 /*keyTime(keyTimes属性で指定されたような値)で実行するかどうかを判別*/
\r
1100 if (tfi.endKeyTime >= advance) {
\r
1101 return tfi(advance - tfi.startKeyTime);
\r
1104 tf = i = tfi = void 0;
\r
1108 _setFrame: function(currentTime) {
\r
1110 *advanceは継続時間内での、進捗率
\r
1111 * 仕様を参照 http://www.w3.org/TR/smil-animation/#AnimFuncValues
\r
1112 *進捗率advanceは、durationと進捗フレーム数とを割った余り(REMAINDER)で算出する
\r
1113 * 仕様を参照 SMIL Animation 3.6.2 Interval timing
\r
1114 * http://www.w3.org/TR/2001/REC-smil-animation-20010904/#IntervalTiming*/
\r
1115 var line = this.timeline,
\r
1116 duration = line.simpleDuration,
\r
1117 /*単純継続時間が不定の場合、補間はせずに初期値が採用されるため、advanceは0となる
\r
1118 * 仕様を参照 SMIL Animation 3.2.2. Animation function values のInterpolation and indefinite simple durations
\r
1119 * http://www.w3.org/TR/2001/REC-smil-animation-20010904/#AnimFuncValues*/
\r
1120 advance = duration ? ( (currentTime - line.begin) % duration ) / duration
\r
1122 this.element.setAttributeNS(this.attrNameSpace, this.attrName, this._tocall(advance));
\r
1123 line = duration = advance = void 0;
\r
1126 _setEndFrame: function(frame) {
\r
1127 /*上書きされたメソッドを呼び出してアニメーションの凍結作業をする*/
\r
1128 if (!this.$setElement._setEndFrame.call(this, frame)
\r
1129 && (this.fill === "freeze")) {
\r
1130 var line = this.timeline,
\r
1131 duration = line.simpleDuration;
\r
1133 var advance = ( line.activeTime % duration ) / duration;
\r
1134 /*例外が発生するため、進捗率が1を超えないように処理*/
\r
1135 advancd = (advance > 1) ? 1 : advance;
\r
1136 /*活動継続時間と単純継続時間が一致すると、余りは0となるため以下の処理*/
\r
1137 advance = advance || 1;
\r
1141 this.element.setAttributeNS(this.attrNameSpace, this.attrName, this._tocall(advance));
\r
1142 line = duration = advance = void 0;
\r
1147 * onメソッドについては、base.jsを参照のこと*/
\r
1148 } ).on ("init", function(ele) {
\r
1149 var isColor = /^fill|stroke|stop-color|color$/.test(this.attrName);
\r
1151 this.setValues = function() {
\r
1152 /*RGB形式では補間に、小数を使わない*/
\r
1153 var s = this.$attribute.setValues.apply(this, arguments);
\r
1154 s.forEach(function(x) {
\r
1162 /*関数fはrgbColor形式への変換処理で使う*/
\r
1163 toRGB = function(x) { return x; };
\r
1165 this.mode = ele.getAttributeNS(null, "calcMode") || "linear";
\r
1166 to = this.setKey(ele);
\r
1169 this.setValues = this.$attribute.setValues;
\r
1170 /*#から始まる文字列を、rgb(.., .., ..,)形式へと変換するための関数*/
\r
1171 toRGB = function(rgbColor) {
\r
1172 var keyword = base("$CSSValue").$SVGColor._keywords[rgbColor];
\r
1174 return "rgb(" + keyword.join(", ") + ")";
\r
1176 if (rgbColor[0] === "#") { //#を含む場合
\r
1178 _parseInt = parseInt;
\r
1179 if (rgbColor.length < 5) {
\r
1180 var r = rgbColor[1],
\r
1183 rgbColor = "#" + r + r + g + g + b + b;
\r
1185 rgbColor.match(/\#(\w{2})(\w{2})(\w{2})/);
\r
1186 s += _parseInt(RegExp.$1, 16)
\r
1188 + _parseInt(RegExp.$2, 16)
\r
1190 + _parseInt(RegExp.$3, 16)
\r
1192 r = g = b = void 0;
\r
1199 this.funcs = to.map( function(x) {
\r
1200 x.to.string = toRGB(x.to.string);
\r
1201 x.to.from.string = toRGB(x.to.from.string);
\r
1203 /*x.keyTimeプロパティは区間を示しているため、区切り時刻に変換しておく
\r
1204 * startKeyTimeプロパティは区間のスタート時点
\r
1205 * endKeyTimeプロパティは区間のエンド地点*/
\r
1206 s.startKeyTime = keyTime;
\r
1207 keyTime = s.endKeyTime = keyTime + x.keyTime;
\r
1210 .filter( function(s) {
\r
1211 /*splineモードで、かつ、円周率を返す関数の場合は配列からはねておく*/
\r
1212 return (this.mode !== "spline")
\r
1213 || (s(0.1) !== Math.PI);
\r
1218 /*$svgEventオブジェクトは、SVGEvent発火を監視するためのオブジェクト*/
\r
1219 base("$frame").up("$svgEvent").mix( {
\r
1223 /*タイムラインの最後のキャッシュ*/
\r
1224 lastTimeLine: null,
\r
1226 /*setTimeTable メソッドはスケジュールの記録をつけるためのメソッド*/
\r
1227 setTimeTable: function () {
\r
1228 var timelines = this.timelines;
\r
1229 for (var i=0, obj = null;i<timelines.length;++i) {
\r
1230 if (!timelines[i].target) {
\r
1231 /*target オブジェクトがないものは除外*/
\r
1234 /*タイムラインから、beginEventとendEventを発火するスケジュールを作成*/
\r
1235 var timeline = timelines[i],
\r
1236 begin = timeline.begin,
\r
1237 target = timeline.target,
\r
1238 simpleDur = timeline.simpleDuration,
\r
1239 activeTime = timeline.activeTime,
\r
1242 eventType: "begin",
\r
1245 frame: begin+activeTime,
\r
1252 obj = obj.next.next = first;
\r
1254 obj = this.first = first;
\r
1256 if (simpleDur && (activeTime !== simpleDur)) {
\r
1257 /*活動継続時間と単純持続時間が異なるとき、repeatイベントを設定
\r
1258 * ただし、repeatイベントはendイベントが発生する前に起きるものと仮定*/
\r
1260 firstFrame: begin + simpleDur,
\r
1261 frame: begin + simpleDur,
\r
1262 eventType: "repeat",
\r
1265 limit: begin + activeTime,
\r
1266 /*リピートの回数 (n >= 1)*/
\r
1268 simpleDuration: simpleDur,
\r
1273 timelines = obj = first = begin = target = simpleDur = activeTime = void 0;
\r
1276 $frame: base("$frame"),
\r
1278 setFrame: function (num) {
\r
1279 var timelines = this.timelines,
\r
1280 lastTimeLine = timelines[timelines.length-1],
\r
1281 s = this.$frame.setFrame(num);
\r
1282 /*キャッシュのlastTimeLineプロパティを使って、再びスケジュールの計算をさせないようにする*/
\r
1283 if (this.lastTimeLine !== lastTimeLine) {
\r
1284 this.lastTimeLine = lastTimeLine;
\r
1285 this.setTimeTable();
\r
1287 /*スケジュールに記録しておいたものを実行して、イベントを発火
\r
1288 * また、発火した場合は記録から取り除いて、次回から再び発火しないようにする*/
\r
1289 var obj = this.first,
\r
1291 floor = Math.floor;
\r
1293 var frame = obj.frame,
\r
1294 target = obj.target
\r
1296 if (frame <= num) {
\r
1297 /*IE11ではSVGEventsやDOMEventsを使うと問題が起きるため、MouseEventsで代用する*/
\r
1298 if (obj.eventType === "repeat") {
\r
1299 var simpleDuration = obj.simpleDuration;
\r
1300 /*リピートイベントが、リピート制限内である場合
\r
1301 *numの段階で、何回リピートしたかを求める*/
\r
1302 detail = obj.count = floor( (num - obj.firstFrame) / simpleDuration) + 1;
\r
1303 /*simpleDurationを足すことによって、リピートイベントが
\r
1304 * 単純継続時間内に何度も繰り返されることを防ぐ*/
\r
1305 frame += simpleDuration;
\r
1306 obj.frame = frame;
\r
1308 /*obj.limitはrepeatイベントの制限で使われるもの*/
\r
1309 if ((obj.eventType !== "repeat") || (frame >= obj.limit)) {
\r
1310 /*ポインタの連結を変更することで、リストからobj を除去*/
\r
1311 cobj.next = obj.next;
\r
1312 if (this.first === obj) {
\r
1313 cobj = this.first = obj.next;
\r
1318 var evt = target.ownerDocument.createEvent("MouseEvents");
\r
1319 evt.initMouseEvent(obj.eventType+"Event" ,true, true, window, detail, 0, 0, 0, 0, false, false, false, false, 0, target);
\r
1320 target.dispatchEvent(evt);
\r
1322 /*next プロパティを書き換えるためのobj オブジェクトのキャッシュ*/
\r
1327 obj = first = frame = target = cobj = simpleDuration = detail = void 0;
\r
1332 function getDocument()
\r
1334 var svg = document.getElementsByTagName("object"),
\r
1335 svgns = "http://www.w3.org/2000/svg";
\r
1336 for (var i=0;i<svg.length;++i) {
\r
1338 var svgDoc = svg[i].getSVGDocument(),
\r
1339 $set = base("$calcMode").$attribute.$setElement;
\r
1340 init($set, "set");
\r
1341 init($set.$animateElement, "animate");
\r
1342 init($set.$animateElement, "animateColor");
\r
1347 function init (obj, name) {
\r
1348 var eles = svgDoc.getElementsByTagNameNS(svgns, name)
\r
1349 for (var i=0;i<eles.length;++i) {
\r
1350 obj.up().init(eles.item(i));
\r
1352 eles = obj = void 0;
\r
1355 window.addEventListener && window.addEventListener("load", getDocument);
\r
1357 function __step() {
\r
1358 if (!document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Animation", "1.1")) {
\r
1359 if (window.requestAnimationFrame && requestAnimationFrame) {
\r
1360 /*IE11などSMILアニメーションに対応していないブラウザ用*/
\r
1361 (function(frame) {
\r
1362 var $f = base("$frame").$svgEvent;
\r
1363 $f.startTime = Date.now();
\r
1364 requestAnimationFrame(step);
\r
1368 $f.setFrame(frame);
\r
1370 requestAnimationFrame(step);
\r
1374 setInterval( (function(frame) {
\r
1375 var $f = base("$frame").$svgEvent;
\r
1376 $f.startTime = Date.now();
\r
1377 return function () {
\r
1379 $f.setFrame(frame);
\r
1385 //#endif // _SMIL_IDL_
\r