// Copyright (c) 2013 - 2017 Adobe Systems Incorporated. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import eve from './eve.js' import { doc, win, CSS_ATTR, xmlns, T_COMMAND, PATH_VALUES } from './lib/constants.js' import { $, is, uuid, clone, preload, cacher, jsonFiller, extend } from './utils.js' let has = 'hasOwnProperty', math = Math, abs = math.abs, E = '', S = ' ', objectToString = Object.prototype.toString, hub = {} export function make(name, parent) { let res = $(name) parent.appendChild(res) let el = wrap(res) return el } export function wrap(dom) { if (!dom) { return dom } if (dom instanceof SnapElement || dom instanceof Fragment) { return dom } if (dom.tagName.toLowerCase() === 'svg') { return new Paper(dom) } if ( dom.tagName && dom.tagName.toLowerCase() == 'object' && dom.type == 'image/svg+xml' ) { return new Paper(dom.contentDocument.getElementsByTagName('svg')[0]) } return new SnapElement(dom) } export class Fragment { constructor(frag) { this.node = frag } } /*\ ** * Creates a drawing surface or wraps existing SVG element. ** - width (number|string) width of surface - height (number|string) height of surface * or - DOM (SVGElement) element to be wrapped into Snap structure * or - array (array) array of elements (will return set of elements) * or - query (string) CSS query selector = (object) @SnapElement \*/ export class Snap { static _ = { $ } /*\ ** * Parses SVG fragment and converts it into a @Fragment ** \*/ static parse(svg) { let f = doc.createDocumentFragment(), full = true, div = doc.createElement('div') svg = String(svg) if (!svg.match(/^\s*<\s*svg(?:\s|>)/)) { 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) } /*\ ** * Creates a DOM fragment from a given list of elements or strings ** - varargs (…) SVG string \*/ static fragment(...args) { let f = doc.createDocumentFragment() for (let i = 0, ii = args.length; i < ii; i++) { let 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) } /*\ ** * Returns closest point to a given one on a given path. - path (SnapElement) path element - x (number) x coord of a point - y (number) y coord of a point = (object) in format { x (number) x coord of the point on the path y (number) y coord of the point on the path length (number) length of the path to the point distance (number) distance from the given point to the path } \*/ // Copied from http://bl.ocks.org/mbostock/8027637 static closestPoint(path, x, y) { function distance2(p) { let dx = p.x - x, dy = p.y - y return dx * dx + dy * dy } let pathNode = path.node, pathLength = pathNode.getTotalLength(), precision = (pathLength / pathNode.pathSegList.numberOfItems) * 0.125, best, bestLength, bestDistance = Infinity // linear scan for coarse approximation for ( let scan, scanLength = 0, scanDistance; scanLength <= pathLength; scanLength += precision ) { if ( (scanDistance = distance2( (scan = pathNode.getPointAtLength(scanLength)) )) < bestDistance ) { best = scan bestLength = scanLength bestDistance = scanDistance } } // binary search for precise estimate precision *= 0.5 while (precision > 0.5) { let before, after, beforeLength, afterLength, beforeDistance, afterDistance if ( (beforeLength = bestLength - precision) >= 0 && (beforeDistance = distance2( (before = pathNode.getPointAtLength(beforeLength)) )) < bestDistance ) { best = before bestLength = beforeLength bestDistance = beforeDistance } else if ( (afterLength = bestLength + precision) <= pathLength && (afterDistance = distance2( (after = pathNode.getPointAtLength(afterLength)) )) < bestDistance ) { best = after bestLength = afterLength bestDistance = afterDistance } else { precision *= 0.5 } } best = { x: best.x, y: best.y, length: bestLength, distance: Math.sqrt(bestDistance) } return best } /*\ ** * Snaps given value to given grid - values (array|number) given array of values or step of the grid - value (number) value to adjust - tolerance (number) #optional maximum distance to the target value that would trigger the snap. Default is `10`. = (number) adjusted value \*/ static snapTo(values, value, tolerance) { tolerance = is(tolerance, 'finite') ? tolerance : 10 if (is(values, 'array')) { let i = values.length while (i--) if (abs(values[i] - value) <= tolerance) { return values[i] } } else { values = +values let rem = value % values if (rem < tolerance) { return value - rem } if (rem > values - tolerance) { return value - rem + values } } return value } /*\ ** * Wraps a DOM element specified by CSS selector as @SnapElement - query (string) CSS selector of the element = (SnapElement) the current element \*/ static select(query = '') { query = query.replace(/([^\\]):/g, '$1\\:') return wrap(doc.querySelector(query)) } /*\ ** * Wraps DOM elements specified by CSS selector as set or array of @SnapElement - query (string) CSS selector of the element = (SnapElement) the current element \*/ static selectAll(query = '') { let nodelist = doc.querySelectorAll(query), set = (Snap.set || Array)() for (let i = 0; i < nodelist.length; i++) { set.push(wrap(nodelist[i])) } return set } /*\ ** * Returns you topmost element under given point. ** = (object) Snap element object - x (number) x coordinate from the top left corner of the window - y (number) y coordinate from the top left corner of the window > Usage | Snap.getElementByPoint(mouseX, mouseY).attr({stroke: "#f00"}); \*/ static getElementByPoint(x, y) { let paper = this, svg = paper.canvas, target = doc.elementFromPoint(x, y) if (!target) { return null } return wrap(target) } constructor(w, h) { if (w) { if (w.nodeType) { return wrap(w) } if (is(w, 'array') && Snap.set) { return Snap.set.apply(Snap, w) } if (w instanceof SnapElement) { return w } if (h == null) { try { w = 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) } } // Transformations /*\ ** * Utility method ** * Parses given path string into an array of arrays of path segments - pathString (string|array) path string or array of segments (in the last case it is returned straight away) = (array) array of segments \*/ Snap.parsePathString = function (pathString) { if (!pathString) { return null } let pth = Snap.path(pathString) if (pth.arr) { return Snap.path.clone(pth.arr) } let paramCounts = { a: 7, c: 6, o: 2, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, u: 3, z: 0 }, data = [] if (is(pathString, 'array') && is(pathString[0], 'array')) { // rough assumption data = Snap.path.clone(pathString) } if (!data.length) { String(pathString).replace(pathCommand, function (a, b, c) { let params = [], name = b.toLowerCase() c.replace(PATH_VALUES, function (a, b) { b && params.push(+b) }) if (name == 'm' && params.length > 2) { data.push([b].concat(params.splice(0, 2))) name = 'l' b = b == 'm' ? 'l' : 'L' } if (name == 'o' && params.length == 1) { data.push([b, params[0]]) } if (name == 'r') { data.push([b].concat(params)) } else while (params.length >= paramCounts[name]) { data.push([b].concat(params.splice(0, paramCounts[name]))) if (!paramCounts[name]) { break } } }) } data.toString = Snap.path.toString pth.arr = Snap.path.clone(data) return data } /*\ ** * Utility method ** * Parses given transform string into an array of transformations - TString (string|array) transform string or array of transformations (in the last case it is returned straight away) = (array) array of transformations \*/ let parseTransformString = (Snap.parseTransformString = function (TString) { if (!TString) { return null } let paramCounts = { r: 3, s: 4, t: 2, m: 6 }, data = [] if (is(TString, 'array') && is(TString[0], 'array')) { // rough assumption data = Snap.path.clone(TString) } if (!data.length) { String(TString).replace(T_COMMAND, function (a, b, c) { let params = [], name = b.toLowerCase() c.replace(PATH_VALUES, function (a, b) { b && params.push(+b) }) data.push([b].concat(params)) }) } data.toString = Snap.path.toString return data }) function svgTransform2string(tstr) { let res = [] tstr = tstr.replace( /(?:^|\s)(\w+)\(([^)]+)\)/g, function (all, name, params) { params = params.split(/\s*,\s*|\s+/) if (name == 'rotate' && params.length == 1) { params.push(0, 0) } if (name == 'scale') { if (params.length > 2) { params = params.slice(0, 2) } else if (params.length == 2) { params.push(0, 0) } if (params.length == 1) { params.push(params[0], 0, 0) } } if (name == 'skewX') { res.push(['m', 1, 0, math.tan(rad(params[0])), 1, 0, 0]) } else if (name == 'skewY') { res.push(['m', 1, math.tan(rad(params[0])), 0, 1, 0, 0]) } else { res.push([name.charAt(0)].concat(params)) } return all } ) return res } Snap._.svgTransform2string = svgTransform2string Snap._.rgTransform = /^[a-z][\s]*-?\.?\d/i function transform2matrix(tstr, bbox) { let tdata = parseTransformString(tstr), m = new Snap.Matrix() if (tdata) { for (let i = 0, ii = tdata.length; i < ii; i++) { let t = tdata[i], tlen = t.length, command = String(t[0]).toLowerCase(), absolute = t[0] != command, inver = absolute ? m.invert() : 0, x1, y1, x2, y2, bb if (command == 't' && tlen == 2) { m.translate(t[1], 0) } else if (command == 't' && tlen == 3) { if (absolute) { x1 = inver.x(0, 0) y1 = inver.y(0, 0) x2 = inver.x(t[1], t[2]) y2 = inver.y(t[1], t[2]) m.translate(x2 - x1, y2 - y1) } else { m.translate(t[1], t[2]) } } else if (command == 'r') { if (tlen == 2) { bb = bb || bbox m.rotate(t[1], bb.x + bb.width / 2, bb.y + bb.height / 2) } else if (tlen == 4) { if (absolute) { x2 = inver.x(t[2], t[3]) y2 = inver.y(t[2], t[3]) m.rotate(t[1], x2, y2) } else { m.rotate(t[1], t[2], t[3]) } } } else if (command == 's') { if (tlen == 2 || tlen == 3) { bb = bb || bbox m.scale(t[1], t[tlen - 1], bb.x + bb.width / 2, bb.y + bb.height / 2) } else if (tlen == 4) { if (absolute) { x2 = inver.x(t[2], t[3]) y2 = inver.y(t[2], t[3]) m.scale(t[1], t[1], x2, y2) } else { m.scale(t[1], t[1], t[2], t[3]) } } else if (tlen == 5) { if (absolute) { x2 = inver.x(t[3], t[4]) y2 = inver.y(t[3], t[4]) m.scale(t[1], t[2], x2, y2) } else { m.scale(t[1], t[2], t[3], t[4]) } } } else if (command == 'm' && tlen == 7) { m.add(t[1], t[2], t[3], t[4], t[5], t[6]) } } } return m } Snap._.transform2matrix = transform2matrix export function getSomeDefs(el) { let p = (el.node.ownerSVGElement && wrap(el.node.ownerSVGElement)) || (el.node.parentNode && wrap(el.node.parentNode)) || Snap.select('svg') || new Snap(0, 0), pdefs = p.select('defs'), defs = pdefs == null ? false : pdefs.node if (!defs) { defs = make('defs', p.node).node } return defs } function getSomeSVG(el) { return ( (el.node.ownerSVGElement && wrap(el.node.ownerSVGElement)) || Snap.select('svg') ) } export function unit2px(el, name, value) { let svg = getSomeSVG(el).node, out = {}, mgr = svg.querySelector('.svg---mgr') if (!mgr) { mgr = $('rect') $(mgr, { x: -9e9, y: -9e9, width: 10, height: 10, class: 'svg---mgr', fill: 'none' }) svg.appendChild(mgr) } function getW(val) { if (val == null) { return E } if (val == +val) { return val } $(mgr, { width: val }) try { return mgr.getBBox().width } catch (e) { return 0 } } function getH(val) { if (val == null) { return E } if (val == +val) { return val } $(mgr, { height: val }) try { return mgr.getBBox().height } catch (e) { return 0 } } function set(nam, f) { if (name == null) { out[nam] = f(el.attr(nam) || 0) } else if (nam == name) { out = f(value == null ? el.attr(nam) || 0 : value) } } switch (el.type) { case 'rect': set('rx', getW) set('ry', getH) case 'image': set('width', getW) set('height', getH) case 'text': set('x', getW) set('y', getH) break case 'circle': set('cx', getW) set('cy', getH) set('r', getW) break case 'ellipse': set('cx', getW) set('cy', getH) set('rx', getW) set('ry', getH) break case 'line': set('x1', getW) set('x2', getW) set('y1', getH) set('y2', getH) break case 'marker': set('refX', getW) set('markerWidth', getW) set('refY', getH) set('markerHeight', getH) break case 'radialGradient': set('fx', getW) set('fy', getH) break case 'tspan': set('dx', getW) set('dy', getH) break default: set(name, getW) } svg.removeChild(mgr) return out } function add2group(list) { if (!is(list, 'array')) { list = Array.prototype.slice.call(arguments, 0) } let i = 0, j = 0, node = this.node while (this[i]) delete this[i++] for (i = 0; i < list.length; i++) { if (list[i].type == 'set') { list[i].forEach(function (el) { node.appendChild(el.node) }) } else { node.appendChild(list[i].node) } } let children = node.childNodes for (i = 0; i < children.length; i++) { this[j++] = wrap(children[i]) } return this } // Hub garbage collector every 10s setInterval(function () { for (let key in hub) if (hub[has](key)) { let el = hub[key], node = el.node if ( (el.type != 'svg' && !node.ownerSVGElement) || (el.type == 'svg' && (!node.parentNode || ('ownerSVGElement' in node.parentNode && !node.ownerSVGElement))) ) { delete hub[key] } } }, 1e4) export class Paper { constructor(w, h) { let res, desc, defs if (w && w.tagName && w.tagName.toLowerCase() == 'svg') { if (w.snap in hub) { return hub[w.snap] } let doc = w.ownerDocument res = new SnapElement(w) desc = w.getElementsByTagName('desc')[0] defs = w.getElementsByTagName('defs')[0] 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 extend(res, Paper.prototype) res.paper = res.root = res } else { res = make('svg', doc.body) $(res.node, { width: w, height: h, version: 1.1, xmlns }) } return res } /*\ ** * Creates a nested SVG element. - x (number) @optional X of the element - y (number) @optional Y of the element - width (number) @optional width of the element - height (number) @optional height of the element - vbx (number) @optional viewbox X - vby (number) @optional viewbox Y - vbw (number) @optional viewbox width - vbh (number) @optional viewbox height ** = (object) the `svg` element ** \*/ svg(x, y, width, height, vbx, vby, vbw, vbh) { let attrs = {} if (is(x, 'object') && y == null) { attrs = x } else { if (x != null) { attrs.x = x } if (y != null) { attrs.y = y } if (width != null) { attrs.width = width } if (height != null) { attrs.height = height } if (vbx != null && vby != null && vbw != null && vbh != null) { attrs.viewBox = [vbx, vby, vbw, vbh] } } return this.el('svg', attrs) } mask(first) { let attr, el = this.el('mask') if (arguments.length == 1 && first && !first.type) { el.attr(first) } else if (arguments.length) { el.add(Array.from(arguments)) } return el } /*\ ** * Equivalent in behaviour to @Paper.g, except it’s a pattern. - x (number) @optional X of the element - y (number) @optional Y of the element - width (number) @optional width of the element - height (number) @optional height of the element - vbx (number) @optional viewbox X - vby (number) @optional viewbox Y - vbw (number) @optional viewbox width - vbh (number) @optional viewbox height ** = (object) the `pattern` element ** \*/ ptrn(x, y, width, height, vx, vy, vw, vh) { if (is(x, 'object')) { let attr = x } else { attr = { patternUnits: 'userSpaceOnUse' } if (x) { attr.x = x } if (y) { attr.y = y } if (width != null) { attr.width = width } if (height != null) { attr.height = height } if (vx != null && vy != null && vw != null && vh != null) { attr.viewBox = [vx, vy, vw, vh] } else { attr.viewBox = [x || 0, y || 0, width || 0, height || 0] } } return this.el('pattern', attr) } /*\ ** * Creates a element. - id (string) @optional id of element to link * or - id (SnapElement) @optional element to link ** = (object) the `use` element ** \*/ use(id) { if (id != null) { if (id instanceof SnapElement) { if (!id.attr('id')) { id.attr({ id: uuid(id.type + 'S') }) } id = id.attr('id') } if (String(id).charAt() == '#') { id = id.substring(1) } return this.el('use', { 'xlink:href': '#' + id }) } else { return SnapElement.prototype.use.call(this) } } /*\ ** * Creates a element. - vbx (number) @optional viewbox X - vby (number) @optional viewbox Y - vbw (number) @optional viewbox width - vbh (number) @optional viewbox height = (object) the `symbol` element ** \*/ symbol(vx, vy, vw, vh) { let attr = {} if (vx != null && vy != null && vw != null && vh != null) { attr.viewBox = [vx, vy, vw, vh] } return this.el('symbol', attr) } /*\ ** * Draws a text string ** - x (number) x coordinate position - y (number) y coordinate position - text (string|array) The text string to draw or array of strings to nest within separate `` elements = (object) the `text` element ** > Usage | let t1 = paper.text(50, 50, "Snap"); | let t2 = paper.text(50, 50, ["S","n","a","p"]); | // Text path usage | t1.attr({textpath: "M10,10L100,100"}); | // or | let pth = paper.path("M10,10L100,100"); | t1.attr({textpath: pth}); \*/ text(x, y, text) { let attr = {} if (is(x, 'object')) { attr = x } else if (x != null) { attr = { x: x, y: y, text: text || '' } } return this.el('text', attr) } /*\ ** * Draws a line ** - x1 (number) x coordinate position of the start - y1 (number) y coordinate position of the start - x2 (number) x coordinate position of the end - y2 (number) y coordinate position of the end = (object) the `line` element ** > Usage | let t1 = paper.line(50, 50, 100, 100); \*/ line(x1, y1, x2, y2) { let attr = {} if (is(x1, 'object')) { attr = x1 } else if (x1 != null) { attr = { x1: x1, x2: x2, y1: y1, y2: y2 } } return this.el('line', attr) } /*\ ** * Draws a polyline ** - points (array) array of points * or - varargs (…) points = (object) the `polyline` element ** > Usage | let p1 = paper.polyline([10, 10, 100, 100]); | let p2 = paper.polyline(10, 10, 100, 100); \*/ polyline(points) { if (arguments.length > 1) { points = Array.from(arguments) } let attr = {} if (is(points, 'object') && !is(points, 'array')) { attr = points } else if (points != null) { attr = { points: points } } return this.el('polyline', attr) } /*\ ** * Draws a polygon. See @Paper.polyline \*/ polygon(points) { if (arguments.length > 1) { points = Array.prototype.slice.call(arguments, 0) } let attr = {} if (is(points, 'object') && !is(points, 'array')) { attr = points } else if (points != null) { attr = { points: points } } return this.el('polygon', attr) } /*\ ** * Creates an element on paper with a given name and no attributes ** - name (string) tag name - attr (object) attributes = (SnapElement) the current element > Usage | let c = paper.circle(10, 10, 10); // is the same as... | let c = paper.el("circle").attr({ | cx: 10, | cy: 10, | r: 10 | }); | // and the same as | let c = paper.el("circle", { | cx: 10, | cy: 10, | r: 10 | }); \*/ el(name, attr) { let el = make(name, this.node) attr && el.attr(attr) return el } /*\ * * Draws a rectangle ** - x (number) x coordinate of the top left corner - y (number) y coordinate of the top left corner - width (number) width - height (number) height - rx (number) #optional horizontal radius for rounded corners, default is 0 - ry (number) #optional vertical radius for rounded corners, default is rx or 0 = (object) the `rect` element ** > Usage | // regular rectangle | let c = paper.rect(10, 10, 50, 50); | // rectangle with rounded corners | let c = paper.rect(40, 40, 50, 50, 10); \*/ rect(x, y, w, h, rx, ry) { let attr if (ry == null) { ry = rx } if (is(x, 'object') && x == '[object Object]') { attr = x } else if (x != null) { attr = { x: x, y: y, width: w, height: h } if (rx != null) { attr.rx = rx attr.ry = ry } } return this.el('rect', attr) } /*\ ** * Draws a circle ** - x (number) x coordinate of the centre - y (number) y coordinate of the centre - r (number) radius = (object) the `circle` element ** > Usage | let c = paper.circle(50, 50, 40); \*/ circle(cx, cy, r) { let attr if (is(cx, 'object') && cx == '[object Object]') { attr = cx } else if (cx != null) { attr = { cx: cx, cy: cy, r: r } } return this.el('circle', attr) } /*\ ** * Places an image on the surface ** - src (string) URI of the source image - x (number) x offset position - y (number) y offset position - width (number) width of the image - height (number) height of the image = (object) the `image` element * or = (object) Snap element object with type `image` ** > Usage | let c = paper.image("apple.png", 10, 10, 80, 80); \*/ image(src, x, y, width, height) { let el = this.el('image') if (is(src, 'object') && 'src' in src) { el.attr(src) } else if (src != null) { let set = { 'xlink:href': src, preserveAspectRatio: 'none' } if (x != null && y != null) { set.x = x set.y = y } if (width != null && height != null) { set.width = width set.height = height } else { preload(src, function () { $(el.node, { width: this.offsetWidth, height: this.offsetHeight }) }) } $(el.node, set) } return el } /*\ ** * Draws an ellipse ** - x (number) x coordinate of the centre - y (number) y coordinate of the centre - rx (number) horizontal radius - ry (number) vertical radius = (object) the `ellipse` element ** > Usage | let c = paper.ellipse(50, 50, 40, 20); \*/ ellipse(cx, cy, rx, ry) { let attr if (is(cx, 'object') && cx == '[object Object]') { attr = cx } else if (cx != null) { attr = { cx: cx, cy: cy, rx: rx, ry: ry } } return this.el('ellipse', attr) } path(d) { let attr if (is(d, 'object') && !is(d, 'array')) { attr = d } else if (d) { attr = { d: d } } return this.el('path', attr) } group(first) { let attr, el = this.el('g') if (arguments.length == 1 && first && !first.type) { el.attr(first) } else if (arguments.length) { el.add(Array.from(arguments)) } return el } /*\ ** * Creates a `` element ** - filstr (string) SVG fragment of filter provided as a string = (object) @SnapElement * Note: It is recommended to use filters embedded into the page inside an empty SVG element. > Usage | let f = paper.filter(''), | c = paper.circle(10, 10, 10).attr({ | filter: f | }); \*/ filter(filstr) { let paper = this if (paper.type !== 'svg') { paper = paper.paper } let f = Snap.parse(String(filstr)), id = uuid((this.type || '') + 'S'), width = paper.node.offsetWidth, height = paper.node.offsetHeight, filter = $('filter') $(filter, { id: id, filterUnits: 'userSpaceOnUse' }) filter.appendChild(f.node) paper.defs.appendChild(filter) return new SnapElement(filter) } toString() { let doc = this.node.ownerDocument, f = doc.createDocumentFragment(), d = doc.createElement('div'), svg = this.node.cloneNode(true), res f.appendChild(d) d.appendChild(svg) $(svg, { xmlns: 'http://www.w3.org/2000/svg' }) res = d.innerHTML return res } /*\ ** * Removes all child nodes of the paper, except . */ clear() { let node = this.node.firstChild, next while (node) { next = node.nextSibling if (node.tagName != 'defs') { node.parentNode.removeChild(node) } else { this.clear.call({ node: node }) } node = next } } } export class SnapElement { constructor(el) { if (el.snap in hub) { return hub[el.snap] } let svg try { svg = el.ownerSVGElement } catch (e) {} /*\ ** * 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 | let c = paper.circle(10, 10, 10); | c.node.onclick = function () { | c.attr("fill", "red"); | }; \*/ this.node = el if (svg) { this.paper = new Paper(svg) } /*\ * SnapElement.type [ property (string) ] ** * SVG tag name of the given element. \*/ this.type = el.tagName || el.nodeName let id = (this.id = uuid((this.type || '') + 'S')) 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 }) { extend(this, Paper.prototype) } } /* ** * Gets or sets given attributes of the element. ** - params (object) contains key-value pairs of attributes you want to set * or - param (string) name of the attribute = (SnapElement) the current element * or = (string) value of attribute > Usage | el.attr({ | fill: "#fc0", | stroke: "#000", | strokeWidth: 2, // CamelCase... | "fill-opacity": 0.5, // or dash-separated names | width: "*=2" // prefixed values | }); | console.log(el.attr("fill")); // #fc0 * Prefixed values in format `"+=10"` supported. All four operations * (`+`, `-`, `*` and `/`) could be used. Optionally you can use units for `+` * and `-`: `"+=2em"`. */ attr(params, value) { let node = this.node if (!params) { if (node.nodeType != 1) { return { text: node.nodeValue } } let attr = node.attributes, out = {} for (let i = 0, ii = attr.length; i < ii; i++) { out[attr[i].nodeName] = attr[i].nodeValue } return out } if (is(params, 'string')) { if (value !== void 0) { params = { [params]: value } } else { return eve('snap.util.getattr.' + params, this).firstDefined() } } for (let att in params) { eve('snap.util.attr.' + att, this, params[att]) } return this } /* ** * Returns array of all the children of the element. = (array) array of Elements */ children() { let out = [], ch = this.node.childNodes for (let i = 0, ii = ch.length; i < ii; i++) { out[i] = new Snap(ch[i]) } return out } /*\ ** * Returns object representation of the given element and all its children. = (object) in format o { o type (string) this.type, o attr (object) attributes map, o childNodes (array) optional array of children in the same format o } \*/ toJSON() { let out = [] jsonFiller([this], out) return out[0] } } // default eve.on('snap.util.getattr', function () { let key = eve.nt().split('.').at(-1) let css = key.replace(/[A-Z]/g, function (letter) { return '-' + letter.toLowerCase() }) if (CSS_ATTR[css]) { return this.node.ownerDocument.defaultView .getComputedStyle(this.node, null) .getPropertyValue(css) } else { return $(this.node, key) } }) eve.on('snap.util.attr', function (value) { let key = eve.nt().split('.').at(-1) let attr = { [key]: value } let css = key.replace(/[A-Z]/g, function (letter) { return '-' + letter.toLowerCase() }) if (CSS_ATTR[css]) { this.node.style[css] = value == null ? E : value } else { $(this.node, attr) } })