1/* KUTE.js - The Light Tweening Engine 2 * package - SVG Plugin 3 * desc - draw SVG strokes, morph SVG and SVG transforms 4 * by dnp_theme 5 * Licensed under MIT-License 6 */ 7 8(function (root,factory) { 9 if (typeof define === 'function' && define.amd) { 10 define(['./kute.js'], factory); 11 } else if(typeof module == 'object' && typeof require == 'function') { 12 module.exports = factory(require('./kute.js')); 13 } else if ( typeof root.KUTE !== 'undefined' ) { 14 factory(root.KUTE); 15 } else { 16 throw new Error("SVG Plugin require KUTE.js."); 17 } 18}(this, function(KUTE) { 19 'use strict'; 20 21 var g = typeof global !== 'undefined' ? global : window, K = KUTE, // connect plugin to KUTE object and global 22 DOM = K.dom, parseProperty = K.parseProperty, prepareStart = K.prepareStart, getCurrentStyle = K.getCurrentStyle, 23 trueColor = K.truC, trueDimension = K.truD, crossCheck = K.crossCheck, 24 number = g.Interpolate.number, unit = g.Interpolate.unit, color = g.Interpolate.color, // interpolate functions 25 defaultOptions = K.defaultOptions, // default tween options since 1.6.1 26 27 // browser detection 28 isIE = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})").exec(navigator.userAgent) !== null ? parseFloat( RegExp.$1 ) : false; 29 30 if (isIE&&isIE<9) {return;} // return if SVG API is not supported 31 32 // here we go with the plugin 33 var pathReg = /(m[^(h|v|l)]*|[vhl][^(v|h|l|z)]*)/gmi, ns = 'http://www.w3.org/2000/svg', 34 // function(array1, array2, length, progress) for SVG morph 35 coords = g.Interpolate.coords = function(a,b,l,v) { 36 var points = []; 37 for(var i=0;i<l;i++) { // for each point 38 points[i] = []; 39 for(var j=0;j<2;j++) { // each point coordinate 40 points[i].push( ((a[i][j]+(b[i][j]-a[i][j])*v) * 1000 >> 0)/1000 ); 41 } 42 } 43 return points; 44 }; 45 46 47 // SVG MORPH 48 var getSegments = function(s,e,r){ // getSegments returns an array of points based on a sample size morphPrecision 49 var s1 = [], e1 = [], le1 = s.getTotalLength(), le2 = e.getTotalLength(), ml = Math.max(le1,le2), 50 d = r, ar = ml / r, j = 0, sl = ar*r; // sl = sample length 51 52 while ( (j += r) < sl ) { // populate the points arrays based on morphPrecision as sample size 53 s1.push( [s.getPointAtLength(j).x, s.getPointAtLength(j).y]); 54 e1.push( [e.getPointAtLength(j).x, e.getPointAtLength(j).y]); 55 } 56 return [s1,e1]; 57 }, 58 getClosestPoint = function(p,t,s){ // utility for polygon paths, returns a close point from the original path (length,pointAtLength,smallest); // intervalLength 59 var x, y, a = [], l = s.length, dx, nx, pr; 60 for (var i=0; i<l; i++){ 61 x = Math.abs(s[i][0] - t.x); 62 y = Math.abs(s[i][1] - t.y); 63 a.push( Math.sqrt( x * x + y * y ) ); 64 } 65 dx = a.indexOf(Math.min.apply(null,a)); 66 pr = !!s[dx-1] ? dx-1 : l-1; 67 nx = !!s[dx+1] ? dx+1 : 0; 68 return Math.abs(s[pr][0] - t.x) < p && Math.abs(s[pr][1] - t.y) < p ? s[pr] 69 : Math.abs(s[nx][0] - t.x) < p && Math.abs(s[nx][1] - t.y) < p ? s[nx] 70 : Math.abs(s[dx][0] - t.x) < p && Math.abs(s[dx][1] - t.y) < p ? s[dx] 71 : [t.x,t.y]; 72 }, 73 pathToAbsolute = function(p) { // simple utility for polygons | this is still BETA / a work in progress 74 var np = p.match(pathReg), wp = [], l = np.length, s, c, r, x = 0, y = 0; 75 for (var i = 0; i<l; i++){ 76 np[i] = np[i]; c = np[i][0]; r = new RegExp(c+'[^\\d|\\-]*','i'); 77 np[i] = np[i].replace(/(^|[^,])\s*-/g, '$1,-').replace(/(\s+\,|\s|\,)/g,',').replace(r,'').split(','); 78 np[i][0] = parseFloat(np[i][0]); 79 np[i][1] = parseFloat(np[i][1]); 80 if (i === 0) { x+=np[i][0]; y +=np[i][1]; } 81 else { 82 x = np[i-1][0]; 83 y = np[i-1][1]; 84 if (/l/i.test(c)) { 85 np[i][0] = c === 'l' ? np[i][0] + x : np[i][0]; 86 np[i][1] = c === 'l' ? np[i][1] + y : np[i][1]; 87 } else if (/h/i.test(c)) { 88 np[i][0] = c === 'h' ? np[i][0] + x : np[i][0]; 89 np[i][1] = y; 90 } else if (/v/i.test(c)) { 91 np[i][0] = x; 92 np[i][1] = c === 'v' ? np[i][0] + y : np[i][0]; 93 } 94 } 95 } 96 return np; 97 }, 98 getOnePath = function(p){ return p.split(/z/i).shift() + 'z'; }, // we only tween first path only 99 createPath = function (p){ // create a <path> when glyph 100 var createdPath = document.createElementNS(ns,'path'), d = typeof p === 'object' ? p.getAttribute('d') : p; 101 createdPath.setAttribute('d',d); return createdPath; 102 }, 103 forcePath = function(p){ // forcePath for glyph elements
104 if (p.tagName === 'glyph') { // perhaps we can also change other SVG tags in the future 105 var c = createPath(p); p.parentNode.appendChild(c); return c; 106 } 107 return p; 108 }, 109 clone = function(a) { 110 var copy; 111 if (a instanceof Array) { 112 copy = []; 113 for (var i = 0, len = a.length; i < len; i++) { 114 copy[i] = clone(a[i]); 115 } 116 return copy; 117 } 118 return a; 119 }, 120 getPath = function(e){ // get path d attribute or create a path from string value 121 var p = {}, el = typeof e === 'object' ? e : /^\.|^\#/.test(e) ? document.querySelector(e) : null; 122 if ( el && /path|glyph/.test(el.tagName) ) { 123 p.e = forcePath(el); 124 p.o = el.getAttribute('d'); 125 126 } else if (!el && /[a-z][^a-z]*/ig.test(e)) { // maybe it's a string path already 127 p.e = createPath(e.trim()); 128 p.o = e; 129 } 130 return p; 131 }, 132 computePathCross = function(s,e){ // pathCross 133 var s1, e1, pointsArray, largerPathLength, smallerPath, largerPath, simulatedSmallerPath, nsm = [], sml, cl = [], len, tl, cs, 134 index = this.options.morphIndex; 135 136 if (!this._isPolygon) { 137 s = createPath(s); e = createPath(e); 138 pointsArray = getSegments(s,e,this.options.morphPrecision); 139 s1 = pointsArray[0]; e1 = pointsArray[1]; largerPathLength = e1.length; 140 } else { 141 s = pathToAbsolute(s); e = pathToAbsolute(e); 142 143 if ( s.length !== e.length ){ 144 largerPathLength = Math.max(s.length,e.length); 145 if ( largerPathLength === e.length) { smallerPath = s; largerPath = e; } else { smallerPath = e; largerPath = s; } 146 sml = smallerPath.length; 147 148 simulatedSmallerPath = createPath('M'+smallerPath.join('L')+'z'); len = simulatedSmallerPath.getTotalLength() / largerPathLength; 149 for (var i=0; i<largerPathLength; i++){ 150 tl = simulatedSmallerPath.getPointAtLength(len*i); 151 cs = getClosestPoint(len,tl,smallerPath); 152 nsm.push( [ cs[0], cs[1] ] ); 153 } 154 155 if (largerPathLength === e.length) { e1 = largerPath; s1 = nsm; } else { s1 = largerPath; e1 = nsm; } 156 } else { 157 s1 = s; e1 = e; 158 } 159 } 160 161 // reverse arrays 162 if (this.options.reverseFirstPath) { s1.reverse(); } 163 if (this.options.reverseSecondPath) { e1.reverse(); } 164 165 // shift second array to for smallest tween distance 166 if (index) { 167 var e11 = e1.splice(index,largerPathLength-index); 168 e1 = e11.concat(e1); 169 } 170 171 s = e = null; 172 return [s1,e1] 173 }; 174 175 // set default morphPrecision since 1.6.1 176 defaultOptions.morphPrecision = 15; 177 178 // process path object and also register the render function 179 parseProperty.path = function(o,v) { 180 if (!('path' in DOM)) { 181 DOM.path = function(l,p,a,b,v){ 182 l.setAttribute("d", v === 1 ? b.o : 'M' + coords( a['d'],b['d'],b['d'].length,v ) + 'Z' ); 183 } 184 } 185 return getPath(v); 186 }; 187 188 prepareStart.path = function(p){ 189 return this.element.getAttribute('d'); 190 }; 191 192 crossCheck.path = function() { // unlike other cases, the crossCheck apply to both to() and fromTo() methods 193 var p1 = getOnePath(this.valuesStart.path.o), p2 = getOnePath(this.valuesEnd.path.o), paths; 194 195 // path tween options 196 this.options.morphPrecision = this.options && 'morphPrecision' in this.options ? parseInt(this.options.morphPrecision) : defaultOptions.morphPrecision; 197 this._isPolygon = !/[CSQTA]/i.test(p1) && !/[CSQTA]/i.test(p2); // check if both shapes are polygons 198 199 // begin processing paths 200 paths = computePathCross.apply(this,[p1,p2]); 201 202 this.valuesStart.path.d = paths[0]; 203 this.valuesEnd.path.d = paths[1]; 204 }; 205 206 207 // SVG DRAW 208 var percent = function(v,l){ return parseFloat(v) / 100 * l; }, 209 // SVG DRAW UTILITITES 210 // http://stackoverflow.com/a/30376660 211 getRectLength = function(el){ // returns the length of a Rect 212 var w = el.getAttribute('width'); 213 var h = el.getAttribute('height'); 214 return (w*2)+(h*2); 215 }, 216 getPolyLength = function(el){ // getPolygonLength / getPolylineLength - return the length of the Polygon / Polyline 217 var points = el.getAttribute('points').split(' '), len = 0; 218 if (points.length > 1) { 219 var coord = function (p) { 220 var c = p.split(','); 221 if (c.length != 2) { return; } // return undefined 222 if (isNaN(c[0]) || isNaN(c[1])) { return; } 223 return [parseFloat(c[0]), parseFloat(c[1])]; 224 }; 225 226 var dist = function (c1, c2) { 227 if (c1 != undefined && c2 != undefined) { 228 return Math.sqrt(Math.pow((c2[0]-c1[0]), 2) + Math.pow((c2[1]-c1[1]), 2)); 229 } 230 return 0; 231 }; 232 233 if (points.length > 2) { 234 for (var i=0; i<points.length-1; i++) { 235 len += dist(coord(points[i]), coord(points[i+1])); 236 } 237 } 238 len += dist(coord(points[0]), coord(points[points.length-1])); 239 } 240 return len; 241 }, 242 getLineLength = function(el){ // return the length of the line 243 var x1 = el.getAttribute('x1'); 244 var x2 = el.getAttribute('x2'); 245 var y1 = el.getAttribute('y1'); 246 var y2 = el.getAttribute('y2'); 247 return Math.sqrt(Math.pow((x2-x1), 2)+Math.pow((y2-y1),2)); 248 }, 249 getCircleLength = function(el){ // return the length of the circle 250 var r = el.getAttribute('r'); 251 return 2 * Math.PI * r; 252 }, 253 getEllipseLength = function(el) { // returns the length of an ellipse 254 var rx = el.getAttribute('rx'), ry = el.getAttribute('ry'), 255 len = 2*rx, wid = 2*ry; 256 return ((Math.sqrt(.5 * ((len * len) + (wid * wid)))) * (Math.PI * 2)) / 2; 257 }, 258 getTotalLength = function(el){ // returns the result of any of the below functions 259 if (/rect/.test(el.tagName)) { 260 return getRectLength(el); 261 } else if (/circle/.test(el.tagName)) { 262 return getCircleLength(el); 263 } else if (/ellipse/.test(el.tagName)) { 264 return getEllipseLength(el); 265 } else if (/polygon|polyline/.test(el.tagName)) { 266 return getPolyLength(el); 267 } else if (/line/.test(el.tagName)) { 268 return getLineLength(el); 269 } 270 }, 271 getDraw = function(e,v){ 272 var l = /path|glyph/.test(e.tagName) ? e.getTotalLength() : getTotalLength(e), start, end, d, o; 273 if ( v instanceof Object ) { 274 return v; 275 } else if (typeof v === 'string') { 276 v = v.split(/\,|\s/); 277 start = /%/.test(v[0]) ? percent(v[0].trim(),l) : parseFloat(v[0]); 278 end = /%/.test(v[1]) ? percent(v[1].trim(),l) : parseFloat(v[1]); 279 } else if (typeof v === 'undefined') { 280 o = parseFloat(getCurrentStyle(e,'stroke-dashoffset')); 281 d = getCurrentStyle(e,'stroke-dasharray').split(/\,/); 282 283 start = 0-o; 284 end = parseFloat(d[0]) + start || l; 285 } 286 return { s: start, e: end, l: l } 287 }; 288 289 parseProperty.draw = function(a,o){ // register the draw property 290 if (!('draw' in DOM)) { 291 DOM.draw = function(l,p,a,b,v){ 292 var pathLength = (a.l*100>>0)/100, start = (number(a.s,b.s,v)*100>>0)/100, end = (number(a.e,b.e,v)*100>>0)/100, 293 offset = 0 - start, dashOne = end+offset; 294 l.style.strokeDashoffset = offset +'px'; 295 l.style.strokeDasharray = (((dashOne <1 ? 0 : dashOne)*100>>0)/100) + 'px, ' + pathLength + 'px'; 296 } 297 } 298 return getDraw(this.element,o); 299 } 300 301 prepareStart.draw = function(){ 302 return getDraw(this.element); 303 } 304 305 306 // SVG Transform 307 var parseStringOrigin = function(origin,box){ 308 return /[a-zA-Z]/.test(origin) && !/px/.test(origin) ? origin.replace(/top|left/,0).replace(/right|bottom/,100).replace(/center|middle/,50) 309 : /%/.test(origin) ? (box.x + parseFloat(origin) * box.width / 100) : parseFloat(origin); 310 }, 311 parseTransformString = function (a){ // helper function that turns transform value from string to object 312 var d = a && /\)/.test(a) ? a.substring(0, a.length-1).split(/\)\s|\)/) : 'none', c = {}; 313 314 if (d instanceof Array) { 315 for (var j=0, jl = d.length; j<jl; j++){ 316 var p = d[j].trim().split('('); c[p[0]] = p[1]; 317 } 318 } 319 return c; 320 }, 321 parseTransformObject = function(v){ 322 var svgTransformObject = {}, bb = this.element.getBBox(), 323 cx = bb.x + bb.width/2, cy = bb.y + bb.height/2, // by default the transformOrigin is "50% 50%" of the shape box 324 origin = this.options.transformOrigin, translation; 325 326 origin = !!origin ? (origin instanceof Array ? origin : origin.split(/\s/)) : [cx,cy]; 327 328 origin[0] = typeof origin[0] === 'number' ? origin[0] : parseStringOrigin(origin[0],bb); 329 origin[1] = typeof origin[1] === 'number' ? origin[1] : parseStringOrigin(origin[1],bb); 330 331 svgTransformObject.origin = origin; 332 333 for ( var i in v ) { // populate the valuesStart and / or valuesEnd 334 if (i === 'rotate'){ 335 svgTransformObject[i] = typeof v[i] === 'number' ? v[i] : v[i] instanceof Array ? v[i][0] : v[i].split(/\s/)[0]*1; 336 } else if (i === 'translate'){ 337 translation = v[i] instanceof Array ? v[i] : /\,|\s/.test(v[i]) ? v[i].split(',') : [v[i],0]; 338 svgTransformObject[i] = [translation[0]*1||0, translation[1]*1||0]; 339 } else if (/skew/.test(i)) { 340 svgTransformObject[i] = v[i]*1||0; 341 } else if (i === 'scale'){ 342 svgTransformObject[i] = parseFloat(v[i])||1; 343 } 344 } 345 346 return svgTransformObject; 347 }; 348 349 parseProperty.svgTransform = function(p,v){ 350 // register the render function 351 if (!('svgTransform' in DOM)) { 352 DOM.svgTransform = function(l,p,a,b,v){ 353 var x = 0, y = 0, tmp, deg = Math.PI/180, 354 scale = 'scale' in b ? number(a.scale,b.scale,v) : 1, 355 rotate = 'rotate' in b ? number(a.rotate,b.rotate,v) : 0, 356 sin = Math.sin(rotate*deg), cos = Math.cos(rotate*deg), 357 skewX = 'skewX' in b ? number(a.skewX,b.skewX,v) : 0, 358 skewY = 'skewY' in b ? number(a.skewY,b.skewY,v) : 0, 359 complex = rotate||skewX||skewY||scale!==1 || 0; 360 361 // start normalizing the translation, we start from last to first (from last chained translation) 362 // the normalized translation will handle the transformOrigin tween option and makes sure to have a consistent transformation 363 x -= complex ? b.origin[0] : 0; y -= complex ? b.origin[1] : 0; // we start with removing transformOrigin from translation 364 x *= scale; y *= scale; // we now apply the scale 365 y += skewY ? x*Math.tan(skewY*deg) : 0; x += skewX ? y*Math.tan(skewX*deg) : 0; // now we apply skews 366 tmp = cos*x - sin*y; // apply rotation as well 367 y = rotate ? sin*x + cos*y : y; x = rotate ? tmp : x; 368 x += 'translate' in b ? number(a.translate[0],b.translate[0],v) : 0; // now we apply the actual translation 369 y += 'translate' in b ? number(a.translate[1],b.translate[1],v) : 0; 370 x += complex ? b.origin[0] : 0; y += complex ? b.origin[1] : 0; // normalizing ends with the addition of the transformOrigin to the translation 371 372 // finally we apply the transform attribute value 373 l.setAttribute('transform', ( x||y ? ('translate(' + (x*1000>>0)/1000 + ( y ? (',' + ((y*1000>>0)/1000)) : '') + ')') : '' ) 374 +( rotate ? 'rotate(' + (rotate*1000>>0)/1000 + ')' : '' ) 375 +( skewX ? 'skewX(' + (skewX*1000>>0)/1000 + ')' : '' ) 376 +( skewY ? 'skewY(' + (skewY*1000>>0)/1000 + ')' : '' ) 377 +( scale !== 1 ? 'scale(' + (scale*1000>>0)/1000 +')' : '' ) ); 378 } 379 } 380 381 // now prepare transform 382 return parseTransformObject.call(this,v); 383 } 384 385 // returns an obect with current transform attribute value 386 prepareStart.svgTransform = function(p,t) { 387 var transformObject = {}, currentTransform = parseTransformString(this.element.getAttribute('transform')); 388 for (var i in t) { transformObject[i] = i in currentTransform ? currentTransform[i] : (i==='scale'?1:0); } // find a value in current attribute value or add a default value 389 return transformObject; 390 } 391 392 crossCheck.svgTransform = function() { // helper function that helps preserve current transform properties into the objects 393 if (!this.options.rpr) return;
393 // fix since 1.6.1 for fromTo() method 394 var valuesStart = this.valuesStart.svgTransform, valuesEnd = this.valuesEnd.svgTransform, 395 currentTransform = parseTransformObject.call(this, parseTransformString(this.element.getAttribute('transform')) ); 396 397 for ( var i in currentTransform ) { valuesStart[i] = currentTransform[i]; } // populate the valuesStart first 398 399 // now try to determine the REAL translation 400 var parentSVG = this.element.ownerSVGElement, 401 newTransform = parentSVG.createSVGTransformFromMatrix( 402 parentSVG.createSVGMatrix() 403 .translate(-valuesStart.origin[0],-valuesStart.origin[1]) // - origin 404 .translate('translate' in valuesStart ? valuesStart.translate[0] : 0,'translate' in valuesStart ? valuesStart.translate[1] : 0) // the current translate 405 .rotate(valuesStart.rotate||0).skewX(valuesStart.skewX||0).skewY(valuesStart.skewY||0).scale(valuesStart.scale||1)// the other functions 406 .translate(+valuesStart.origin[0],+valuesStart.origin[1]) // + origin 407 ); 408 409 valuesStart.translate = [newTransform.matrix.e,newTransform.matrix.f]; // finally the translate we're looking for 410 411 // copy existing and unused properties to the valuesEnd 412 for ( var i in valuesStart) { if ( !(i in valuesEnd)) { valuesEnd[i] = valuesStart[i]; } } 413 } 414 415 return this; 416}));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.