230 lines
9.9 KiB
JavaScript
230 lines
9.9 KiB
JavaScript
/*!
|
|
* 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 <path>.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: <path> 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" <path> 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 }; |