Snap静态方法 - bobby169/Snap.svgDoc GitHub Wiki

[TOC]

全局重要函数

Snap

  • width (number|string) width of surface
  • height (number|string) height of surface

或:

  • DOM (SVGElement) element to be wrapped into Snap structure

或:

  • array (array) array of elements (will return set of elements)

或:

  • query (string) CSS query selector

Creates a drawing surface or wraps existing SVG element.

function Snap(w, h) {
    if (w) {
        // 是一个dom元素
        if (w.nodeType) {
            return wrap(w);
        }
        if (is(w, "array") && Snap.set) {
            return Snap.set.apply(Snap, w);
        }
        if (w instanceof Element) {
            return w;
        }
        if (h == null) {
            try {
                // w为id或class
                w = glob.doc.querySelector(String(w));
                return wrap(w);
            } catch (e) {
                return null;
            }
        }
    }
    w = w == null ? "100%" : w;
    h = h == null ? "100%" : h;
    return new Paper(w, h);
}

Element(el)

var idgen = 0,
    idprefix = "S" + (+new Date).toString(36);
    
var ID = function (el) {
    return (el && el.type ? el.type : E) + idprefix + (idgen++).toString(36);
}

function Element(el) {
    if (el.snap in hub) {
        return hub[el.snap];
    }
    var svg;
    try {
        svg = el.ownerSVGElement;
    } catch(e) {}
    /*\
     * Element.node
     [ property (object) ]
     **
     * Gives you a reference to the DOM object, so you can assign event handlers or just mess around.
     > Usage
     | // draw a circle at coordinate 10,10 with radius of 10
     | var c = paper.circle(10, 10, 10);
     | c.node.onclick = function () {
     |     c.attr("fill", "red");
     | };
    \*/
    
    this.node = el;
    
    if (svg) {
        this.paper = new Paper(svg);
    }
    /*\
     * Element.type
     [ property (string) ]
     **
     * SVG tag name of the given element.
    \*/
    this.type = el.tagName || el.nodeName;
    
    // 如果id不存在,自动生成
    var id = this.id = ID(this);
    
    this.anims = {};
    this._ = {
        transform: []
    };
    el.snap = id;
    hub[id] = this;
    if (this.type == "g") {
        this.add = add2group;
    }
    if (this.type in {g: 1, mask: 1, pattern: 1, symbol: 1}) {
        for (var method in Paper.prototype) if (Paper.prototype[has](method)) {
            this[method] = Paper.prototype[method];
        }
    }
}

Paper(w, h)

function Paper(w, h) {
    var res,
        desc,
        defs,
        proto = Paper.prototype;
    if (w && w.tagName && w.tagName.toLowerCase() == "svg") {
        if (w.snap in hub) {
            return hub[w.snap];
        }
        var doc = w.ownerDocument;
        
        res = new Element(w); // Paper返回的是一个element
        
        desc = w.getElementsByTagName("desc")[0];
        defs = w.getElementsByTagName("defs")[0];
        
        // 如何desc标签不存在会自动创建
        if (!desc) {
            desc = $("desc");
            desc.appendChild(doc.createTextNode("Created with Snap"));
            res.node.appendChild(desc);
        }
        if (!defs) {
            defs = $("defs");
            res.node.appendChild(defs);
        }
        res.defs = defs;
        for (var key in proto) if (proto[has](key)) {
            res[key] = proto[key];
        }
        res.paper = res.root = res;
    } else {
        // svg不存在,则在document.body上创建一个宽为w,高为h的svg元素
        res = make("svg", glob.doc.body);
        $(res.node, {
            height: h,
            version: 1.1,
            width: w,
            xmlns: xmlns
        });
    }
    return res;
}

Fragment(flag)

function Fragment(frag) {
    this.node = frag;
}

Snap静态方法

Snap.plugin (callback)

吏上最简plugin写法

Snap.plugin = function (f) {
    f(Snap, Element, Paper, glob, Fragment);
};

示例:

Snap.plugin(function (Snap, Element, Paper, global, Fragment) {
  Snap.newmethod = function () {};
  Element.prototype.newmethod = function () {};
  Paper.prototype.newmethod = function () {};
});

Snap.fragment

