/*! * VERSION: 0.2.1 * DATE: 2019-02-07 * UPDATES AND DOCS AT: http://greensock.com * * @license Copyright (c) 2008-2019, GreenSock. All rights reserved. * DrawSVGPlugin 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 */ /* eslint-disable */ import { _gsScope } from "gsap/TweenLite.js"; var _doc = _gsScope.document, _computedStyleScope = (typeof(window) !== "undefined" ? window : _doc.defaultView || {getComputedStyle:function() {}}), _getComputedStyle = function(e) { return _computedStyleScope.getComputedStyle(e); //to avoid errors in Microsoft Edge, we need to call getComputedStyle() from a specific scope, typically window. }, _numbersExp = /(?:(-|-=|\+=)?\d*\.?\d*(?:e[\-+]?\d+)?)[0-9]/ig, _isEdge = (((_gsScope.navigator || {}).userAgent || "").indexOf("Edge") !== -1), //Microsoft Edge has a bug that causes it not to redraw the path correctly if the stroke-linecap is anything other than "butt" (like "round") and it doesn't match the stroke-linejoin. A way to trigger it is to change the stroke-miterlimit, so we'll only do that if/when we have to (to maximize performance) _types = {rect:["width","height"], circle:["r","r"], ellipse:["rx","ry"], line:["x2","y2"]}, DrawSVGPlugin; function getDistance(x1, y1, x2, y2, scaleX, scaleY) { x2 = (parseFloat(x2 || 0) - parseFloat(x1 || 0)) * scaleX; y2 = (parseFloat(y2 || 0) - parseFloat(y1 || 0)) * scaleY; return Math.sqrt(x2 * x2 + y2 * y2); } function unwrap(element) { if (typeof(element) === "string" || !element.nodeType) { element = _gsScope.TweenLite.selector(element); if (element.length) { element = element[0]; } } return element; } //accepts values like "100%" or "20% 80%" or "20 50" and parses it into an absolute start and end position on the line/stroke based on its length. Returns an an array with the start and end values, like [0, 243] function parse(value, length, defaultStart) { var i = value.indexOf(" "), s, e; if (i === -1) { s = defaultStart !== undefined ? defaultStart + "" : value; e = value; } else { s = value.substr(0, i); e = value.substr(i+1); } s = (s.indexOf("%") !== -1) ? (parseFloat(s) / 100) * length : parseFloat(s); e = (e.indexOf("%") !== -1) ? (parseFloat(e) / 100) * length : parseFloat(e); return (s > e) ? [e, s] : [s, e]; } function getLength(element) { if (!element) { return 0; } element = unwrap(element); var type = element.tagName.toLowerCase(), scaleX = 1, scaleY = 1, length, bbox, points, prevPoint, i, rx, ry; if (element.getAttribute("vector-effect") === "non-scaling-stroke") { //non-scaling-stroke basically scales the shape and then strokes it at the screen-level (after transforms), thus we need to adjust the length accordingly. scaleY = element.getScreenCTM(); scaleX = Math.sqrt(scaleY.a * scaleY.a + scaleY.b * scaleY.b); scaleY = Math.sqrt(scaleY.d * scaleY.d + scaleY.c * scaleY.c); } try { //IE bug: calling .getTotalLength() locks the repaint area of the stroke to whatever its current dimensions are on that frame/tick. To work around that, we must call getBBox() to force IE to recalculate things. bbox = element.getBBox(); //solely for fixing bug in IE - we don't actually use the bbox. } catch (e) { //firefox has a bug that throws an error if the element isn't visible. console.log("Error: Some browsers like Firefox won't report measurements of invisible elements (like display:none or masks inside defs)."); } if ((!bbox || (!bbox.width && !bbox.height)) && _types[type]) { //if the element isn't visible, try to discern width/height using its attributes. bbox = { width: parseFloat( element.getAttribute(_types[type][0]) ), height: parseFloat( element.getAttribute(_types[type][1]) ) }; if (type !== "rect" && type !== "line") { //double the radius for circles and ellipses bbox.width *= 2; bbox.height *= 2; } if (type === "line") { bbox.x = parseFloat( element.getAttribute("x1") ); bbox.y = parseFloat( element.getAttribute("y1") ); bbox.width = Math.abs(bbox.width - bbox.x); bbox.height = Math.abs(bbox.height - bbox.y); } } if (type === "path") { prevPoint = element.style.strokeDasharray; element.style.strokeDasharray = "none"; length = element.getTotalLength() || 0; if (scaleX !== scaleY) { console.log("Warning: length cannot be measured accurately when vector-effect is non-scaling-stroke and the element isn't proportionally scaled."); } length *= (scaleX + scaleY) / 2; element.style.strokeDasharray = prevPoint; } else if (type === "rect") { length = bbox.width * 2 * scaleX + bbox.height * 2 * scaleY; } else if (type === "line") { length = getDistance(bbox.x, bbox.y, bbox.x + bbox.width, bbox.y + bbox.height, scaleX, scaleY); } else if (type === "polyline" || type === "polygon") { points = element.getAttribute("points").match(_numbersExp) || []; if (type === "polygon") { points.push(points[0], points[1]); } length = 0; for (i = 2; i < points.length; i+=2) { length += getDistance(points[i-2], points[i-1], points[i], points[i+1], scaleX, scaleY) || 0; } } else if (type === "circle" || type === "ellipse") { rx = (bbox.width / 2) * scaleX; ry = (bbox.height / 2) * scaleY; length = Math.PI * ( 3 * (rx + ry) - Math.sqrt((3 * rx + ry) * (rx + 3 * ry)) ); } return length || 0; } function getPosition(element, length) { if (!element) { return [0, 0]; } element = unwrap(element); length = length || (getLength(element) + 1); var cs = _getComputedStyle(element), dash = cs.strokeDasharray || "", offset = parseFloat(cs.strokeDashoffset), i = dash.indexOf(","); if (i < 0) { i = dash.indexOf(" "); } dash = (i < 0) ? length : parseFloat(dash.substr(0, i)) || 0.00001; if (dash > length) { dash = length; } return [Math.max(0, -offset), Math.max(0, dash - offset)]; } DrawSVGPlugin = _gsScope._gsDefine.plugin({ propName: "drawSVG", API: 2, version: "0.2.1", global: true, overwriteProps: ["drawSVG"], init: function(target, value, tween, index) { if (!target.getBBox) { return false; } var length = getLength(target) + 1, start, end, overage, cs; this._style = target.style; this._target = target; if (typeof(value) === "function") { value = value(index, target); } if (value === true || value === "true") { value = "0 100%"; } else if (!value) { value = "0 0"; } else if ((value + "").indexOf(" ") === -1) { value = "0 " + value; } start = getPosition(target, length); end = parse(value, length, start[0]); this._length = length + 10; if (start[0] === 0 && end[0] === 0) { overage = Math.max(0.00001, end[1] - length); //allow people to go past the end, like values of 105% because for some paths, Firefox doesn't return an accurate getTotalLength(), so it could end up coming up short. this._dash = length + overage; this._offset = length - start[1] + overage; this._offsetPT = this._addTween(this, "_offset", this._offset, length - end[1] + overage, "drawSVG"); } else { this._dash = (start[1] - start[0]) || 0.000001; //some browsers render artifacts if dash is 0, so we use a very small number in that case. this._offset = -start[0]; this._dashPT = this._addTween(this, "_dash", this._dash, (end[1] - end[0]) || 0.00001, "drawSVG"); this._offsetPT = this._addTween(this, "_offset", this._offset, -end[0], "drawSVG"); } if (_isEdge) { //to work around a bug in Microsoft Edge, animate the stroke-miterlimit by 0.0001 just to trigger the repaint (unnecessary if it's "round" and stroke-linejoin is also "round"). Imperceptible, relatively high-performance, and effective. Another option was to set the "d" attribute to its current value on every tick, but that seems like it'd be much less performant. cs = _getComputedStyle(target); if (cs.strokeLinecap !== cs.strokeLinejoin) { end = parseFloat(cs.strokeMiterlimit); this._addTween(target.style, "strokeMiterlimit", end, end + 0.0001, "strokeMiterlimit"); } } this._live = (target.getAttribute("vector-effect") === "non-scaling-stroke" || (value + "").indexOf("live") !== -1); return true; }, //called each time the values should be updated, and the ratio gets passed as the only parameter (typically it's a value between 0 and 1, but it can exceed those when using an ease like Elastic.easeOut or Back.easeOut, etc.) set: function(ratio) { if (this._firstPT) { //when the element has vector-effect="non-scaling-stroke" and the SVG is resized (like on a window resize), it actually changes the length of the stroke! So we must sense that and make the proper adjustments. if (this._live) { var length = getLength(this._target) + 11, lengthRatio; if (length !== this._length) { lengthRatio = length / this._length; this._length = length; this._offsetPT.s *= lengthRatio; this._offsetPT.c *= lengthRatio; if (this._dashPT) { this._dashPT.s *= lengthRatio; this._dashPT.c *= lengthRatio; } else { this._dash *= lengthRatio; } } } this._super.setRatio.call(this, ratio); this._style.strokeDashoffset = this._offset; if (ratio === 1 || ratio === 0) { this._style.strokeDasharray = (this._offset < 0.001 && this._length - this._dash <= 10) ? "none" : (this._offset === this._dash) ? "0px, 999999px" : this._dash + "px," + this._length + "px"; } else { this._style.strokeDasharray = this._dash + "px," + this._length + "px"; } } } }); DrawSVGPlugin.getLength = getLength; DrawSVGPlugin.getPosition = getPosition; export { DrawSVGPlugin, DrawSVGPlugin as default };