UNPKG

gsap

Version:

GSAP is a framework-agnostic JavaScript animation library that turns developers into animation superheroes. Build high-performance animations that work in **every** major browser. Animate CSS, SVG, canvas, React, Vue, WebGL, colors, strings, motion paths,

314 lines (273 loc) 10.7 kB
/*! * DrawSVGPlugin 3.13.0 * https://gsap.com * * @license Copyright 2008-2025, GreenSock. All rights reserved. * Subject to the terms at https://gsap.com/standard-license * @author: Jack Doyle, jack@greensock.com */ /* eslint-disable */ var gsap, _toArray, _doc, _win, _isEdge, _coreInitted, _warned, _getStyleSaver, _reverting, _windowExists = function _windowExists() { return typeof window !== "undefined"; }, _getGSAP = function _getGSAP() { return gsap || _windowExists() && (gsap = window.gsap) && gsap.registerPlugin && gsap; }, _numExp = /[-+=\.]*\d+[\.e\-\+]*\d*[e\-\+]*\d*/gi, //finds any numbers, including ones that start with += or -=, negative numbers, and ones in scientific notation like 1e-8. _types = { rect: ["width", "height"], circle: ["r", "r"], ellipse: ["rx", "ry"], line: ["x2", "y2"] }, _round = function _round(value) { return Math.round(value * 10000) / 10000; }, _parseNum = function _parseNum(value) { return parseFloat(value) || 0; }, _parseSingleVal = function _parseSingleVal(value, length) { var num = _parseNum(value); return ~value.indexOf("%") ? num / 100 * length : num; }, _getAttributeAsNumber = function _getAttributeAsNumber(target, attr) { return _parseNum(target.getAttribute(attr)); }, _sqrt = Math.sqrt, _getDistance = function _getDistance(x1, y1, x2, y2, scaleX, scaleY) { return _sqrt(Math.pow((_parseNum(x2) - _parseNum(x1)) * scaleX, 2) + Math.pow((_parseNum(y2) - _parseNum(y1)) * scaleY, 2)); }, _warn = function _warn(message) { return console.warn(message); }, _hasNonScalingStroke = function _hasNonScalingStroke(target) { return target.getAttribute("vector-effect") === "non-scaling-stroke"; }, _bonusValidated = 1, //<name>DrawSVGPlugin</name> //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] _parse = function _parse(value, length, defaultStart) { var i = value.indexOf(" "), s, e; if (i < 0) { s = defaultStart !== undefined ? defaultStart + "" : value; e = value; } else { s = value.substr(0, i); e = value.substr(i + 1); } s = _parseSingleVal(s, length); e = _parseSingleVal(e, length); return s > e ? [e, s] : [s, e]; }, _getLength = function _getLength(target) { target = _toArray(target)[0]; if (!target) { return 0; } var type = target.tagName.toLowerCase(), style = target.style, scaleX = 1, scaleY = 1, length, bbox, points, prevPoint, i, rx, ry; if (_hasNonScalingStroke(target)) { //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 = target.getScreenCTM(); scaleX = _sqrt(scaleY.a * scaleY.a + scaleY.b * scaleY.b); scaleY = _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 = target.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. _warn("Some browsers won't measure invisible elements (like display:none or masks inside defs)."); } var _ref = bbox || { x: 0, y: 0, width: 0, height: 0 }, x = _ref.x, y = _ref.y, width = _ref.width, height = _ref.height; if ((!bbox || !width && !height) && _types[type]) { //if the element isn't visible, try to discern width/height using its attributes. width = _getAttributeAsNumber(target, _types[type][0]); height = _getAttributeAsNumber(target, _types[type][1]); if (type !== "rect" && type !== "line") { //double the radius for circles and ellipses width *= 2; height *= 2; } if (type === "line") { x = _getAttributeAsNumber(target, "x1"); y = _getAttributeAsNumber(target, "y1"); width = Math.abs(width - x); height = Math.abs(height - y); } } if (type === "path") { prevPoint = style.strokeDasharray; style.strokeDasharray = "none"; length = target.getTotalLength() || 0; _round(scaleX) !== _round(scaleY) && !_warned && (_warned = 1) && _warn("Warning: <path> length cannot be measured when vector-effect is non-scaling-stroke and the element isn't proportionally scaled."); length *= (scaleX + scaleY) / 2; style.strokeDasharray = prevPoint; } else if (type === "rect") { length = width * 2 * scaleX + height * 2 * scaleY; } else if (type === "line") { length = _getDistance(x, y, x + width, y + height, scaleX, scaleY); } else if (type === "polyline" || type === "polygon") { points = target.getAttribute("points").match(_numExp) || []; 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 = width / 2 * scaleX; ry = height / 2 * scaleY; length = Math.PI * (3 * (rx + ry) - _sqrt((3 * rx + ry) * (rx + 3 * ry))); } return length || 0; }, _getPosition = function _getPosition(target, length) { target = _toArray(target)[0]; if (!target) { return [0, 0]; } length || (length = _getLength(target) + 1); var cs = _win.getComputedStyle(target), dash = cs.strokeDasharray || "", offset = _parseNum(cs.strokeDashoffset), i = dash.indexOf(","); i < 0 && (i = dash.indexOf(" ")); dash = i < 0 ? length : _parseNum(dash.substr(0, i)); dash > length && (dash = length); return [-offset || 0, dash - offset || 0]; }, _initCore = function _initCore() { if (_windowExists()) { _doc = document; _win = window; _coreInitted = gsap = _getGSAP(); _toArray = gsap.utils.toArray; _getStyleSaver = gsap.core.getStyleSaver; _reverting = gsap.core.reverting || function () {}; _isEdge = ((_win.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) } }; export var DrawSVGPlugin = { version: "3.13.0", name: "drawSVG", register: function register(core) { gsap = core; _initCore(); }, init: function init(target, value, tween, index, targets) { if (!target.getBBox) { return false; } _coreInitted || _initCore(); var length = _getLength(target), start, end, cs; this.styles = _getStyleSaver && _getStyleSaver(target, "strokeDashoffset,strokeDasharray,strokeMiterlimit"); this.tween = tween; this._style = target.style; this._target = target; if (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 = _round(length); this._dash = _round(start[1] - start[0]); //some browsers render artifacts if dash is 0, so we use a very small number in that case. this._offset = _round(-start[0]); this._dashPT = this.add(this, "_dash", this._dash, _round(end[1] - end[0]), 0, 0, 0, 0, 0, 1); this._offsetPT = this.add(this, "_offset", this._offset, _round(-end[0]), 0, 0, 0, 0, 0, 1); 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 = _win.getComputedStyle(target); if (cs.strokeLinecap !== cs.strokeLinejoin) { end = _parseNum(cs.strokeMiterlimit); this.add(target.style, "strokeMiterlimit", end, end + 0.01); } } this._live = _hasNonScalingStroke(target) || ~(value + "").indexOf("live"); this._nowrap = ~(value + "").indexOf("nowrap"); this._props.push("drawSVG"); return _bonusValidated; }, render: function render(ratio, data) { if (data.tween._time || !_reverting()) { var pt = data._pt, style = data._style, length, lengthRatio, dash, offset; if (pt) { //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 (data._live) { length = _getLength(data._target); if (length !== data._length) { lengthRatio = length / data._length; data._length = length; if (data._offsetPT) { data._offsetPT.s *= lengthRatio; data._offsetPT.c *= lengthRatio; } if (data._dashPT) { data._dashPT.s *= lengthRatio; data._dashPT.c *= lengthRatio; } else { data._dash *= lengthRatio; } } } while (pt) { pt.r(ratio, pt.d); pt = pt._next; } dash = data._dash || ratio && ratio !== 1 && 0.0001 || 0; // only let it be zero if it's at the start or end of the tween. length = data._length - dash + 0.1; offset = data._offset; dash && offset && dash + Math.abs(offset % data._length) > data._length - 0.05 && (offset += offset < 0 ? 0.005 : -0.005) && (length += 0.005); style.strokeDashoffset = dash ? offset : offset + 0.001; style.strokeDasharray = length < 0.1 ? "none" : dash ? dash + "px," + (data._nowrap ? 999999 : length) + "px" : "0px, 999999px"; } } else { data.styles.revert(); } }, getLength: _getLength, getPosition: _getPosition }; _getGSAP() && gsap.registerPlugin(DrawSVGPlugin); export { DrawSVGPlugin as default };