Creates a DOM fragment from a given list of elements or strings

Snap.fragment = function () {
    var args = Array.prototype.slice.call(arguments, 0),
        f = glob.doc.createDocumentFragment();
    for (var i = 0, ii = args.length; i < ii; i++) {
        var item = args[i];
        if (item.node && item.node.nodeType) {
            f.appendChild(item.node);
        }
        if (item.nodeType) {
            f.appendChild(item);
        }
        if (typeof item == "string") {
            f.appendChild(Snap.parse(item).node);
        }
    }
    return new Fragment(f);
};

Document.createDocumentFragment() 创建一个新的空白的文档片段( DocumentFragment)

DocumentFragments 是DOM节点。它们不是主DOM树的一部分。通常的用例是创建文档片段,将元素附加到文档片段,然后将文档片段附加到DOM树。在DOM树中,文档片段被其所有的子元素所代替。

因为文档片段存在于内存中,并不在DOM树中,所以将子元素插入到文档片段时不会引起页面回流(对元素位置和几何上的计算)。因此,使用文档片段通常会带来更好的性能。

Snap.ajax(url, [postData], callback, scope)

  • url 字符串。URL
  • postData 对象或字符串。要post的数据。
  • callback 函数。回调函数。
  • scope 对象。回调函数的作用域。
Snap.ajax = function (url, postData, callback, scope){
    var req = new XMLHttpRequest,
        id = ID();
    if (req) {
        if (is(postData, "function")) {
            scope = callback;
            callback = postData;
            postData = null;
        } else if (is(postData, "object")) {
            var pd = [];
            for (var key in postData) if (postData.hasOwnProperty(key)) {
                pd.push(encodeURIComponent(key) + "=" + encodeURIComponent(postData[key]));
            }
            postData = pd.join("&");
        }
        req.open(postData ? "POST" : "GET", url, true);
        if (postData) {
            req.setRequestHeader("X-Requested-With", "XMLHttpRequest");
            req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        }
        if (callback) {
            eve.once("snap.ajax." + id + ".0", callback);
            eve.once("snap.ajax." + id + ".200", callback);
            eve.once("snap.ajax." + id + ".304", callback);
        }
        req.onreadystatechange = function() {
            if (req.readyState != 4) return;
            eve("snap.ajax." + id + "." + req.status, scope, req);
        };
        if (req.readyState == 4) {
            return req;
        }
        req.send(postData);
        return req;
    }
};

示例

let svg = Snap("#svg");
let c = svg.paper.circle(50,50,40);

document.getElementById("button").onclick = function() {
    Snap.ajax("../_ajax/ajax.php", function(response) {
        c.attr({
            fill: response.responseText   
        });										
    });
};

Snap.load(url, callback, [scope])

  • url 字符串。URL
  • callback 函数。回调
  • scope 对象。回调函数的作用域。

加载外部的SVG文件作为文档片段 @Fragment

Snap.load = function (url, callback, scope) {
    Snap.ajax(url, function (req) {
        var f = Snap.parse(req.responseText);
        scope ? callback.call(scope, f) : callback(f);
    });
};

示例:

Snap.load("../_ajax/ajax.svg", function(svg) {
    this.appendChild(svg.node);
}, document.getElementById("append"));

Snap.fragment(varargs)

  • varargs Svg字符串或svg片段可html元素

根据给定的元素列表或字符串创建DOM片段。

因为文档片段存在于内存中,并不在DOM树中,所以将子元素插入到文档片段时不会引起页面回流(对元素位置和几何上的计算)。因此,使用文档片段通常会带来更好的性能

let glob = {
    win: root.window,
    doc: root.window.document
};

function Fragment(frag) {
    this.node = frag;
}

Snap.fragment = function () {
    // Document.createDocumentFragment()
    // https://developer.mozilla.org/zh-CN/docs/Web/API/Document/createDocumentFragment
    var args = Array.prototype.slice.call(arguments, 0),
        f = glob.doc.createDocumentFragment();
    for (var i = 0, ii = args.length; i < ii; i++) {
        var item = args[i];
        if (item.node && item.node.nodeType) {
            f.appendChild(item.node);
        }
        if (item.nodeType) {
            f.appendChild(item);
        }
        if (typeof item == "string") {
            f.appendChild(Snap.parse(item).node);
        }
    }
    return new Fragment(f);
};

