258 lines
10 KiB
JavaScript
258 lines
10 KiB
JavaScript
/*!
|
|
* MotionPathHelper 3.14.2
|
|
* 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 */
|
|
|
|
import PathEditor from "./utils/PathEditor.js";
|
|
|
|
let gsap, _win, _doc, _docEl, _body, MotionPathPlugin, _arrayToRawPath, _rawPathToString, _context,
|
|
_bonusValidated = 1, //<name>MotionPathHelper</name>
|
|
_selectorExp = /(^[#\.][a-z]|[a-y][a-z])/i,
|
|
_isString = value => typeof(value) === "string",
|
|
_createElement = (type, ns) => {
|
|
let e = _doc.createElementNS ? _doc.createElementNS((ns || "http://www.w3.org/1999/xhtml").replace(/^https/, "http"), type) : _doc.createElement(type); //some servers swap in https for http in the namespace which can break things, making "style" inaccessible.
|
|
return e.style ? e : _doc.createElement(type); //some environments won't allow access to the element's style when created with a namespace in which case we default to the standard createElement() to work around the issue. Also note that when GSAP is embedded directly inside an SVG file, createElement() won't allow access to the style object in Firefox (see https://gsap.com/forums/topic/20215-problem-using-tweenmax-in-standalone-self-containing-svg-file-err-cannot-set-property-csstext-of-undefined/).
|
|
},
|
|
_getPositionOnPage = target => {
|
|
let bounds = target.getBoundingClientRect(),
|
|
windowOffsetY = _docEl.clientTop - (_win.pageYOffset || _docEl.scrollTop || _body.scrollTop || 0),
|
|
windowOffsetX = _docEl.clientLeft - (_win.pageXOffset || _docEl.scrollLeft || _body.scrollLeft || 0);
|
|
return {left:bounds.left + windowOffsetX, top:bounds.top + windowOffsetY, right: bounds.right + windowOffsetX, bottom: bounds.bottom + windowOffsetY};
|
|
},
|
|
_getInitialPath = (x, y) => {
|
|
let coordinates = [0,31,8,58,24,75,40,90,69,100,100,100],
|
|
i;
|
|
for (i = 0; i < coordinates.length; i+=2) {
|
|
coordinates[i] += x;
|
|
coordinates[i+1] += y;
|
|
}
|
|
return "M" + x + "," + y + "C" + coordinates.join(",");
|
|
},
|
|
_getGlobalTime = animation => {
|
|
let time = animation.totalTime();
|
|
while (animation) {
|
|
time = animation.startTime() + time / (animation.timeScale() || 1);
|
|
animation = animation.parent;
|
|
}
|
|
return time;
|
|
},
|
|
_copyElement,
|
|
_initCopyToClipboard = () => {
|
|
_copyElement = _createElement("textarea");
|
|
_copyElement.style.display = "none";
|
|
_body.appendChild(_copyElement);
|
|
},
|
|
_parsePath = (path, target, vars) => (_isString(path) && _selectorExp.test(path)) ? _doc.querySelector(path) : Array.isArray(path) ? _rawPathToString(_arrayToRawPath([{x:gsap.getProperty(target, "x"), y:gsap.getProperty(target, "y")}, ...path], vars)) : (_isString(path) || path && (path.tagName + "").toLowerCase() === "path") ? path : 0,
|
|
_addCopyToClipboard = (target, getter, onComplete) => {
|
|
target.addEventListener('click', e => {
|
|
if (e.target._gsHelper) {
|
|
let c = getter(e.target);
|
|
_copyElement.value = c;
|
|
if (c && _copyElement.select) {
|
|
console.log(c);
|
|
_copyElement.style.display = "block";
|
|
_copyElement.select();
|
|
try {
|
|
_doc.execCommand('copy');
|
|
_copyElement.blur();
|
|
onComplete && onComplete(target);
|
|
} catch (err) {
|
|
console.warn("Copy didn't work; this browser doesn't permit that.");
|
|
}
|
|
_copyElement.style.display = "none";
|
|
}
|
|
}
|
|
});
|
|
},
|
|
_identityMatrixObject = {matrix:{a:1, b:0, c:0, d:1, e:0, f:0}},
|
|
_getConsolidatedMatrix = target => (target.transform.baseVal.consolidate() || _identityMatrixObject).matrix,
|
|
_findMotionPathTween = target => {
|
|
let tweens = gsap.getTweensOf(target),
|
|
i = 0;
|
|
for (; i < tweens.length; i++) {
|
|
if (tweens[i].vars.motionPath) {
|
|
return tweens[i];
|
|
} else if (tweens[i].timeline) {
|
|
tweens.push(...tweens[i].timeline.getChildren());
|
|
}
|
|
}
|
|
},
|
|
_initCore = (core, required) => {
|
|
let message = "Please gsap.registerPlugin(MotionPathPlugin)";
|
|
_win = window;
|
|
gsap = gsap || core || _win.gsap || console.warn(message);
|
|
gsap && PathEditor.register(gsap);
|
|
_doc = document;
|
|
_body = _doc.body;
|
|
_docEl = _doc.documentElement;
|
|
if (gsap) {
|
|
MotionPathPlugin = gsap.plugins.motionPath;
|
|
MotionPathHelper.PathEditor = PathEditor;
|
|
_context = gsap.core.context || function() {};
|
|
}
|
|
if (!MotionPathPlugin) {
|
|
(required === true) && console.warn(message);
|
|
} else {
|
|
_initCopyToClipboard();
|
|
_arrayToRawPath = MotionPathPlugin.arrayToRawPath;
|
|
_rawPathToString = MotionPathPlugin.rawPathToString;
|
|
}
|
|
};
|
|
|
|
export class MotionPathHelper {
|
|
|
|
constructor(targetOrTween, vars = {}) {
|
|
if (!MotionPathPlugin) {
|
|
_initCore(vars.gsap, 1);
|
|
}
|
|
let copyButton = _createElement("div"),
|
|
self = this,
|
|
offset = {x:0, y:0},
|
|
target, path, isSVG, startX, startY, position, svg, animation, svgNamespace, temp, matrix, refreshPath, animationToScrub, createdSVG;
|
|
if (targetOrTween instanceof gsap.core.Tween) {
|
|
animation = targetOrTween;
|
|
target = animation.targets()[0];
|
|
} else {
|
|
target = gsap.utils.toArray(targetOrTween)[0];
|
|
animation = _findMotionPathTween(target);
|
|
}
|
|
path = _parsePath(vars.path, target, vars);
|
|
this.offset = offset;
|
|
position = _getPositionOnPage(target);
|
|
startX = parseFloat(gsap.getProperty(target, "x", "px"));
|
|
startY = parseFloat(gsap.getProperty(target, "y", "px"));
|
|
isSVG = (target.getCTM && target.tagName.toLowerCase() !== "svg");
|
|
if (animation && !path) {
|
|
path = _parsePath(animation.vars.motionPath.path || animation.vars.motionPath, target, animation.vars.motionPath);
|
|
}
|
|
copyButton.setAttribute("class", "copy-motion-path");
|
|
copyButton.style.cssText = "border-radius:8px; background-color:rgba(85, 85, 85, 0.7); color:#fff; cursor:pointer; padding:6px 12px; font-family:Signika Negative, Arial, sans-serif; position:fixed; left:50%; transform:translate(-50%, 0); font-size:19px; bottom:10px";
|
|
copyButton.innerText = "COPY MOTION PATH";
|
|
copyButton._gsHelper = self;
|
|
(gsap.utils.toArray(vars.container)[0] || _body).appendChild(copyButton);
|
|
_addCopyToClipboard(copyButton, () => self.getString(), () => gsap.fromTo(copyButton, {backgroundColor:"white"}, {duration:0.5, backgroundColor:"rgba(85, 85, 85, 0.6)"}));
|
|
svg = path && path.ownerSVGElement;
|
|
if (!svg) {
|
|
svgNamespace = (isSVG && target.ownerSVGElement && target.ownerSVGElement.getAttribute("xmlns")) || "http://www.w3.org/2000/svg";
|
|
if (isSVG) {
|
|
svg = target.ownerSVGElement;
|
|
temp = target.getBBox();
|
|
matrix = _getConsolidatedMatrix(target);
|
|
startX = matrix.e;
|
|
startY = matrix.f;
|
|
offset.x = temp.x;
|
|
offset.y = temp.y;
|
|
} else {
|
|
svg = _createElement("svg", svgNamespace);
|
|
createdSVG = true;
|
|
_body.appendChild(svg);
|
|
svg.setAttribute("viewBox", "0 0 100 100");
|
|
svg.setAttribute("class", "motion-path-helper");
|
|
svg.style.cssText = "overflow:visible; background-color: transparent; position:absolute; z-index:5000; width:100px; height:100px; top:" + (position.top - startY) + "px; left:" + (position.left - startX) + "px;";
|
|
}
|
|
temp = _isString(path) && !_selectorExp.test(path) ? path : _getInitialPath(startX, startY);
|
|
path = _createElement("path", svgNamespace);
|
|
path.setAttribute("d", temp);
|
|
path.setAttribute("vector-effect", "non-scaling-stroke");
|
|
path.style.cssText = "fill:transparent; stroke-width:" + (vars.pathWidth || 3) + "; stroke:" + (vars.pathColor || "#555") + "; opacity:" + (vars.pathOpacity || 0.6);
|
|
svg.appendChild(path);
|
|
} else {
|
|
vars.pathColor && gsap.set(path, {stroke: vars.pathColor});
|
|
vars.pathWidth && gsap.set(path, {strokeWidth: vars.pathWidth});
|
|
vars.pathOpacity && gsap.set(path, {opacity: vars.pathOpacity});
|
|
}
|
|
|
|
if (offset.x || offset.y) {
|
|
gsap.set(path, {x:offset.x, y:offset.y});
|
|
}
|
|
|
|
if (!("selected" in vars)) {
|
|
vars.selected = true;
|
|
}
|
|
if (!("anchorSnap" in vars)) {
|
|
vars.anchorSnap = p => {
|
|
if (p.x * p.x + p.y * p.y < 16) {
|
|
p.x = p.y = 0;
|
|
}
|
|
};
|
|
}
|
|
|
|
animationToScrub = animation && animation.parent && animation.parent.data === "nested" ? animation.parent.parent : animation;
|
|
|
|
vars.onPress = () => {
|
|
animationToScrub.pause(0);
|
|
};
|
|
|
|
refreshPath = () => {
|
|
//let m = _getConsolidatedMatrix(path);
|
|
//animation.vars.motionPath.offsetX = m.e - offset.x;
|
|
//animation.vars.motionPath.offsetY = m.f - offset.y;
|
|
if (this.editor._anchors.length < 2) {
|
|
console.warn("A motion path must have at least two anchors.");
|
|
} else {
|
|
animation.invalidate();
|
|
animationToScrub.restart();
|
|
}
|
|
};
|
|
vars.onRelease = vars.onDeleteAnchor = refreshPath;
|
|
|
|
this.editor = PathEditor.create(path, vars);
|
|
if (vars.center) {
|
|
gsap.set(target, {transformOrigin:"50% 50%", xPercent:-50, yPercent:-50});
|
|
}
|
|
if (animation) {
|
|
if (animation.vars.motionPath.path) {
|
|
animation.vars.motionPath.path = path;
|
|
} else {
|
|
animation.vars.motionPath = {path:path};
|
|
}
|
|
if (animationToScrub.parent !== gsap.globalTimeline) {
|
|
gsap.globalTimeline.add(animationToScrub, _getGlobalTime(animationToScrub) - animationToScrub.delay());
|
|
}
|
|
animationToScrub.repeat(-1).repeatDelay(1);
|
|
|
|
} else {
|
|
animation = animationToScrub = gsap.to(target, {
|
|
motionPath: {
|
|
path: path,
|
|
start: vars.start || 0,
|
|
end: ("end" in vars) ? vars.end : 1,
|
|
autoRotate: ("autoRotate" in vars) ? vars.autoRotate : false,
|
|
align: path,
|
|
alignOrigin: vars.alignOrigin
|
|
},
|
|
duration: vars.duration || 5,
|
|
ease: vars.ease || "power1.inOut",
|
|
repeat:-1,
|
|
repeatDelay:1,
|
|
paused:!vars.path
|
|
});
|
|
}
|
|
this.animation = animation;
|
|
|
|
_context(this);
|
|
this.kill = this.revert = () => {
|
|
this.editor.kill();
|
|
copyButton.parentNode && copyButton.parentNode.removeChild(copyButton);
|
|
createdSVG && svg.parentNode && svg.parentNode.removeChild(svg);
|
|
animationToScrub && animationToScrub.revert();
|
|
}
|
|
}
|
|
|
|
getString() {
|
|
return this.editor.getString(true, -this.offset.x, -this.offset.y);
|
|
}
|
|
|
|
}
|
|
|
|
MotionPathHelper.register = _initCore;
|
|
MotionPathHelper.create = (target, vars) => new MotionPathHelper(target, vars);
|
|
MotionPathHelper.editPath = (path, vars) => PathEditor.create(path, vars);
|
|
MotionPathHelper.version = "3.14.2";
|
|
|
|
export { MotionPathHelper as default }; |