/*! * version: 0.8.1 * date: 2015-12-18 * updates and docs at: http://greensock.com * * @license copyright (c) 2008-2016, greensock. all rights reserved. * morphsvgplugin is a club greensock membership benefit; you must have a valid membership to use * this code without violating the terms of use. visit http://greensock.com/club/ to sign up or get more details. * this work is subject to the software agreement that was issued with your membership. * * @author: jack doyle, jack@greensock.com */ var _gsscope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with amd/requirejs and commonjs/node (_gsscope._gsqueue || (_gsscope._gsqueue = [])).push( function() { "use strict"; var _deg2rad = math.pi / 180, _rad2deg = 180 / math.pi, _svgpathexp = /[achlmqstvz]|(-?\d*\.?\d*(?:e[\-+]?\d+)?)[0-9]/ig, _numbersexp = /(?:(-|-=|\+=)?\d*\.?\d*(?:e[\-+]?\d+)?)[0-9]/ig, _commands = /[achlmqstvz]/ig, tweenlite = _gsscope.tweenlite, //_nonnumbersexp = /(?:([\-+](?!(\d|=)))|[^\d\-+=e]|(e(?![\-+][\d])))+/ig, _log = function(message) { if (window.console) { console.log(message); } }, // translates an arc into a normalized array of cubic beziers excluding the starting x/y. the circle the arc follows will be centered at 0,0 and have a radius of 1 (hence normalized). each bezier covers no more than 90 degrees; the arc will be divided evenly into a maximum of four curves. _normalizedarctobeziers = function(anglestart, angleextent) { var segments = math.ceil(math.abs(angleextent) / 90), l = 0, a = [], angleincrement, controllength, angle, dx, dy, i; anglestart *= _deg2rad; angleextent *= _deg2rad; angleincrement = angleextent / segments; controllength = 4 / 3 * math.sin(angleincrement / 2) / (1 + math.cos(angleincrement / 2)); for (i = 0; i < segments; i++) { angle = anglestart + i * angleincrement; dx = math.cos(angle); dy = math.sin(angle); a[l++] = dx - controllength * dy; a[l++] = dy + controllength * dx; angle += angleincrement; dx = math.cos(angle); dy = math.sin(angle); a[l++] = dx + controllength * dy; a[l++] = dy - controllength * dx; a[l++] = dx; a[l++] = dy; } return a; }, // translates svg arc data into an array of cubic beziers _arctobeziers = function(lastx, lasty, rx, ry, angle, largearcflag, sweepflag, x, y) { if (lastx === x && lasty === y) { return; } rx = math.abs(rx); ry = math.abs(ry); var anglerad = (angle % 360) * _deg2rad, cosangle = math.cos(anglerad), sinangle = math.sin(anglerad), dx2 = (lastx - x) / 2, dy2 = (lasty - y) / 2, x1 = (cosangle * dx2 + sinangle * dy2), y1 = (-sinangle * dx2 + cosangle * dy2), rx_sq = rx * rx, ry_sq = ry * ry, x1_sq = x1 * x1, y1_sq = y1 * y1, radiicheck = x1_sq / rx_sq + y1_sq / ry_sq; if (radiicheck > 1) { rx = math.sqrt(radiicheck) * rx; ry = math.sqrt(radiicheck) * ry; rx_sq = rx * rx; ry_sq = ry * ry; } var sign = (largearcflag === sweepflag) ? -1 : 1, sq = ((rx_sq * ry_sq) - (rx_sq * y1_sq) - (ry_sq * x1_sq)) / ((rx_sq * y1_sq) + (ry_sq * x1_sq)); if (sq < 0) { sq = 0; } var coef = (sign * math.sqrt(sq)), cx1 = coef * ((rx * y1) / ry), cy1 = coef * -((ry * x1) / rx), sx2 = (lastx + x) / 2, sy2 = (lasty + y) / 2, cx = sx2 + (cosangle * cx1 - sinangle * cy1), cy = sy2 + (sinangle * cx1 + cosangle * cy1), ux = (x1 - cx1) / rx, uy = (y1 - cy1) / ry, vx = (-x1 - cx1) / rx, vy = (-y1 - cy1) / ry, n = math.sqrt((ux * ux) + (uy * uy)), p = ux; sign = (uy < 0) ? -1 : 1; var anglestart = (sign * math.acos(p / n)) * _rad2deg; n = math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy)); p = ux * vx + uy * vy; sign = (ux * vy - uy * vx < 0) ? -1 : 1; var angleextent = (sign * math.acos(p / n)) * _rad2deg; if (!sweepflag && angleextent > 0) { angleextent -= 360; } else if (sweepflag && angleextent < 0) { angleextent += 360; } angleextent %= 360; anglestart %= 360; var bezierpoints = _normalizedarctobeziers(anglestart, angleextent), a = cosangle * rx, b = sinangle * rx, c = sinangle * -ry, d = cosangle * ry, l = bezierpoints.length - 2, i, px, py; //translate all the bezier points according to the matrix... for (i = 0; i < l; i += 2) { px = bezierpoints[i]; py = bezierpoints[i+1]; bezierpoints[i] = px * a + py * c + cx; bezierpoints[i+1] = px * b + py * d + cy; } bezierpoints[bezierpoints.length-2] = x; //always set the end to exactly where it's supposed to be bezierpoints[bezierpoints.length-1] = y; return bezierpoints; }, //spits back an array of cubic bezier segments that use absolute coordinates. each segment starts with a "moveto" command (x coordinate, then y) and then 2 control points (x, y, x, y), then anchor. the goal is to minimize memory and maximize speed. _pathdatatobezier = function(d) { var a = (d + "").match(_svgpathexp) || [], path = [], relativex = 0, relativey = 0, elements = a.length, l = 2, points = 0, i, j, x, y, command, isrelative, segment, startx, starty, difx, dify, beziers, prevcommand; if (!d || !isnan(a[0]) || isnan(a[1])) { _log("error: malformed path data: " + d); return path; } for (i = 0; i < elements; i++) { prevcommand = command; if (isnan(a[i])) { command = a[i].touppercase(); isrelative = (command !== a[i]); //lower case means relative } else { //commands like "c" can be strung together without any new command characters between. i--; } x = +a[i+1]; y = +a[i+2]; if (isrelative) { x += relativex; y += relativey; } if (i === 0) { startx = x; starty = y; } // "m" (move) if (command === "m") { if (segment && segment.length < 8) { //if the path data was funky and just had a m with no actual drawing anywhere, skip it. path.length-=1; l = 0; } relativex = startx = x; relativey = starty = y; segment = [x, y]; points += l; l = 2; path.push(segment); i += 2; command = "l"; //an "m" with more than 2 values gets interpreted as "lineto" commands ("l"). // "c" (cubic bezier) } else if (command === "c") { if (!segment) { segment = [0, 0]; } segment[l++] = x; segment[l++] = y; if (!isrelative) { relativex = relativey = 0; } segment[l++] = relativex + a[i + 3] * 1; //note: "*1" is just a fast/short way to cast the value as a number. waaay faster in chrome, slightly slower in firefox. segment[l++] = relativey + a[i + 4] * 1; segment[l++] = relativex = relativex + a[i + 5] * 1; segment[l++] = relativey = relativey + a[i + 6] * 1; //if (y === segment[l-1] && y === segment[l-3] && x === segment[l-2] && x === segment[l-4]) { //if all the values are the same, eliminate the waste. // segment.length = l = l-6; //} i += 6; // "s" (continuation of cubic bezier) } else if (command === "s") { if (prevcommand === "c" || prevcommand === "s") { difx = relativex - segment[l - 4]; dify = relativey - segment[l - 3]; segment[l++] = relativex + difx; segment[l++] = relativey + dify; } else { segment[l++] = relativex; segment[l++] = relativey; } segment[l++] = x; segment[l++] = y; if (!isrelative) { relativex = relativey = 0; } segment[l++] = relativex = relativex + a[i + 3] * 1; segment[l++] = relativey = relativey + a[i + 4] * 1; //if (y === segment[l-1] && y === segment[l-3] && x === segment[l-2] && x === segment[l-4]) { //if all the values are the same, eliminate the waste. // segment.length = l = l-6; //} i += 4; // "q" (quadratic bezier) } else if (command === "q") { difx = x - relativex; dify = y - relativey; segment[l++] = relativex + difx * 2 / 3; segment[l++] = relativey + dify * 2 / 3; if (!isrelative) { relativex = relativey = 0; } relativex = relativex + a[i + 3] * 1; relativey = relativey + a[i + 4] * 1; difx = x - relativex; dify = y - relativey; segment[l++] = relativex + difx * 2 / 3; segment[l++] = relativey + dify * 2 / 3; segment[l++] = relativex; segment[l++] = relativey; i += 4; // "t" (continuation of quadratic bezier) } else if (command === "t") { difx = relativex - segment[l-4]; dify = relativey - segment[l-3]; segment[l++] = relativex + difx; segment[l++] = relativey + dify; difx = (relativex + difx * 1.5) - x; dify = (relativey + dify * 1.5) - y; segment[l++] = x + difx * 2 / 3; segment[l++] = y + dify * 2 / 3; segment[l++] = relativex = x; segment[l++] = relativey = y; i += 2; // "h" (horizontal line) } else if (command === "h") { y = relativey; //if (x !== relativex) { segment[l++] = relativex + (x - relativex) / 3; segment[l++] = relativey + (y - relativey) / 3; segment[l++] = relativex + (x - relativex) * 2 / 3; segment[l++] = relativey + (y - relativey) * 2 / 3; segment[l++] = relativex = x; segment[l++] = y; //} i += 1; // "v" (horizontal line) } else if (command === "v") { y = x; //adjust values because the first (and only one) isn't x in this case, it's y. x = relativex; if (isrelative) { y += relativey - relativex; } //if (y !== relativey) { segment[l++] = x; segment[l++] = relativey + (y - relativey) / 3; segment[l++] = x; segment[l++] = relativey + (y - relativey) * 2 / 3; segment[l++] = x; segment[l++] = relativey = y; //} i += 1; // "l" (line) or "z" (close) } else if (command === "l" || command === "z") { if (command === "z") { x = startx; y = starty; segment.closed = true; } if (command === "l" || math.abs(relativex - x) > 0.5 || math.abs(relativey - y) > 0.5) { segment[l++] = relativex + (x - relativex) / 3; segment[l++] = relativey + (y - relativey) / 3; segment[l++] = relativex + (x - relativex) * 2 / 3; segment[l++] = relativey + (y - relativey) * 2 / 3; segment[l++] = x; segment[l++] = y; if (command === "l") { i += 2; } } relativex = x; relativey = y; // "a" (arc) } else if (command === "a") { beziers = _arctobeziers(relativex, relativey, a[i+1]*1, a[i+2]*1, a[i+3]*1, a[i+4]*1, a[i+5]*1, (isrelative ? relativex : 0) + a[i+6]*1, (isrelative ? relativey : 0) + a[i+7]*1); for (j = 0; j < beziers.length; j++) { segment[l++] = beziers[j]; } relativex = segment[l-2]; relativey = segment[l-1]; i += 7; } else { _log("error: malformed path data: " + d); } } path.totalpoints = points + l; return path; }, //adds a certain number of beziers while maintaining the path shape (so that the start/end values can have a matching quantity of points to animate). only pass in one segment of the bezier at a time. format: [xanchor, yanchor, xcontrolpoint1, ycontrolpoint1, xcontrolpoint2, ycontrolpoint2, xanchor, yanchor, xcontrolpoint1, etc...] _subdividebezier = function(bezier, quantity) { var tally = 0, max = 0.999999, l = bezier.length, newpointspersegment = quantity / ((l - 2) / 6), ax, ay, cp1x, cp1y, cp2x, cp2y, bx, by, x1, y1, x2, y2, i, t; for (i = 2; i < l; i += 6) { tally += newpointspersegment; while (tally > max) { //compare with 0.99999 instead of 1 in order to prevent rounding errors ax = bezier[i-2]; ay = bezier[i-1]; cp1x = bezier[i]; cp1y = bezier[i+1]; cp2x = bezier[i+2]; cp2y = bezier[i+3]; bx = bezier[i+4]; by = bezier[i+5]; t = 1 / (math.floor(tally) + 1); //progress along the bezier (value between 0 and 1) x1 = ax + (cp1x - ax) * t; x2 = cp1x + (cp2x - cp1x) * t; x1 += (x2 - x1) * t; x2 += ((cp2x + (bx - cp2x) * t) - x2) * t; y1 = ay + (cp1y - ay) * t; y2 = cp1y + (cp2y - cp1y) * t; y1 += (y2 - y1) * t; y2 += ((cp2y + (by - cp2y) * t) - y2) * t; bezier.splice(i, 4, ax + (cp1x - ax) * t, //first control point ay + (cp1y - ay) * t, x1, //second control point y1, x1 + (x2 - x1) * t, //new fabricated anchor on line y1 + (y2 - y1) * t, x2, //third control point y2, cp2x + (bx - cp2x) * t, //fourth control point cp2y + (by - cp2y) * t ); i += 6; l += 6; tally--; } } return bezier; }, _beziertopathdata = function(beziers) { var data = "", l = beziers.length, rnd = 100, sl, s, i, segment; for (s = 0; s < l; s++) { segment = beziers[s]; data += "m" + segment[0] + "," + segment[1] + " c"; sl = segment.length; for (i = 2; i < sl; i++) { data += (((segment[i++] * rnd) | 0) / rnd) + "," + (((segment[i++] * rnd) | 0) / rnd) + " " + (((segment[i++] * rnd) | 0) / rnd) + "," + (((segment[i++] * rnd) | 0) / rnd) + " " + (((segment[i++] * rnd) | 0) / rnd) + "," + (((segment[i] * rnd) | 0) / rnd) + " "; } if (segment.closed) { data += "z"; } } return data; }, _reversebezier = function(bezier) { var a = [], i = bezier.length - 1, l = 0; while (--i > -1) { a[l++] = bezier[i]; a[l++] = bezier[i+1]; i--; } for (i = 0; i < l; i++) { bezier[i] = a[i]; } bezier.reversed = bezier.reversed ? false : true; }, _getaveragexy = function(bezier) { var l = bezier.length, x = 0, y = 0, i; for (i = 0; i < l; i++) { x += bezier[i++]; y += bezier[i]; } return [x / (l / 2), y / (l / 2)]; }, _getsize = function(bezier) { //rough estimate of the bounding box (based solely on the anchors) of a single segment. sets "size", "centerx", and "centery" properties on the bezier array itself, and returns the size (width * height) var l = bezier.length, xmax = bezier[0], xmin = xmax, ymax = bezier[1], ymin = ymax, x, y, i; for (i = 6; i < l; i+=6) { x = bezier[i]; y = bezier[i+1]; if (x > xmax) { xmax = x; } else if (x < xmin) { xmin = x; } if (y > ymax) { ymax = y; } else if (y < ymin) { ymin = y; } } bezier.centerx = (xmax + xmin) / 2; bezier.centery = (ymax + ymin) / 2; return (bezier.size = (xmax - xmin) * (ymax - ymin)); }, _gettotalsize = function(bezier) { //rough estimate of the bounding box of the entire list of bezier segments (based solely on the anchors). sets "size", "centerx", and "centery" properties on the bezier array itself, and returns the size (width * height) var segment = bezier.length, xmax = bezier[0][0], xmin = xmax, ymax = bezier[0][1], ymin = ymax, l, x, y, i, b; while (--segment > -1) { b = bezier[segment]; l = b.length; for (i = 6; i < l; i+=6) { x = b[i]; y = b[i+1]; if (x > xmax) { xmax = x; } else if (x < xmin) { xmin = x; } if (y > ymax) { ymax = y; } else if (y < ymin) { ymin = y; } } } bezier.centerx = (xmax + xmin) / 2; bezier.centery = (ymax + ymin) / 2; return (bezier.size = (xmax - xmin) * (ymax - ymin)); }, _sortbycomplexity = function(a, b) { return b.length - a.length; }, _sortbysize = function(a, b) { var sizea = a.size || _getsize(a), sizeb = b.size || _getsize(b); return (math.abs(sizeb - sizea) < (sizea + sizeb) / 20) ? (b.centerx - a.centerx) || (b.centery - a.centery) : sizeb - sizea; //if the size is within 10% of each other, prioritize position from left to right, then top to bottom. }, _offsetbezier = function(bezier, shapeindex) { var a = bezier.slice(0), l = bezier.length, wrap = l - 2, i, index; shapeindex = shapeindex | 0; for (i = 0; i < l; i++) { index = (i + shapeindex) % wrap; bezier[i++] = a[index]; bezier[i] = a[index+1]; } }, _gettotalmovement = function(sb, eb, shapeindex, offsetx, offsety) { var l = sb.length, d = 0, wrap = l - 2, index, i, x, y; shapeindex *= 6; for (i = 0; i < l; i += 6) { index = (i + shapeindex) % wrap; y = sb[index] - (eb[i] - offsetx); x = sb[index+1] - (eb[i+1] - offsety); d += math.sqrt(x * x + y * y); } return d; }, _getclosestshapeindex = function(sb, eb, checkreverse) { //finds the index in a closed cubic bezier array that's closest to the angle provided (angle measured from the center or average x/y). var l = sb.length, scenter = _getaveragexy(sb), //when comparing distances, adjust the coordinates as if the shapes are centered with each other. ecenter = _getaveragexy(eb), offsetx = ecenter[0] - scenter[0], offsety = ecenter[1] - scenter[1], min = _gettotalmovement(sb, eb, 0, offsetx, offsety), minindex = 0, copy, d, i; for (i = 6; i < l; i += 6) { d = _gettotalmovement(sb, eb, i / 6, offsetx, offsety); if (d < min) { min = d; minindex = i; } } if (checkreverse) { copy = sb.slice(0); _reversebezier(copy); for (i = 6; i < l; i += 6) { d = _gettotalmovement(copy, eb, i / 6, offsetx, offsety); if (d < min) { min = d; minindex = -i; } } } return minindex / 6; }, _getclosestanchor = function(bezier, x, y) { //finds the x/y of the anchor that's closest to the provided x/y coordinate (returns an array, like [x, y]). the bezier should be the top-level type that contains an array for each segment. var j = bezier.length, closestdistance = 99999999999, closestx = 0, closesty = 0, b, dx, dy, d, i, l; while (--j > -1) { b = bezier[j]; l = b.length; for (i = 0; i < l; i += 6) { dx = b[i] - x; dy = b[i+1] - y; d = math.sqrt(dx * dx + dy * dy); if (d < closestdistance) { closestdistance = d; closestx = b[i]; closesty = b[i+1]; } } } return [closestx, closesty]; }, _getclosestsegment = function(bezier, pool, startindex, sortratio, offsetx, offsety) { //matches the bezier to the closest one in a pool (array) of beziers, assuming they are in order of size and we shouldn't drop more than 20% of the size, otherwise prioritizing location (total distance to the center). extracts the segment out of the pool array and returns it. var l = pool.length, index = 0, minsize = math.min(bezier.size || _getsize(bezier), pool[startindex].size || _getsize(pool[startindex])) * sortratio, //limit things based on a percentage of the size of either the bezier or the next element in the array, whichever is smaller. min = 999999999999, cx = bezier.centerx + offsetx, cy = bezier.centery + offsety, size, i, dx, dy, d; for (i = startindex; i < l; i++) { size = pool[i].size || _getsize(pool[i]); if (size < minsize) { break; } dx = pool[i].centerx - cx; dy = pool[i].centery - cy; d = math.sqrt(dx * dx + dy * dy); if (d < min) { index = i; min = d; } } d = pool[index]; pool.splice(index, 1); return d; }, _equalizesegmentquantity = function(start, end, shapeindex, map) { //returns an array of shape indexes, 1 for each segment. var dif = end.length - start.length, longer = dif > 0 ? end : start, shorter = dif > 0 ? start : end, added = 0, sortmethod = (map === "complexity") ? _sortbycomplexity : _sortbysize, sortratio = (map === "position") ? 0 : (typeof(map) === "number") ? map : 0.8, i = shorter.length, shapeindices = (typeof(shapeindex) === "object" && shapeindex.push) ? shapeindex.slice(0) : [shapeindex], reverse = (shapeindices[0] === "reverse" || shapeindices[0] < 0), log = (shapeindex === "log"), eb, sb, b, x, y, offsetx, offsety; if (!shorter[0]) { return; } if (longer.length > 1) { start.sort(sortmethod); end.sort(sortmethod); offsetx = longer.size || _gettotalsize(longer); //ensures centerx and centery are defined (used below). offsetx = shorter.size || _gettotalsize(shorter); offsetx = longer.centerx - shorter.centerx; offsety = longer.centery - shorter.centery; if (sortmethod === _sortbysize) { for (i = 0; i < shorter.length; i++) { longer.splice(i, 0, _getclosestsegment(shorter[i], longer, i, sortratio, offsetx, offsety)); } } } if (dif) { if (dif < 0) { dif = -dif; } if (longer[0].length > shorter[0].length) { //since we use shorter[0] as the one to map the origination point of any brand new fabricated segments, do any subdividing first so that there are more points to choose from (if necessary) _subdividebezier(shorter[0], ((longer[0].length - shorter[0].length)/6) | 0); } i = shorter.length; while (added < dif) { x = longer[i].size || _getsize(longer[i]); //just to ensure centerx and centery are calculated which we use on the next line. b = _getclosestanchor(shorter, longer[i].centerx, longer[i].centery); x = b[0]; y = b[1]; shorter[i++] = [x, y, x, y, x, y, x, y]; shorter.totalpoints += 8; added++; } } for (i = 0; i < start.length; i++) { eb = end[i]; sb = start[i]; dif = eb.length - sb.length; if (dif < 0) { _subdividebezier(eb, (-dif/6) | 0); } else if (dif > 0) { _subdividebezier(sb, (dif/6) | 0); } if (reverse && !sb.reversed) { _reversebezier(sb); } shapeindex = (shapeindices[i] || shapeindices[i] === 0) ? shapeindices[i] : "auto"; if (shapeindex) { //if start shape is closed, find the closest point to the start/end, and re-organize the bezier points accordingly so that the shape morphs in a more intuitive way. if (sb.closed || (math.abs(sb[0] - sb[sb.length - 2]) < 0.5 && math.abs(sb[1] - sb[sb.length - 1]) < 0.5)) { if (shapeindex === "auto" || shapeindex === "log") { shapeindices[i] = shapeindex = _getclosestshapeindex(sb, eb, i === 0); if (shapeindex < 0) { reverse = true; _reversebezier(sb); shapeindex = -shapeindex; } _offsetbezier(sb, shapeindex * 6); } else if (shapeindex !== "reverse") { if (i && shapeindex < 0) { //only happens if an array is passed as shapeindex and a negative value is defined for an index beyond 0. very rare, but helpful sometimes. _reversebezier(sb); } _offsetbezier(sb, (shapeindex < 0 ? -shapeindex : shapeindex) * 6); } //otherwise, if it's not a closed shape, consider reversing it if that would make the overall travel less } else if (!reverse && (shapeindex === "auto" && (math.abs(eb[0] - sb[0]) + math.abs(eb[1] - sb[1]) + math.abs(eb[eb.length - 2] - sb[sb.length - 2]) + math.abs(eb[eb.length - 1] - sb[sb.length - 1]) > math.abs(eb[0] - sb[sb.length - 2]) + math.abs(eb[1] - sb[sb.length - 1]) + math.abs(eb[eb.length - 2] - sb[0]) + math.abs(eb[eb.length - 1] - sb[1])) || (shapeindex % 2))) { _reversebezier(sb); shapeindices[i] = -1; reverse = true; } else if (shapeindex === "auto") { shapeindices[i] = 0; } else if (shapeindex === "reverse") { shapeindices[i] = -1; } if (sb.closed !== eb.closed) { //if one is closed and one isn't, don't close either one otherwise the tweening will look weird (but remember, the beginning and final states will honor the actual values, so this only affects the inbetween state) sb.closed = eb.closed = false; } } } if (log) { _log("shapeindex:[" + shapeindices.join(",") + "]"); } return shapeindices; }, _pathfilter = function(a, shapeindex, map, precompile) { var start = _pathdatatobezier(a[0]), end = _pathdatatobezier(a[1]); if (!_equalizesegmentquantity(start, end, (shapeindex || shapeindex === 0) ? shapeindex : "auto", map)) { return; //malformed path data or null target } a[0] = _beziertopathdata(start); a[1] = _beziertopathdata(end); if (precompile === "log" || precompile === true) { _log('precompile:["' + a[0] + '","' + a[1] + '"]'); } }, _buildpathfilter = function(shapeindex, map, precompile) { return (map || precompile || shapeindex || shapeindex === 0) ? function(a) { _pathfilter(a, shapeindex, map, precompile); } : _pathfilter; }, _offsetpoints = function(text, offset) { if (!offset) { return text; } var a = text.match(_numbersexp) || [], l = a.length, s = "", inc, i, j; if (offset === "reverse") { i = l-1; inc = -2; } else { i = (((parseint(offset, 10) || 0) * 2 + 1) + l * 100) % l; inc = 2; } for (j = 0; j < l; j += 2) { s += a[i-1] + "," + a[i] + " "; i = (i + inc) % l; } return s; }, //adds a certain number of points while maintaining the polygon/polyline shape (so that the start/end values can have a matching quantity of points to animate). returns the revised string. _equalizepointquantity = function(a, quantity) { var tally = 0, x = parsefloat(a[0]), y = parsefloat(a[1]), s = x + "," + y + " ", max = 0.999999, newpointspersegment, i, l, j, factor, nextx, nexty; l = a.length; newpointspersegment = quantity * 0.5 / (l * 0.5 - 1); for (i = 0; i < l-2; i += 2) { tally += newpointspersegment; nextx = parsefloat(a[i+2]); nexty = parsefloat(a[i+3]); if (tally > max) { //compare with 0.99999 instead of 1 in order to prevent rounding errors factor = 1 / (math.floor(tally) + 1); j = 1; while (tally > max) { s += (x + (nextx - x) * factor * j).tofixed(2) + "," + (y + (nexty - y) * factor * j).tofixed(2) + " "; tally--; j++; } } s += nextx + "," + nexty + " "; x = nextx; y = nexty; } return s; }, _pointsfilter = function(a) { var startnums = a[0].match(_numbersexp) || [], endnums = a[1].match(_numbersexp) || [], dif = endnums.length - startnums.length; if (dif > 0) { a[0] = _equalizepointquantity(startnums, dif); } else { a[1] = _equalizepointquantity(endnums, -dif); } }, _buildpointsfilter = function(shapeindex) { return !isnan(shapeindex) ? function(a) { _pointsfilter(a); a[1] = _offsetpoints(a[1], parseint(shapeindex, 10)); } : _pointsfilter; }, _createpath = function(e, ignore) { var path = document.createelementns("http://www.w3.org/2000/svg", "path"), attr = array.prototype.slice.call(e.attributes), i = attr.length; ignore = "," + ignore + ","; while (--i > -1) { if (ignore.indexof("," + attr[i].nodename + ",") === -1) { path.setattributens(null, attr[i].nodename, attr[i].nodevalue); } } return path; }, _converttopath = function(e, swap) { var type = e.tagname.tolowercase(), circ = 0.552284749831, data, x, y, r, ry, path, rcirc, rycirc, points, w, h, x2, x3, x4, x5, x6, y2, y3, y4, y5, y6; if (type === "path" || !e.getbbox) { return e; } path = _createpath(e, "x,y,width,height,cx,cy,rx,ry,r,x1,x2,y1,y2,points"); if (type === "rect") { r = +e.getattribute("rx") || 0; ry = +e.getattribute("ry") || 0; x = +e.getattribute("x") || 0; y = +e.getattribute("y") || 0; w = (+e.getattribute("width") || 0) - r * 2; h = (+e.getattribute("height") || 0) - ry * 2; if (r || ry) { //if there are rounded corners, render cubic beziers x2 = x + r * (1 - circ); x3 = x + r; x4 = x3 + w; x5 = x4 + r * circ; x6 = x4 + r; y2 = y + ry * (1 - circ); y3 = y + ry; y4 = y3 + h; y5 = y4 + ry * circ; y6 = y4 + ry; data = "m" + x6 + "," + y3 + " v" + y4 + " c" + [x6, y5, x5, y6, x4, y6, x4 - (x4 - x3) / 3, y6, x3 + (x4 - x3) / 3, y6, x3, y6, x2, y6, x, y5, x, y4, x, y4 - (y4 - y3) / 3, x, y3 + (y4 - y3) / 3, x, y3, x, y2, x2, y, x3, y, x3 + (x4 - x3) / 3, y, x4 - (x4 - x3) / 3, y, x4, y, x5, y, x6, y2, x6, y3].join(",") + "z"; } else { data = "m" + (x + w) + "," + y + " v" + h + " h" + (-w) + " v" + (-h) + " h" + w + "z"; } } else if (type === "circle" || type === "ellipse") { if (type === "circle") { r = ry = +e.getattribute("r") || 0; rycirc = r * circ; } else { r = +e.getattribute("rx") || 0; ry = +e.getattribute("ry") || 0; rycirc = ry * circ; } x = +e.getattribute("cx") || 0; y = +e.getattribute("cy") || 0; rcirc = r * circ; data = "m" + (x+r) + "," + y + " c" + [x+r, y + rycirc, x + rcirc, y + ry, x, y + ry, x - rcirc, y + ry, x - r, y + rycirc, x - r, y, x - r, y - rycirc, x - rcirc, y - ry, x, y - ry, x + rcirc, y - ry, x + r, y - rycirc, x + r, y].join(",") + "z"; } else if (type === "line") { data = "m" + e.getattribute("x1") + "," + e.getattribute("y1") + " l" + e.getattribute("x2") + "," + e.getattribute("y2"); } else if (type === "polyline" || type === "polygon") { points = (e.getattribute("points") + "").match(_numbersexp) || []; x = points.shift(); y = points.shift(); data = "m" + x + "," + y + " l" + points.join(","); if (type === "polygon") { data += "," + x + "," + y + "z"; } } path.setattribute("d", data); if (swap && e.parentnode) { e.parentnode.insertbefore(path, e); e.parentnode.removechild(e); } return path; }, _parseshape = function(shape, forcepath, target) { var isstring = typeof(shape) === "string", e, type; if (!isstring || (shape.match(_numbersexp) || []).length < 3) { e = isstring ? tweenlite.selector(shape) : [shape]; if (e && e[0]) { e = e[0]; type = e.nodename.touppercase(); if (forcepath && type !== "path") { //if we were passed an element (or selector text for an element) that isn't a path, convert it. e = _converttopath(e, false); type = "path"; } shape = e.getattribute(type === "path" ? "d" : "points") || ""; if (e === target) { //if the shape matches the target element, the user wants to revert to the original which should have been stored in the data-original attribute shape = e.getattributens(null, "data-original") || shape; } } else { _log("warning: invalid morph to: " + shape); shape = false; } } return shape; }, _morphmessage = "use morphsvgplugin.converttopath(elementorselectortext) to convert to a path before morphing.", morphsvgplugin = _gsscope._gsdefine.plugin({ propname: "morphsvg", api: 2, global: true, version: "0.8.1", //called when the tween renders for the first time. this is where initial values should be recorded and any setup routines should run. init: function(target, value, tween) { var type, p, pt, shape, ispoly; if (typeof(target.setattribute) !== "function") { return false; } type = target.nodename.touppercase(); ispoly = (type === "polyline" || type === "polygon"); if (type !== "path" && !ispoly) { _log("warning: cannot morph a <" + type + "> svg element. " + _morphmessage); return false; } p = (type === "path") ? "d" : "points"; if (typeof(value) === "string" || value.getbbox || value[0]) { value = {shape:value}; } shape = _parseshape(value.shape || value.d || value.points || "", (p === "d"), target); if (ispoly && _commands.test(shape)) { _log("warning: a <" + type + "> cannot accept path data. " + _morphmessage); return false; } if (shape) { this._target = target; if (!target.getattributens(null, "data-original")) { target.setattributens(null, "data-original", target.getattribute(p)); //record the original state in a data-original attribute so that we can revert to it later. } pt = this._addtween(target, "setattribute", target.getattribute(p) + "", shape + "", "morphsvg", false, p, (typeof(value.precompile) === "object") ? function(a) {a[0] = value.precompile[0]; a[1] = value.precompile[1];} : (p === "d") ? _buildpathfilter(value.shapeindex, value.map || morphsvgplugin.defaultmap, value.precompile) : _buildpointsfilter(value.shapeindex)); if (pt) { this._overwriteprops.push("morphsvg"); pt.end = shape; pt.endprop = p; } } return true; }, set: function(ratio) { var pt; this._super.setratio.call(this, ratio); if (ratio === 1) { pt = this._firstpt; while (pt) { if (pt.end) { this._target.setattribute(pt.endprop, pt.end); //make sure the end value is exactly as specified (in case we had to add fabricated points during the tween) } pt = pt._next; } } } }); morphsvgplugin.pathfilter = _pathfilter; morphsvgplugin.pointsfilter = _pointsfilter; morphsvgplugin.subdividerawbezier = _subdividebezier; morphsvgplugin.defaultmap = "size"; morphsvgplugin.pathdatatorawbezier = function(data) { return _pathdatatobezier(_parseshape(data, true)); }; morphsvgplugin.equalizesegmentquantity = _equalizesegmentquantity; morphsvgplugin.converttopath = function(targets, swap) { if (typeof(targets) === "string") { targets = tweenlite.selector(targets); } var a = (!targets || targets.length === 0) ? [] : (targets.length && targets[0] && targets[0].nodetype) ? array.prototype.slice.call(targets, 0) : [targets], i = a.length; while (--i > -1) { a[i] = _converttopath(a[i], (swap !== false)); } return a; }; morphsvgplugin.pathdatatobezier = function(data, vars) { //converts svg path data into an array of {x, y} objects that can be plugged directly into a bezier tween. you can optionally pass in a 2d matrix like [a, b, c, d, tx, ty] containing numbers that should transform each point. var bezier = _pathdatatobezier(_parseshape(data, true))[0] || [], prefix = 0, a, i, l, matrix, offsetx, offsety, bbox, e; vars = vars || {}; e = vars.align || vars.relative; matrix = vars.matrix || [1,0,0,1,0,0]; offsetx = vars.offsetx || 0; offsety = vars.offsety || 0; if (e === "relative" || e === true) { offsetx -= bezier[0] * matrix[0] + bezier[1] * matrix[2]; offsety -= bezier[0] * matrix[1] + bezier[1] * matrix[3]; prefix = "+="; } else { offsetx += matrix[4]; offsety += matrix[5]; if (e) { e = (typeof(e) === "string") ? tweenlite.selector(e) : [e]; if (e && e[0]) { bbox = e[0].getbbox() || {x:0, y:0}; offsetx -= bbox.x; offsety -= bbox.y; } } } a = []; l = bezier.length; if (matrix) { for (i = 0; i < l; i+=2) { a.push({x:prefix + (bezier[i] * matrix[0] + bezier[i+1] * matrix[2] + offsetx), y:prefix + (bezier[i] * matrix[1] + bezier[i+1] * matrix[3] + offsety)}); } } else { for (i = 0; i < l; i+=2) { a.push({x:prefix + (bezier[i] + offsetx), y:prefix + (bezier[i+1] + offsety)}); } } return a; }; }); if (_gsscope._gsdefine) { _gsscope._gsqueue.pop()(); }