訪問http://caniuse.com/#search=svg
IE對SVG動畫支持不好。
那我們就說服老板不支持IE。
老板任性,硬着頭皮上。
訪問 https://github.com/FakeSmile/FakeSmile 這個項目。
它用模擬的方式實現SVG動畫。
使用demo如下:
<!DOCTYPE HTML>
<head>
</head>
<script type="text/javascript" src="./svg.js"></script>
<body>
<meta http-equiv="content-type" content="application/xhtml+xml; charset=utf-8" />
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.0" viewBox="0 0 120 40" xml:space="preserve">
<rect x="0" y="0" width="10" height="10" style="fill:blue">
<animate attributeName="x" from="0" to="100" dur="5s" repeatCount="indefinite" />
</rect>
</svg>
<svg width="100%" height="100%" version="1.1"
xmlns="http://www.w3.org/2000/svg">
<circle cx="100" cy="100" r="30" stroke='red' strokeWidth= 1 fill = 'none'>
<animate attributeType="XML"
attributeName="r"
from="20" to="60"
dur="2s"
repeatCount="indefinite">
</animate>
</circle>
<circle cx="100" cy="100" r="20" stroke='red' strokeWidth= 1 fill = 'none'>
<animate attributeType="XML"
attributeName="r"
from="10" to="40"
dur="2s"
repeatCount="indefinite">
</animate>
</circle>
<circle cx="100" cy="100" r="15" stroke='red' strokeWidth= 1 fill = 'none'>
<animate attributeType="XML"
attributeName="r"
from="5" to="20"
dur="2s"
repeatCount="indefinite">
</animate>
</circle>
<circle cx="100" cy="100" r="5" stroke='red' strokeWidth= 1 fill = 'red'></circle>
</svg>
</body>
</html>
svg.js
/*
@id {7eeff186-cfb4-f7c3-21f2-a15f210dca49}
@name FakeSmile
@version 0.3.0
@description SMIL implementation in ECMAScript
@creator David Leunen (leunen.d@gmail.com)
@homepageURL http://leunen.me/fakesmile/
@ff_min_version 2.0
@ff_max_version 3.*
*/
// ==UserScript==
// @name smil
// @namespace svg.smil
// ==/UserScript==
/* MIT or GPLv3 Licenses */
/*
Copyright 2008 David Leunen
Copyright 2012 Helder Magalhaes
*/
/**
* Milliseconds Per Frame - relation between animation smoothness and resources usage:
* 83 for ~12fps (standard quality web animation; low CPU usage; slightly jumpy; recommended for discrete or slow-motion animations);
* 67 for ~15fps (high quality web animation; reasonable resources usage; recommended for most use-cases);
* 40 for 25fps ("cine"-look; recommended for good quality animations on television systems);
* 33 for ~30fps (half LCD refresh rate; recommended for high quality animations on desktop systems);
* 25 for 40fps (very smooth animation; recommended for high quality animations on dedicated desktop systems);
* 17 for ~60fps (LCD refresh rate; high CPU and system overhead; only recommended for very high quality animations running on high-end systems).
* Lower values are *not* recommended - may cause an overall negative impact on the Operating System and a relevant energy consumption increase!
* References:
* http://animation.about.com/od/faqs/f/faq_fpsnumber.htm
* http://en.wikipedia.org/wiki/Frame_rate#Frame_rates_in_film_and_television
* https://www.nczonline.net/blog/2011/12/14/timer-resolution-in-browsers/
*/
var mpf = 67;
var splinePrecision = 25;
var svgns="http://www.w3.org/2000/svg";
var smilanimns="http://www.w3.org/2001/smil-animation";
var smil2ns="http://www.w3.org/2001/SMIL20";
var smil21ns="http://www.w3.org/2005/SMIL21";
var smil3ns="http://www.w3.org/ns/SMIL30";
var timesheetns="http://www.w3.org/2007/07/SMIL30/Timesheets";
var xlinkns="http://www.w3.org/1999/xlink";
var animators = new Array(); // all animators
var id2anim = new Object(); // id -> animation elements (workaround a Gecko bug)
var animations = new Array(); // running animators
var timeZero; // timeline start-up timestamp
var prevTime; // previous render timestamp
var animTimer; // render loop timer id, when active
/**
* If declarative animations are not supported,
* the document animations are fetched and registered.
*/
function initSMIL() {
if (document.documentElement.getAttribute("smiling")=="fake")
return;
document.documentElement.setAttribute("smiling", "fake");
smile(document);
timeZero = new Date();
prevTime = new Date(0); // not yet rendered
// I schedule them (after having instantiating them, for sync-based events)
// (it doesn't work either: first 0s animation don't trigger begin event to the following -> make it asynchronous)
for (var i=0, j=animators.length; i<j; ++i)
animators[i].register();
}
function getURLCallback(data) {
if (data.success)
smile(parseXML(data.content, document));
}
function xhrCallback() {
if (this.readyState==4 && this.status==200 && this.responseXML!=null)
smile(this.responseXML);
}
function smile(animating) {
var request = null;
var src = null;
var impl = document.implementation;
// namespace-to-process cache
// ("process" in the sense of "feature check states that support by script is needed")
// (map is initialized this way to avoid variables names being picked up as key instead of their value)
var ns2proc = {};
// NOTE: feature strings are broken in ASV - apparently only declarative switch declarations work
// (we have already filter this implementation, though, during the loading phase)
// http://tech.groups.yahoo.com/group/svg-developers/message/61236
ns2proc[svgns] = !impl.hasFeature("http://www.w3.org/TR/SVG11/feature#Animation", "1.1"); //&& !impl.hasFeature("org.w3c.svg.animation", "1.0");
ns2proc[smilanimns] = !impl.hasFeature(smilanimns, "1.1");
ns2proc[smil2ns] = !impl.hasFeature(smil2ns, "2.0");
ns2proc[smil21ns] = !impl.hasFeature(smil21ns, "2.1");
ns2proc[smil3ns] = !impl.hasFeature(smil3ns, "3.0");
ns2proc[timesheetns] = !impl.hasFeature(timesheetns, "1.0");
var animates = animating.getElementsByTagName("*");
for (var i=0, j=animates.length; i<j; ++i) {
var anim = animates.item(i);
var nodeName = anim.localName;
var namespaceURI = anim.namespaceURI;
switch (nodeName.length) {
case 4: // "link".length
if ((nodeName=="link" || nodeName=="LINK") && anim.getAttribute("rel")=="timesheet" && anim.getAttribute("type")=="application/smil+xml") {
src = anim.getAttribute("src");
if (src)
break;
}
continue;
case 9: // "timesheet".length
if (nodeName=="timesheet" && ns2proc[anim.namespaceURI]) {
src = anim.getAttribute("href");
if (src)
break;
}
continue;
case 3: // "set".length
if (nodeName=="set") {
break;
}
continue;
case 7: // "animate".length
if (nodeName=="animate") {
break;
}
continue;
case 12: // "animateColor".length
if (nodeName=="animateColor") {
break;
}
continue;
case 13: // "animateMotion".length
if (nodeName=="animateMotion") {
break;
}
continue;
case 16: // "animateTransform".length
if (nodeName=="animateTransform") {
break;
}
continue;
default:
continue;
}
// deal with external timesheets
if (src && src.length > 0) {
if (!request){
// lazy initialization of XHR
request = window.XMLHttpRequest ? new XMLHttpRequest() : window.ActiveXObject ? new ActiveXObject("MSXML2.XMLHTTP.3.0") : null;
if (request) {
if (request.overrideMimeType)
request.overrideMimeType('text/xml');
request.onreadystatechange = xhrCallback;
}
}
if (request) {
request.open("GET", src, false);
request.send(null);
} else if (window.getURL && window.parseXML) {
getURL(src, getURLCallback);
}
// reset variable
src = null;
continue;
}
// deal with animations
if (ns2proc[anim.namespaceURI]) {
var targets = getTargets(anim);
var elAnimators = new Array();
for (var k=0; k<targets.length; ++k) {
var target = targets[k];
var animator = new Animator(anim, target, k);
animators.push(animator);
elAnimators[k] = animator;
}
anim.animators = elAnimators;
var id = anim.getAttribute("id");
if (id)
id2anim[id] = anim;
}
}
}
function getTargets(anim) {
if (anim.hasAttribute("select"))
return select(anim);
var href = anim.getAttributeNS(xlinkns, "href");
if (href!=null && href!="")
return [document.getElementById(href.substring(1))];
else {
var target = anim.parentNode;
if (target.localName=="item" && (target.namespaceURI==timesheetns || target.namespaceURI==smil3ns))
return select(target);
return [target];
}
}
function select(element) {
var selector = element.getAttribute("select");
var parent = element.parentNode;
while(parent && parent.nodeType==1) {
if (parent.localName=="item" && (parent.namespaceURI==timesheetns || parent.namespaceURI==smil3ns))
selector = parent.getAttribute("select")+" "+selector;
parent = parent.parentNode;
}
return document.querySelectorAll(selector);
}
function getEventTargetsById(id, ref) {
var element = null;
if (id=="prev") {
element = ref.previousSibling;
while(element && element.nodeType!=1)
element = element.previousSibling;
}
if (element==null)
element = document.getElementById(id);
if (element==null)
element = id2anim[id]; // because getElementById doesn't returns SMIL elements in Gecko
if (element==null)
return null;
if (element.animators)
return element.animators;
return [element];
}
/**
* Corresponds to one <animate>, <set>, <animateTransform>, ...
* (there can be more than one Animator for each element)
*/
Animator.prototype = {
/**
* Registers the animation.
* It schedules the beginnings and endings.
*/
register : function() {
var begin = this.anim.getAttribute("begin");
if (begin)
this.schedule(begin, this.begin);
else
this.begin(0);
var end = this.anim.getAttribute("end");
if (end)
this.schedule(end, this.finish);
},
/**
* Schedules the starts or ends of the animation.
*/
schedule : function(timeValueList, func) {
var me = this; // I do that because if I use "this", the addEventListener understands the event source
var timeValues = timeValueList.split(";");
for (var i=0; i<timeValues.length; ++i) {
var time = timeValues[i].trim();
if (time.length>11 && time.substring(0,10)=="wallclock(") {
var wallclock = new Date();
wallclock.setISO8601(time.substring(10,time.length-1));
if (!isNaN(wallclock.getTime())) {
var now = new Date();
var diff = wallclock-now;
func.call(me, diff);
}
} else if (isNaN(parseInt(time))) {
var offset = 0;
var io = time.indexOf("+");
if (io==-1)
io = time.indexOf("-");
if (io!=-1) {
offset = toMillis(time.substring(io).replace(/ /g, ""));
time = time.substring(0, io).trim();
}
io = time.indexOf(".");
var elements;
if (io==-1) {
elements = [this.target];
} else {
var id = time.substring(0, io);
if (id.indexOf("index(")==0)
id = id.substring(6,id.length-1)+this.index;
elements = getEventTargetsById(id, this.anim);
}
var event = time.substring(io+1);
var call = funk(func, me, offset);
for (var j=0; j<elements.length; ++j) {
var element = elements[j];
if (element==null)
continue;
element.addEventListener(event, call, false);
}
} else {
time = toMillis(time);
func.call(me, time);
}
}
},
/**
* Remembers the initial value of the animated attribute.
* This function is overridden.
*/
getCurVal : function() {
if (this.attributeType=="CSS") {
// should use this.target.getPresentationAttribute instead
return this.target.style.getPropertyValue(this.attributeName);
} else {
//var animAtt = this.target[this.attributeName];
//if (animAtt && animAtt.animVal)
// return animAtt.animVal.value;
//else
return this.target.getAttributeNS(this.namespace, this.attributeName);
}
},
/**
* Starts the animation.
* I mean the very beginning of it.
* Not called when repeating.
*/
begin : function(offset) {
if (this.restart=="never" || (this.running && this.restart=="whenNotActive"))
return;
if (this.running)
this.finish();
if (offset && offset>0) {
var me = this;
var myself = this.begin;
var call = function() {myself.call(me)};
window.setTimeout(call, offset);
return;
}
this.startTime = new Date();
if (offset && offset<0) {
this.startTime.setTime(this.startTime.getTime()+offset);
if (this.startTime<timeZero)
return;
}
this.stop();
this.running = true;
var initVal = this.getCurVal();
this.realInitVal = initVal;
// TODO
// I should get the inherited value here (getPresentationAttribute is not supported)
if (!initVal && propDefaults[this.attributeName] )
initVal = propDefaults[this.attributeName];
if (this.anim.nodeName=="set")
this.step(this.to);
this.iteration = 0;
if (this.values) {
this.animVals = this.values.split(";");
for (var i=0; i<this.animVals.length; ++i)
this.animVals[i] = this.animVals[i].trim();
} else {
this.animVals = new Array();
if (this.from)
this.animVals[0] = this.from;
else
this.animVals[0] = initVal;
if (this.by && this.animVals[0])
this.animVals[1] = this.add(this.normalize(this.animVals[0]), this.normalize(this.by));
else
this.animVals[1] = this.to;
}
if (this.animVals[this.animVals.length-1]) {
this.freezed = this.animVals[this.animVals.length-1];
if (this.animVals[0]) {
if ( (this.animVals[0][0]=="#" || colors[this.animVals[0]] || (this.animVals[0].length>5 && this.animVals[0].trim().substring(0,4)=="rgb(")) &&
(this.freezed[0]=="#" || colors[this.freezed] || (this.freezed.length>5 && this.freezed.trim().substring(0,4)=="rgb(")) )
this.color();
else {
var cp = new Array();
var oneVal = this.animVals[0];
var qualified = getUnit(oneVal);
cp[0] = qualified[0];
this.unit = qualified[1];
for (var i=1; i<this.animVals.length; ++i) {
var oneVal = this.animVals[i];
var qualified = getUnit(oneVal);
if (qualified[1]==this.unit)
cp[i] = qualified[0];
else {
cp = this.animVals;
break;
}
}
this.animVals = cp;
}
}
}
this.iterBegin = this.startTime;
animations.push(this);
// if this is the first running animator, start the rendering loop
if (!animTimer) {
// asynchronous to render all animators, listeners, etc. starting at this frame
window.setTimeout(animate, 0);
// schedules the rendering loop
animTimer = window.setInterval(animate, mpf);
}
for (var i=0; i<this.beginListeners.length; ++i)
this.beginListeners[i].call();
var onbegin = this.anim.getAttribute("onbegin");
if (onbegin)
eval(onbegin);
},
/**
* This function is overridden for multiple values attributes (scale, rotate, translate).
*/
normalize : function(value) {
return value;
},
/**
* Sums up two normalized values.
*/
add : function(a, b) {
return ""+(parseFloat(a)+parseFloat(b));
},
/**
* Computes and applies the animated value for a given time.
* Returns false if this animation has been stopped (removed from the running array).
*/
f : function(curTime) {
var dur = this.computedDur;
if (isNaN(dur))
return true;
var beginTime = this.iterBegin;
var diff = curTime-beginTime;
var percent = diff/dur;
if (percent>=1)
return this.end(curTime);
var iteration = this.iteration;
if (this.repeatCount && this.repeatCount!="indefinite" && (iteration+percent)>=this.repeatCount) {
if (this.fill=="freeze")
this.freezed = this.valueAt(this.repeatCount-iteration);
return this.end(curTime);
}
if (this.repeatDur && this.repeatDur!="indefinite" && (curTime-this.startTime)>=toMillis(this.repeatDur)) {
if (this.fill=="freeze") {
var div = toMillis(this.repeatDur)/dur;
this.freezed = this.valueAt(div-Math.floor(div));
}
return this.end(curTime);
}
var anim = this.anim;
if (anim.localName=="set")
return true;
var curVal = this.valueAt(percent);
this.step(curVal);
return true;
},
isInterpolable : function(from, to) {
var areN = (!isNaN(from) && !isNaN(to));
if (!areN && from.trim().indexOf(" ")!=-1 && to.trim().indexOf(" ")!=-1) {
var tfrom = from.trim().split(" ");
var tto = to.trim().split(" ");
areN = true;
if (tfrom.length==tto.length)
for (var i=0; i<tto.length; ++i)
if (!this.isInterpolable(tfrom[i], tto[i]))
return false;
}
return areN;
},
valueAt : function(percent) {
var tValues = this.animVals;
if (percent==1)
return tValues[tValues.length-1];
if (this.calcMode=="discrete" || !this.isInterpolable(tValues[0],tValues[1])) {
if (this.keyTimes) {
for (var i=1; i<this.keyTimes.length; ++i)
if (this.keyTimes[i]>percent)
return tValues[i-1];
return tValues[tValues.length-1];
}
var parts = tValues.length;
var div = Math.floor(percent*parts);
return tValues[div];
} else {
var index;
if (this.keyTimes) {
for (var i=1; i<this.keyTimes.length; ++i)
if (this.keyTimes[i]>percent) {
index = i-1;
var t1 = this.keyTimes[index];
percent = (percent-t1)/(this.keyTimes[i]-t1);
break;
}
if (i>=this.keyTimes.length)
index = i-2;
} else {
var parts = tValues.length-1;
index = Math.floor(percent*parts);
percent = (percent%(1/parts))*parts;
}
if (this.calcMode=="spline")
percent = this.spline(percent, index);
return this.interpolate(this.normalize(tValues[index]), this.normalize(tValues[index+1]), percent);
}
},
spline : function(percent, index) {
var path = this.keySplines[index];
var tot = path.getTotalLength();
var step = tot/splinePrecision;
for (var i=0; i<=tot; i+=step) {
var pt = path.getPointAtLength(i);
if (pt.x>percent) {
var pt1 = path.getPointAtLength(i-step);
percent -= pt1.x;
percent /= pt.x-pt1.x;
return pt1.y+((pt.y-pt1.y)*percent);
}
}
var pt = path.getPointAtLength(tot);
var pt1 = path.getPointAtLength(tot-step);
percent -= pt1.x;
percent /= pt.x-pt1.x;
return pt1.y+((pt.y-pt1.y)*percent);
},
/**
* Performs the interpolation.
* This function is overridden.
*/
interpolate : function(from, to, percent) {
if (!this.isInterpolable(from, to)) {
if (percent<.5)
return from;
else
return to;
}
if (from.trim().indexOf(" ")!=-1) {
var tfrom = from.split(" ");
var tto = to.split(" ");
var ret = new Array();
for (var i=0; i<tto.length; ++i)
ret[i] = parseFloat(tfrom[i])+((tto[i]-tfrom[i])*percent);
return ret.join(" ");
}
return parseFloat(from)+((to-from)*percent);
},
/**
* Apply a value to the attribute the animator is linked to.
* This function is overridden.
*/
step : function(value) {
var attributeName = this.attributeName;
var attributeType = this.attributeType;
if (attributeType=="CSS") {
// workaround a Gecko and WebKit bug
if (attributeName=="font-size" && !isNaN(value))
value += "px";
else if (this.unit)
value += this.unit;
this.target.style.setProperty(attributeName, value, "");
} else {
if (this.unit)
value += this.unit;
//var animAtt = this.target[attributeName];
//if (animAtt && animAtt.animVal)
// animAtt.animVal.value = value;
//else
this.target.setAttributeNS(this.namespace, attributeName, value);
}
},
/**
* Normal end of the animation:
* it restarts if repeatCount.
*/
end : function(now) {
if (!this.repeatCount && !this.repeatDur)
return this.finish();
else {
++this.iteration;
if (this.repeatCount && this.repeatCount!="indefinite" && this.iteration>=this.repeatCount)
return this.finish();
else if (this.repeatDur && this.repeatDur!="indefinite" && (now-this.startTime)>=toMillis(this.repeatDur))
return this.finish();
else {
if (this.accumulate=="sum") {
var curVal = this.getCurVal();
if (!curVal && propDefaults[this.attributeName] )
curVal = propDefaults[this.attributeName];
if (this.by && !this.from) {
this.animVals[0] = curVal;
this.animVals[1] = this.add(this.normalize(curVal), this.normalize(this.by));
} else {
for (var i=0; i<this.animVals.length; ++i)
this.animVals[i] = this.add(this.normalize(curVal), this.normalize(this.animVals[i]));
}
this.freezed = this.animVals[this.animVals.length-1];
}
this.iterBegin = now;
for (var i=0; i<this.repeatIterations.length; ++i) {
if (this.repeatIterations[i]==this.iteration)
this.repeatListeners[i].call();
}
var onrepeat = this.anim.getAttribute("onrepeat");
if (onrepeat)
eval(onrepeat);
}
}
return true;
},
/**
* Really stop of the animation (it doesn't repeat).
* Freezes or removes the animated value.
*/
finish : function(offset) {
if (this.min && this.min!="indefinite") {
var now = new Date();
if ((now-this.startTime)>=this.computedMin)
return true;
}
if (offset && offset>0) {
var me = this;
var myself = this.finish;
var call = function() {myself.call(me)};
window.setTimeout(call, offset);
return true;
}
if (offset && offset<0) {
var now = new Date();
now.setTime(now.getTime()+offset);
if (now<this.startTime)
return true;
}
var fill = this.fill;
var kept = true;
if (fill=="freeze") {
this.freeze();
} else {
this.stop();
this.step(this.realInitVal);
kept = false;
}
if (this.running) {
for (var i=0; i<this.endListeners.length; ++i)
this.endListeners[i].call();
var onend = this.anim.getAttribute("onend");
if (onend)
eval(onend);
this.running = false;
}
return kept;
},
/**
* Removes this animation from the running array.
*/
stop : function() {
for (var i=0, j=animations.length; i<j; ++i)
if (animations[i]==this) {
animations.splice(i, 1);
// if this is the last running animator, stop the rendering loop
if (!animations.length && animTimer) {
window.clearInterval(animTimer);
animTimer = null;
}
break;
}
},
/**
* Freezes the attribute value to the ending value.
*/
freeze : function() {
this.step(this.freezed);
},
/**
* Adds a listener to this animation beginning or ending.
*/
addEventListener : function(event, func, b) {
if (event=="begin")
this.beginListeners.push(func);
else if (event=="end")
this.endListeners.push(func);
else if (event.length>7 && event.substring(0,6)=="repeat") {
var iteration = event.substring(7,event.length-1);
this.repeatListeners.push(func);
this.repeatIterations.push(iteration);
}
},
/**
* Returns the path linked to this animateMotion.
*/
getPath : function() {
var mpath = this.anim.getElementsByTagNameNS(svgns,"mpath")[0];
if (mpath) {
var pathHref = mpath.getAttributeNS(xlinkns, "href");
return document.getElementById(pathHref.substring(1));
} else {
var d = this.anim.getAttribute("path");
if (d) {
var pathEl = createPath(d);
//pathEl.setAttribute("display", "none");
//this.anim.parentNode.appendChild(pathEl);
return pathEl;
}
}
return null;
},
/**
* Initializes this animator as a translation (x,y):
* <animateTransform type="translate"> or
* <animateMotion> without a path.
*/
translation : function() {
if (this.by && this.by.indexOf(",")==-1)
this.by = this.by+",0";
this.normalize = function(value) {
var coords = value.replace(/,/g," ").replace(/ +/," ").split(/ /);
coords[0] = parseFloat(coords[0]);
if (coords.length==1)
coords[1] = 0;
//coords[1] = this.initVal.split(",")[1];
else
coords[1] = parseFloat(coords[1]);
return coords;
};
this.add = function(a, b) {
var x = a[0]+b[0];
var y = a[1]+b[1];
return x+","+y;
};
this.isInterpolable = function(from, to) { return true; };
this.interpolate = function(from, to, percent) {
var x = from[0]+((to[0]-from[0])*percent);
var y = from[1]+((to[1]-from[1])*percent);
return x+","+y;
};
},
/**
* Initializes this animator as a color animation:
* <animateColor> or
* <animate> on a color attribute.
*/
color : function() {
this.isInterpolable = function(from, to) { return true; };
this.interpolate = function(from, to, percent) {
var r = Math.round(from[0]+((to[0]-from[0])*percent));
var g = Math.round(from[1]+((to[1]-from[1])*percent));
var b = Math.round(from[2]+((to[2]-from[2])*percent));
var val = "rgb("+r+","+g+","+b+")";
return val;
};
this.normalize = function(value) {
var rgb = toRGB(value);
if (rgb==null)
return toRGB(propDefaults[this.attributeName]);
return rgb;
};
this.add = function(a, b) {
var ret = new Array();
for (var i=0; i<a.length; ++i)
ret.push(Math.min(a[i],255)+Math.min(b[i],255));
return ret.join(",");
};
},
d : function() {
this.isInterpolable = function(from, to) { return true; };
this.interpolate = function(from, to, percent) {
var path = "";
var listFrom = from.myNormalizedPathSegList;
var listTo = to.myNormalizedPathSegList;
var segFrom, segTo, typeFrom, typeTo;
for (var i=0, j=Math.min(listFrom.numberOfItems, listTo.numberOfItems); i<j; ++i) {
segFrom = listFrom.getItem(i);
segTo = listTo.getItem(i);
typeFrom = segFrom.pathSegType;
typeTo = segTo.pathSegType;
// NOTE: in 'normalizedPathSegList', only 'M', 'L', 'C' and 'z' path data commands are expected
if (typeFrom==1 || typeTo==1) // PATHSEG_CLOSEPATH
path += " z ";
else {
var x = segFrom.x+((segTo.x-segFrom.x)*percent);
var y = segFrom.y+((segTo.y-segFrom.y)*percent);
if (typeFrom==2 || typeTo==2) // PATHSEG_MOVETO_ABS
path += " M ";
else if (typeFrom==4 || typeTo==4) // PATHSEG_LINETO_ABS
path += " L ";
// NOTE: need to be more strict here, as interpolating a 'C' command with an 'M' or an 'L' isn't yet supported
// (additional trickery is required for dealing with different DOM interfaces and interpolating them)
else if (typeFrom==6 && typeTo==6) { // PATHSEG_CURVETO_CUBIC_ABS
var x1 = segFrom.x1+((segTo.x1-segFrom.x1)*percent);
var y1 = segFrom.y1+((segTo.y1-segFrom.y1)*percent);
var x2 = segFrom.x2+((segTo.x2-segFrom.x2)*percent);
var y2 = segFrom.y2+((segTo.y2-segFrom.y2)*percent);
path += " C "+x1+","+y1+" "+x2+","+y2+" ";
} else
// "unexpected" type found, which means that 'pathSegList' is being used
// (incomplete support for segment interpolation therefore switch to a discrete approach)
return (percent<.5? from: to).getAttribute("d");
path += x+","+y;
}
}
return path;
};
this.normalize = function(value) {
var path = createPath(value);
return path;
};
}
};
/**
* Constructor:
* - initializes
* - gets the attributes
* - corrects and precomputes some values
* - specializes some functions
*/
function Animator(anim, target, index) {
this.anim = anim;
this.target = target;
this.index = index;
anim.targetElement = target;
this.attributeType = anim.getAttribute("attributeType");
this.attributeName = anim.getAttribute("attributeName");
if (this.attributeType!="CSS" && this.attributeType!="XML") {
// attributeType not specified, default stands for "auto"
// "The implementation must first search through the list of CSS properties for a matching property name"
// http://www.w3.org/TR/SVG11/animate.html#AttributeTypeAttribute
if (propDefaults[this.attributeName] && this.target.style.getPropertyValue(this.attributeName))
this.attributeType = "CSS";
else
this.attributeType = "XML";
}
if (this.attributeType=="XML" && this.attributeName) {
this.namespace = null;
var chColon = this.attributeName.indexOf(":");
if (chColon != -1) {
var prefix = this.attributeName.substring(0,chColon);
this.attributeName = this.attributeName.substring(chColon+1);
var node = target;
while(node && node.nodeType==1) {
var ns = node.getAttributeNS("http://www.w3.org/2000/xmlns/", prefix);
if (ns) {
this.namespace = ns;
break;
}
node = node.parentNode;
}
}
}
if (this.attributeName=="d")
this.d();
else if (this.attributeName=="points") {
this.isInterpolable = function(from, to) { return true; };
this.interpolate = function(from, to, percent) {
var ret = new Array();
var xyFrom, xyTo, x, y;
for (var i=0, j=Math.min(from.length, to.length); i<j; ++i) {
xyFrom = from[i].split(",");
xyTo = to[i].split(",");
x = parseFloat(xyFrom[0])+((parseFloat(xyTo[0])-xyFrom[0])*percent);
y = parseFloat(xyFrom[1])+((parseFloat(xyTo[1])-xyFrom[1])*percent);
ret.push(x+","+y);
}
return ret.join(" ");
};
this.normalize = function(value) {
var ar = value.split(" ");
for (var i=ar.length-1; i>=0; --i)
if (ar[i]=="")
ar.splice(i,1);
return ar;
};
}
this.from = anim.getAttribute("from");
this.to = anim.getAttribute("to");
this.by = anim.getAttribute("by");
this.values = anim.getAttribute("values");
if (this.values) {
this.values = this.values.trim();
if (this.values[this.values.length-1]==";")
this.values = this.values.substring(0, this.values.length-1);
}
this.calcMode = anim.getAttribute("calcMode");
this.keyTimes = anim.getAttribute("keyTimes");
if (this.keyTimes) {
this.keyTimes = this.keyTimes.split(";");
for (var i=0; i<this.keyTimes.length; ++i)
this.keyTimes[i] = parseFloat(this.keyTimes[i]);
this.keyPoints = anim.getAttribute("keyPoints");
if (this.keyPoints) {
this.keyPoints = this.keyPoints.split(";");
for (var i=0; i<this.keyPoints.length; ++i)
this.keyPoints[i] = parseFloat(this.keyPoints[i]);
}
}
this.keySplines = anim.getAttribute("keySplines");
if (this.keySplines) {
this.keySplines = this.keySplines.split(";");
for (var i=0; i<this.keySplines.length; ++i)
this.keySplines[i] = createPath("M 0 0 C "+this.keySplines[i]+" 1 1");
}
this.dur = anim.getAttribute("dur");
if (this.dur && this.dur!="indefinite")
this.computedDur = toMillis(this.dur);
this.max = anim.getAttribute("max");
if (this.max && this.max!="indefinite") {
this.computedMax = toMillis(this.max);
if (!isNaN(this.computedMax) && this.computedMax>0 && (!this.computedDur || this.computedDur>this.computedMax))
this.computedDur = this.computedMax;
}
this.min = anim.getAttribute("min");
if (this.min) {
this.computedMin = toMillis(this.min);
if (!this.computedDur || this.computedDur<this.computedMin)
this.computedDur = this.computedMin;
}
this.fill = anim.getAttribute("fill");
this.type = anim.getAttribute("type");
this.repeatCount = anim.getAttribute("repeatCount");
this.repeatDur = anim.getAttribute("repeatDur");
this.accumulate = anim.getAttribute("accumulate");
this.additive = anim.getAttribute("additive");
this.restart = anim.getAttribute("restart");
if (!this.restart)
this.restart = "always";
this.beginListeners = new Array();
this.endListeners = new Array();
this.repeatListeners = new Array();
this.repeatIterations = new Array();
var nodeName = anim.localName;
if (nodeName=="animateColor") {
this.color();
} else if (nodeName=="animateMotion") {
this.isInterpolable = function(from, to) { return true; };
this.getCurVal = function() {
var curTrans = this.target.transform;
if (curTrans && curTrans.animVal.numberOfItems>0) {
var transList = curTrans.animVal;
return decompose(transList.getItem(0).matrix, "translate");
} else
return "0,0";
};
this.path = this.getPath();
if (this.path) {
this.valueAt = function(percent) {
var length = this.path.getTotalLength();
var point = this.path.getPointAtLength(percent*length);
return point.x+","+point.y;
};
} else {
this.translation();
}
this.freeze = function() {
var val = this.valueAt(1);
this.step(val);
};
if (this.keyPoints && this.keyTimes) {
this.pathKeyTimes = this.keyTimes;
this.keyTimes = null;
this.superValueAt = this.valueAt;
this.valueAt = function(percent) {
for (var i=1; i<this.keyPoints.length; ++i) {
var fakePC = this.keyPoints[this.keyPoints.length-1]
if (this.pathKeyTimes[i]>percent) {
var pt = this.keyPoints[i-1];
if (this.calcMode=="discrete")
fakePC = pt;
else {
var t1 = this.pathKeyTimes[i-1];
percent = (percent-t1)/(this.pathKeyTimes[i]-t1);
fakePC = pt+((this.keyPoints[i]-pt)*percent)
}
break;
}
}
return this.superValueAt(fakePC);
};
}
this.step = function(value) {
value = "translate("+value+")";
this.target.setAttribute("transform", value);
};
} else if (nodeName=="animateTransform") {
this.isInterpolable = function(from, to) { return true; };
this.getCurVal = function() {
var type = this.type;
var curTrans = this.target.transform;
if (curTrans && curTrans.animVal.numberOfItems>0) {
var transList = curTrans.animVal;
return decompose(transList.getItem(0).matrix, type);
} else {
if (type=="scale")
return "1,1";
else if (type=="translate")
return "0,0";
else if (type=="rotate")
return "0,0,0";
else
return 0;
}
};
if (this.type=="scale") {
this.normalize = function(value) {
value = value.replace(/,/g," ");
var coords = value.split(" ");
coords[0] = parseFloat(coords[0]);
if (coords.length==1)
coords[1] = coords[0];
else
coords[1] = parseFloat(coords[1]);
return coords;
};
this.add = function(a, b) {
var ret = new Array();
for (var i=0; i<a.length; ++i)
ret.push(a[i]*b[i]);
return ret.join(",");
};
} else if (this.type=="translate") {
this.translation();
} else if (this.type=="rotate") {
this.normalize = function(value) {
value = value.replace(/,/g," ");
var coords = value.split(" ");
coords[0] = parseFloat(coords[0]);
if (coords.length<3) {
coords[1] = 0;
coords[2] = 0;
} else {
coords[1] = parseFloat(coords[1]);
coords[2] = parseFloat(coords[2]);
}
return coords;
};
this.add = function(a, b) {
var ret = new Array();
for (var i=0; i<a.length; ++i)
ret.push(a[i]+b[i]);
return ret.join(",");
};
}
if (this.type=="scale" || this.type=="rotate") {
if (this.from)
this.from = this.normalize(this.from).join(",");
if (this.to)
this.to = this.normalize(this.to).join(",");
if (this.by)
this.by = this.normalize(this.by).join(",");
if (this.values) {
var tvals = this.values.split(";");
for (var i=0; i<tvals.length; ++i)
tvals[i] = this.normalize(tvals[i]).join(",");
this.values = tvals.join(";");
}
this.interpolate = function(from, to, percent) {
var ret = new Array();
for (var i=0; i<from.length; ++i)
ret.push(from[i]+((to[i]-from[i])*percent));
return ret.join(",");
};
}
this.step = function(value) {
var attributeName = this.attributeName;
value = this.type+"("+value+")";
this.target.setAttribute(attributeName, value);
};
}
var me = this;
this.anim.beginElement = function() { me.begin(); return true; };
this.anim.beginElementAt = function(offset) { me.begin(offset*1000); return true; };
this.anim.endElement = function() { me.finish(); return true; };
this.anim.endElementAt = function(offset) { me.finish(offset*1000); return true; };
this.anim.getStartTime = function() { return (me.iterBegin-timeZero)/1000; };
this.anim.getCurrentTime = function() {
var now = new Date();
return (now-me.iterBegin)/1000;
};
}
/**
* Can be called at any time.
* It's the main loop.
*/
function animate() {
var curTime = new Date();
if (curTime<=prevTime)
return;
for (var i=0, j=animations.length; i<j; ++i) {
try {
if (!animations[i].f(curTime)) {
// animation was removed therefore we need to adjust both the iterator and the auxiliary variable
--i; --j;
}
} catch(exc) {
if (exc.message!=="Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsIDOMSVGPathElement.getTotalLength]") {
// NOTE: in IE, console object is only available when Developer tools are open
if (window.console && console.log) {
console.log(exc);
// uncomment to force error display
//} else {
// alert(exc);
}
}
}
}
prevTime = curTime;
// it would be cool if the attributes would be computed only, in the previous loop
// and then the last values applied after the loop
// for that, f(t) must return the value, and we must have a map for object(?).attributeType.attributeName -> value
// then f(t) cannot return false when autostopping -> we must find another mechanism
}
/**
* Converts a clock-value to milliseconds.
* Supported: "s" | "ms" | "min" | "h" | no-units
*/
function toMillis(time) {
time = time.trim();
var len = time.length;
var io = time.indexOf(":");
if (io!=-1) {
var clockVal = time.split(":");
len = clockVal.length;
time = 0;
if (len==3)
time += parseInt(clockVal[0])*3600000;
time += parseInt(clockVal[len-2])*60000;
time += parseFloat(clockVal[len-1])*1000;
} else if (len>2 && time.substring(len-2)=="ms") {
time = parseInt(time.substring(0, time.length-2));
} else if (len>1 && time[len-1]=="s") {
time = time.substring(0, time.length-1);
time *= 1000;
} else if (len>3 && time.substring(len-3)=="min") {
time = time.substring(0, time.length-3);
time *= 60000;
} else if (len>1 && time[len-1]=="h") {
time = time.substring(0, time.length-1);
time *= 3600000;
} else {
time *= 1000;
}
return time;
}
/**
* Decompose a matrix into its scale, translate, rotate or skew.
*/
function decompose(matrix, type) {
if (type=="translate")
return matrix.e+","+matrix.f;
var a = matrix.a;
var b = matrix.b;
var c = matrix.c;
var d = matrix.d;
if (type=="rotate")
return Math.atan2(c,a)+",0,0";
var ModA = Math.sqrt(a*a+c*c);
var ModB = Math.sqrt(b*b+d*d);
if (type=="scale") {
var AxB = a*d-b*c;
var scaleX = AxB==0?0:(AxB/ModA);
var scaleY = ModB;
return scaleX+","+scaleY;
}
var AdotB = a*b+c*d;
if (AdotB==0)
return 0;
var shear = Math.PI/2-Math.acos(AdotB/(ModB*ModA));
return (shear*180)/Math.PI;
}
/**
* Convert an rgb(), #XXX, #XXXXXX or named color
* into an [r,g,b] array.
*/
function toRGB(color) {
if (color.substring(0, 3)=="rgb") {
color = color.replace(/ /g, "");
color = color.replace("rgb(", "");
color = color.replace(")", "");
var rgb = color.split(",");
for (var i=0; i<rgb.length; ++i) {
var len = rgb[i].length-1;
if (rgb[i][len]=="%")
rgb[i] = Math.round((rgb[i].substring(0,len))*2.55);
else
rgb[i] = parseInt(rgb[i]);
}
return rgb;
} else if (color.charAt(0)=="#") {
color = color.trim();
var rgb = new Array();
if (color.length==7) {
rgb[0] = parseInt(color.substring(1,3),16);
rgb[1] = parseInt(color.substring(3,5),16);
rgb[2] = parseInt(color.substring(5,7),16);
} else {
rgb[0] = color.substring(1,2);
rgb[1] = color.substring(2,3);
rgb[2] = color.substring(3,4);
rgb[0] = parseInt(rgb[0]+rgb[0],16);
rgb[1] = parseInt(rgb[1]+rgb[1],16);
rgb[2] = parseInt(rgb[2]+rgb[2],16);
}
return rgb;
} else {
return colors[color];
}
}
function createPath(d) {
var path = document.createElementNS(svgns, "path");
path.setAttribute("d", d);
try {
if (path.normalizedPathSegList)
path.myNormalizedPathSegList = path.normalizedPathSegList;
} catch(exc) {}
if (!path.myNormalizedPathSegList) {
// TODO : normalize the path
path.myNormalizedPathSegList = path.pathSegList;
}
return path;
}
// NOTE: units which aren't valid variable names are enclosed in quotes
var units = {grad: 1, deg: 1, rad: 1, kHz: 1, Hz: 1, em: 1, ex: 1, px: 1, pt: 1, pc: 1, mm: 1, cm: 1, in: 1, ms: 1, s: 1, "%": 1};
function getUnit(str) {
if (str && str.substring && str.length > 1) {
for (var i=1; i<4; ++i) { // loop through units string length
var vlen = str.length-i;
if (vlen>0) {
var unit = str.substring(vlen);
if (units[unit]) {
var val = str.substring(0, vlen);
if (!isNaN(val))
return [val,unit];
}
}
}
}
return [str,null];
}
var colors = {
aliceblue : [240, 248, 255],
antiquewhite : [250, 235, 215],
aqua : [0, 255, 255],
aquamarine : [127, 255, 212],
azure : [240, 255, 255],
beige : [245, 245, 220],
bisque : [255, 228, 196],
black : [0, 0, 0],
blanchedalmond : [255, 235, 205],
blue : [0, 0, 255],
blueviolet : [138, 43, 226],
brown : [165, 42, 42],
burlywood : [222, 184, 135],
cadetblue : [95, 158, 160],
chartreuse : [127, 255, 0],
chocolate : [210, 105, 30],
coral : [255, 127, 80],
cornflowerblue : [100, 149, 237],
cornsilk : [255, 248, 220],
crimson : [220, 20, 60],
cyan : [0, 255, 255],
darkblue : [0, 0, 139],
darkcyan : [0, 139, 139],
darkgoldenrod : [184, 134, 11],
darkgray : [169, 169, 169],
darkgreen : [0, 100, 0],
darkgrey : [169, 169, 169],
darkkhaki : [189, 183, 107],
darkmagenta : [139, 0, 139],
darkolivegreen : [85, 107, 47],
darkorange : [255, 140, 0],
darkorchid : [153, 50, 204],
darkred : [139, 0, 0],
darksalmon : [233, 150, 122],
darkseagreen : [143, 188, 143],
darkslateblue : [72, 61, 139],
darkslategray : [47, 79, 79],
darkslategrey : [47, 79, 79],
darkturquoise : [0, 206, 209],
darkviolet : [148, 0, 211],
deeppink : [255, 20, 147],
deepskyblue : [0, 191, 255],
dimgray : [105, 105, 105],
dimgrey : [105, 105, 105],
dodgerblue : [30, 144, 255],
firebrick : [178, 34, 34],
floralwhite : [255, 250, 240],
forestgreen : [34, 139, 34],
fuchsia : [255, 0, 255],
gainsboro : [220, 220, 220],
ghostwhite : [248, 248, 255],
gold : [255, 215, 0],
goldenrod : [218, 165, 32],
gray : [128, 128, 128],
grey : [128, 128, 128],
green : [0, 128, 0],
greenyellow : [173, 255, 47],
honeydew : [240, 255, 240],
hotpink : [255, 105, 180],
indianred : [205, 92, 92],
indigo : [75, 0, 130],
ivory : [255, 255, 240],
khaki : [240, 230, 140],
lavender : [230, 230, 250],
lavenderblush : [255, 240, 245],
lawngreen : [124, 252, 0],
lemonchiffon : [255, 250, 205],
lightblue : [173, 216, 230],
lightcoral : [240, 128, 128],
lightcyan : [224, 255, 255],
lightgoldenrodyellow : [250, 250, 210],
lightgray : [211, 211, 211],
lightgreen : [144, 238, 144],
lightgrey : [211, 211, 211],
lightpink : [255, 182, 193],
lightsalmon : [255, 160, 122],
lightseagreen : [32, 178, 170],
lightskyblue : [135, 206, 250],
lightslategray : [119, 136, 153],
lightslategrey : [119, 136, 153],
lightsteelblue : [176, 196, 222],
lightyellow : [255, 255, 224],
lime : [0, 255, 0],
limegreen : [50, 205, 50],
linen : [250, 240, 230],
magenta : [255, 0, 255],
maroon : [128, 0, 0],
mediumaquamarine : [102, 205, 170],
mediumblue : [0, 0, 205],
mediumorchid : [186, 85, 211],
mediumpurple : [147, 112, 219],
mediumseagreen : [60, 179, 113],
mediumslateblue : [123, 104, 238],
mediumspringgreen : [0, 250, 154],
mediumturquoise : [72, 209, 204],
mediumvioletred : [199, 21, 133],
midnightblue : [25, 25, 112],
mintcream : [245, 255, 250],
mistyrose : [255, 228, 225],
moccasin : [255, 228, 181],
navajowhite : [255, 222, 173],
navy : [0, 0, 128],
oldlace : [253, 245, 230],
olive : [128, 128, 0],
olivedrab : [107, 142, 35],
orange : [255, 165, 0],
orangered : [255, 69, 0],
orchid : [218, 112, 214],
palegoldenrod : [238, 232, 170],
palegreen : [152, 251, 152],
paleturquoise : [175, 238, 238],
palevioletred : [219, 112, 147],
papayawhip : [255, 239, 213],
peachpuff : [255, 218, 185],
peru : [205, 133, 63],
pink : [255, 192, 203],
plum : [221, 160, 221],
powderblue : [176, 224, 230],
purple : [128, 0, 128],
red : [255, 0, 0],
rosybrown : [188, 143, 143],
royalblue : [65, 105, 225],
saddlebrown : [139, 69, 19],
salmon : [250, 128, 114],
sandybrown : [244, 164, 96],
seagreen : [46, 139, 87],
seashell : [255, 245, 238],
sienna : [160, 82, 45],
silver : [192, 192, 192],
skyblue : [135, 206, 235],
slateblue : [106, 90, 205],
slategray : [112, 128, 144],
slategrey : [112, 128, 144],
snow : [255, 250, 250],
springgreen : [0, 255, 127],
steelblue : [70, 130, 180],
tan : [210, 180, 140],
teal : [0, 128, 128],
thistle : [216, 191, 216],
tomato : [255, 99, 71],
turquoise : [64, 224, 208],
violet : [238, 130, 238],
wheat : [245, 222, 179],
white : [255, 255, 255],
whitesmoke : [245, 245, 245],
yellow : [255, 255, 0],
yellowgreen : [154, 205, 50]
};
// NOTE: variables cannot contain dashes, as they are seen as a subtraction expression
// (therefore, in those cases, enclosing in quotes is required)
var propDefaults = {
font : "see individual properties",
"font-family" : "Arial",
"font-size" : "medium",
"font-size-adjust" : "none",
"font-stretch" : "normal",
"font-style" : "normal",
"font-variant" : "normal",
"font-weight" : "normal",
direction : "ltr",
"letter-spacing" : "normal",
"text-decoration" : "none",
"unicode-bidi" : "normal",
"word-spacing" : "normal",
clip : "auto",
color : "depends on user agent",
cursor : "auto",
display : "inline",
overflow : "hidden",
visibility : "visible",
"clip-path" : "none",
"clip-rule" : "nonzero",
mask : "none",
opacity: 1,
"enable-background" : "accumulate",
filter : "none",
"flood-color" : "black",
"flood-opacity" : 1,
"lighting-color" : "white",
"stop-color" : "black",
"stop-opacity" : 1,
"pointer-events" : "visiblePainted",
"color-interpolation" : "sRGB",
"color-interpolation-filters" : "linearRGB",
"color-profile" : "auto",
"color-rendering" : "auto",
fill : "black",
"fill-opacity" : 1,
"fill-rule" : "nonzero",
"image-rendering" : "auto",
"marker-end" : "none",
"marker-mid" : "none",
"marker-start" : "none",
"shape-rendering" : "auto",
stroke : "none",
"stroke-dasharray" : "none",
"stroke-dashoffset" : 0,
"stroke-linecap" : "butt",
"stroke-linejoin" : "miter",
"stroke-miterlimit" : 4,
"stroke-opacity" : 1,
"stroke-width" : 1,
"text-rendering" : "auto",
"alignment-baseline" : 0,
"baseline-shift" : "baseline",
"dominant-baseline" : "auto",
"glyph-orientation-horizontal" : 0,
"glyph-orientation-vertical" : "auto",
kerning : "auto",
"text-anchor" : "start",
"writing-mode" : "lr-tb"
};
function funk(func, obj, arg) {
return function() {func.call(obj, arg);};
}
/**
* Removes the leading and trailing spaces chars from the string.
* NOTE: part of ES5, so use feature detection
* http://stackoverflow.com/questions/2308134/trim-in-javascript-not-working-in-ie/#2308157
* NOTE: the regular expression used in fallback is placed in global namespace for performance
* (as it's far better having a "singleton" than bloating every string instance)
*/
if (typeof String.prototype.trim !== "function") {
window._trimRegExp = new RegExp("^\\s+|\\s+$", "g");
String.prototype.trim = function() {
return this.replace(window._trimRegExp, "");
};
}
/**
* Set an ISO 8601 timestamp to a Date object.
* NOTE: as ES5 doesn't define precisely what "parse" should do, we run a sample to test for feasibility
* http://stackoverflow.com/questions/2479714/does-javascript-ecmascript3-support-iso8601-date-parsing/#2481375
* NOTE: the regular expression used in fallback is placed in global namespace for performance
* (as it's far better having a "singleton" than bloating every date instance)
*/
if (!isNaN(Date.parse("2012-04-22T19:53:32Z"))){
// parse did well, use the native implementation
Date.prototype.setISO8601 = function (string) {
this.setTime(Date.parse(string));
};
}else{
window._setISO8601RegExp = new RegExp(
"([0-9]{4})(?:-([0-9]{2})(?:-([0-9]{2})" +
"(?:T([0-9]{2}):([0-9]{2})(?::([0-9]{2})(?:\.([0-9]+))?)?" +
"(?:Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?"
);
Date.prototype.setISO8601 = function (string) {
var d = window._setISO8601RegExp.exec(string);
// check that RegExp was applied successfully and that at least year is present
if (d && d.length>1) {
var date = new Date(d[1], 0, 1);
if (d[2]) { date.setMonth(d[2] - 1); }
if (d[3]) { date.setDate(d[3]); }
if (d[4]) { date.setHours(d[4]); }
if (d[5]) { date.setMinutes(d[5]); }
if (d[6]) { date.setSeconds(d[6]); }
// NOTE: ISO 8601 "decimal fraction of a second" needs to be converted to milliseconds
if (d[7]) { date.setMilliseconds(parseFloat("0." + d[7]) * 1000); }
if (d[8]) {
var offset = (parseInt(d[10]) * 60) + parseInt(d[11]);
if (d[9]!='-') { offset = -offset; }
} else
var offset = 0;
offset -= date.getTimezoneOffset();
this.setTime(date.getTime() + (offset * 60 * 1000));
} else
this.setTime(NaN);
};
}
try {
// NOTE: ASV skips triggering the library here, as 'addEventListener' is not supported
// (but that's not an issue as most popular versions, ASV3 and ASV6 beta, both support SMIL)
window.addEventListener("load", initSMIL, false);
} catch(exc) {}