讓IE瀏覽器支持SVG動畫

訪問http://caniuse.com/#search=svg
IE對SVG動畫支持很差。
那咱們就說服老闆不支持IE。
老闆任性,硬着頭皮上。javascript

訪問 https://github.com/FakeSmile/FakeSmile 這個項目。
它用模擬的方式實現SVG動畫。
使用demo以下:html

<!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.jsjava

/*
@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) {}
相關文章
相關標籤/搜索