示例:

let svg = new Snap('svg')
let c = Snap.fragment('<circle cx="50" cy="50" r="40"></circle><circle cx="150" cy="50" r="40"></circle>');
svg.append(c)

let b = Snap.fragment(c)
svg.append(b)

let span = document.getElementById('span')
let s = Snap.fragment(span)
svg.append(s)

Snap.animate(from, to, setter, duration, [easing], [callback])

  • from (number|array)。动画起始数值或数值数组。
  • to (number|array)。动画结束数值或数值数组。
  • setter (function)。接受一个数值参数的贴心函数。
  • duration (number)。动画持续时间,单位是毫秒。
  • easing (function)。来自mina或自定义的缓动函数。
  • callback (function)。动画结束时候执行的回调函数。

返回值

  • 对象。mina格式的动画对象。
{
    id (string) 。动画的id, 你可以认为其只读。
    duration (function)。获得或设置动画的持续时间。
    easing (function)。缓动函数。
    speed (function)。获得或设置动画的速度。
    status (function)。获得或设置动画的状态。
    stop (function)。停止动画。
}
// naimation.js
Snap.animate = function (from, to, setter, ms, easing, callback) {
    if (typeof easing == "function" && !easing.length) {
        callback = easing;
        easing = mina.linear;
    }
    var now = mina.time(),
        anim = mina(from, to, now, now + ms, mina.time, setter, easing);
    callback && eve.once("mina.finish." + anim.id, callback);
    return anim;
};

// mina.js
mina = function (a, A, b, B, get, set, easing) {
    var anim = {
        id: ID(),
        start: a,
        end: A,
        b: b,
        s: 0,
        dur: B - b,
        spd: 1,
        get: get,
        set: set,
        easing: easing || mina.linear,
        status: sta,
        speed: speed,
        duration: duration,
        stop: stopit,
        pause: pause,
        resume: resume,
        update: update
    };
    animations[anim.id] = anim;
    var len = 0, i;
    for (i in animations) if (animations.hasOwnProperty(i)) {
        len++;
        if (len == 2) {
            break;
        }
    }
    len == 1 && frame();
    return anim;
};

frame = function (timeStamp) {
    // Manual invokation?
    if (!timeStamp) {
        // Frame loop stopped?
        if (!requestID) {
            // Start frame loop...
            requestID = requestAnimFrame(frame);
        }
        return;
    }
    var len = 0;
    for (var i in animations) if (animations.hasOwnProperty(i)) {
        var a = animations[i],
            b = a.get(),
            res;
        len++;
        a.s = (b - a.b) / (a.dur / a.spd);
        if (a.s >= 1) {
            delete animations[i];
            a.s = 1;
            len--;
            (function (a) {
                setTimeout(function () {
                    eve("mina.finish." + a.id, a);
                });
            }(a));
        }
        a.update();
    }
    requestID = len ? requestAnimFrame(frame) : false;
},

示例:

let rect = Snap("#svg").paper.rect(0, 20, 60, 40);

document.getElementById("button").onclick = function() {        
    Snap.animate(0, 20, function (val) {
        rect.attr({
            x: val
        });
    }, 1000);
    // 以上代码,类似于下面的效果
    // rect.animate({x: 20}, 1000);
    // 显然,上面的更强大,更灵活;下面的则是简单易懂
};

Snap.animation(attr, duration, [easing], [callback])

  • attr (object)。最终目标的属性。
  • duration (number) 。动画持续的时间,单位为毫秒。
  • easing (function)。mina或自定义的缓动函数。
  • callback (function)。动画结束的时候执行的回调函数。

返回:

  • 对象(object)。动画对象。

创建一个animation对象

let Animation = function (attr, ms, easing, callback) {
    if (typeof easing == "function" && !easing.length) {
        callback = easing;
        easing = mina.linear;
    }
    this.attr = attr;
    this.dur = ms;
    easing && (this.easing = easing);
    callback && (this.callback = callback);
};

Snap.animation = function (attr, ms, easing, callback) {
    return new Animation(attr, ms, easing, callback);
};

示例:

var anim = Snap.animation({ foo: "bar" }, 100, mina.easein);
console.info(anim)
it("Snap.animation - no easing or callback", function() {
    var anim = Snap.animation({ foo: "bar" }, 100);
    expect(anim).to.be.an("object");
    expect(anim.dur).to.be(100);
    expect(anim.attr.foo).to.be("bar");
});
it("Snap.animation - with easing", function() {
    var anim = Snap.animation({ foo: "bar" }, 100, mina.easein);
    expect(anim).to.be.an("object");
    expect(anim.dur).to.be(100);
    expect(anim.attr.foo).to.be("bar");
    expect(anim.easing).to.be.a("function");
});
it("Snap.animation - with callback", function() {
    var cb = function(){};
    var anim = Snap.animation({ foo: "bar" }, 100, cb);
    expect(anim).to.be.an("object");
    expect(anim.dur).to.be(100);
    expect(anim.attr.foo).to.be("bar");
    expect(anim.callback).to.be.a("function");
});
it("Snap.animation - with easing & callback", function() {
    var cb = function(){};
    var anim = Snap.animation({ foo: "bar" }, 100, mina.linear, cb);
    expect(anim).to.be.an("object");
    expect(anim.dur).to.be(100);
    expect(anim.attr.foo).to.be("bar");
    expect(anim.easing).to.be.a("function");
    expect(anim.callback).to.be.a("function");
    expect(anim.easing).to.not.be(anim.callback);
});

Snap.getElementByPoint(x, y)

  • x (number) x coordinate from the top left corner of the window
  • y (number) y coordinate from the top left corner of the window

返回

  • (object) Snap element object

Returns you topmost element under given point. 返回当前文档上处于指定坐标位置最顶层的元素, 坐标是相对于包含该文档的浏览器窗口的左上角为原点来计算的, 通常 x 和 y 坐标都应为正数.

Snap.getElementByPoint = function (x, y) {
    var paper = this,
        svg = paper.canvas,
        target = glob.doc.elementFromPoint(x, y);
        // https://developer.mozilla.org/zh-CN/docs/Web/API/Document/elementFromPoint
    if (glob.win.opera && target.tagName == "svg") {
        var so = getOffset(target),
            sr = target.createSVGRect();
        sr.x = x - so.x;
        sr.y = y - so.y;
        sr.width = sr.height = 1;
        var hits = target.getIntersectionList(sr, null);
        if (hits.length) {
            target = hits[hits.length - 1];
        }
    }
    if (!target) {
        return null;
    }
    return wrap(target);
};

示例:

let svg = Snap("#svg");
let c1 = svg.paper.circle(50,50,40).attr({
  fill: "red"
}), c2 = svg.paper.circle(100,50,40).attr({
  fill: "green"
});

document.body.addEventListener('click',(e) => {
  let target = Snap.getElementByPoint(e.pageX,e.pageY)
  target.attr({
      stroke: "blue",
      strokeWidth: 5
  });
})

Snap.select(query)

  • query (string) CSS selector of the element

返回

  • (Element) the current element

Wraps a DOM element specified by CSS selector as @Element

Snap.select = function (query) {
    query = Str(query).replace(/([^\\]):/g, "$1\\:");
    return wrap(glob.doc.querySelector(query));
};

示例:

let s = Snap(10, 10);
let group1 = s.group();
let group2 = s.group();
let group3 = s.group();
let circle1 = s.circle(10, 20, 30).attr({
  'class': 'circle-one'
});
let circle2 = s.circle(5, 10, 25).attr({
  'class': 'circle-two'
});
group1.add(group2);
group2.add(group3);
group2.add(circle1);
group3.add(circle2);

let c1 = Snap.select('.circle-one');
let c2 = Snap.select('.circle-two');

console.info(c1 === circle1)
console.info(c2 === circle2)

Snap.selectAll(query)

  • query (string) CSS selector of the element
Snap.selectAll = function (query) {
    var nodelist = glob.doc.querySelectorAll(query),
        set = (Snap.set || Array)();
    for (var i = 0; i < nodelist.length; i++) {
        set.push(wrap(nodelist[i]));
    }
    return set;
};

注意:selectAll返回的是一个Set对象,Set就是一个数组

示例

let s = Snap(10, 10);
let group1 = s.group();
let group2 = s.group();
let group3 = s.group();
let circle1 = s.circle(10, 20, 30).attr({
  'class': 'circle-one'
});
let circle2 = s.circle(5, 10, 25).attr({
  'class': 'circle-two'
});
group1.add(group2);
group2.add(group3);
group2.add(circle1);
group3.add(circle2);

let circles = Snap.selectAll('circle');

console.info(circles.length)

Snap.parse(svg)

  • svg (string) SVG string

Parses SVG fragment and converts it into a @Fragment

返回

  • (Fragment) the @Fragment
Snap.parse = function (svg) {
    var f = glob.doc.createDocumentFragment(),
        full = true,
        div = glob.doc.createElement("div");
    svg = Str(svg);
    if (!svg.match(/^\s*<\s*svg(?:\s|>)/)) {
        svg = "<svg>" + svg + "</svg>";
        full = false;
    }
    div.innerHTML = svg;
    svg = div.getElementsByTagName("svg")[0];
    if (svg) {
        if (full) {
            f = svg;
        } else {
            while (svg.firstChild) {
                f.appendChild(svg.firstChild);
            }
        }
    }
    return new Fragment(f);
};

示例:

var fragment = Snap.parse('<circle cx="50" cy="50" r="40"></circle>');
Snap("#svg").add(fragment);

Snap.format(token, json)

  • token (string) string to format
  • json (object) object which properties are used as a replacement

返回

  • (string) formatted string
Snap.format = (function () {
    var tokenRegex = /\{([^\}]+)\}/g,
        objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g, // matches .xxxxx or ["xxxxx"] to run over object properties
        replacer = function (all, key, obj) {
            var res = obj;
            key.replace(objNotationRegex, function (all, name, quote, quotedName, isFunc) {
                name = name || quotedName;
                if (res) {
                    if (name in res) {
                        res = res[name];
                    }
                    typeof res == "function" && isFunc && (res = res());
                }
            });
            res = (res == null || res == obj ? all : res) + "";
            return res;
        };
    return function (str, obj) {
        return Str(str).replace(tokenRegex, function (all, key) {
            return replacer(all, key, obj);
        });
    };
})();

示例:

let str = Snap.format("M{x},{y}h{dim.width}v{dim.height}h{dim['negative width']}z", {
    x: 10,
    y: 20,
    dim: {
        width: 40,
        height: 50,
        "negative width": -40
    }
})

console.info(str)

Snap.SnapTo(values, value, tolerance)

  • values (array|number) given array of values or step of the grid
  • value (number) 要调整的值
  • tolerance (number) 触发snap的距离目标值的最大值,默认10

贴紧网格

let svg = new Snap('#svg')
let gridSize = 50
let orig = {
    x: 0,
    y: 0
}

let hPath = ''
let vPath = ''
for (let i = 1; i< 100; i++) {
    hPath += `M0 ${i*gridSize} h1510 `
    vPath += `M${i*gridSize} 0 v1510 `
}

svg.paper.path(hPath).attr({
    stroke: "#00f",
    strokeWidth: 1
})

svg.paper.path(vPath).attr({
    stroke: "#0f0",
    strokeWidth: 1
})

let rect = svg.paper.rect(150, 30, 100, 100)
rect.attr({
    fill: "red"
})

rect.drag(
    //---move/drag---
    function (dx, dy, x, y, e) {
        this.attr({
            x: orig.x + dx,
            y: orig.y + dy
        })
    },
    //---mousedown/start
    function (x, y, e) {
        orig.x = e.toElement.x.baseVal.value;
        orig.y = e.toElement.y.baseVal.value;
    },
    //---mouseup/end: snap to grid---
    function (e) {
        orig.x = e.toElement.x.baseVal.value;
        orig.y = e.toElement.y.baseVal.value;

        let xSnap = Snap.snapTo(gridSize, orig.x, 30);
        let ySnap = Snap.snapTo(gridSize, orig.y, 30);
        this.attr({
            x: xSnap,
            y: ySnap
        })
    }
)
⚠️ **GitHub.com Fallback** ⚠️