/*! * VERSION: 0.17.0 * DATE: 2019-02-12 * UPDATES AND DOCS AT: http://greensock.com * * Requires TweenLite and CSSPlugin version 1.17.0 or later (TweenMax contains both TweenLite and CSSPlugin). ThrowPropsPlugin is required for momentum-based continuation of movement after the mouse/touch is released (ThrowPropsPlugin is a membership benefit of Club GreenSock - http://greensock.com/club/). * * @license Copyright (c) 2008-2019, GreenSock. All rights reserved. * This work is subject to the terms at http://greensock.com/standard-license or for * Club GreenSock members, the software agreement that was issued with your membership. * * @author: Jack Doyle, jack@greensock.com */ /* eslint-disable */ 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"; _gsScope._gsDefine("utils.Draggable", ["events.EventDispatcher","TweenLite","plugins.CSSPlugin"], function(EventDispatcher, TweenLite, CSSPlugin) { var _tempVarsXY = {css:{}, data:"_draggable"}, //speed optimization - we reuse the same vars object for x/y TweenLite.set() calls to minimize garbage collection tasks and improve performance. _tempVarsX = {css:{}, data:"_draggable"}, _tempVarsY = {css:{}, data:"_draggable"}, _tempVarsRotation = {css:{}}, _globals = _gsScope._gsDefine.globals, _tempEvent = {}, //for populating with pageX/pageY in old versions of IE _emptyFunc = function() { return false; }, _dummyElement = {style:{}, appendChild:_emptyFunc, removeChild:_emptyFunc}, _doc = _gsScope.document || {createElement: function() {return _dummyElement;}}, _docElement = _doc.documentElement || {}, _createElement = function(type) { return _doc.createElementNS ? _doc.createElementNS("http://www.w3.org/1999/xhtml", type) : _doc.createElement(type); }, _tempDiv = _createElement("div"), _emptyArray = [], _RAD2DEG = 180 / Math.PI, _max = 999999999999999, _getTime = Date.now || function() {return new Date().getTime();}, _isOldIE = !!(!_doc.addEventListener && _doc.all), _placeholderDiv = _doc.createElement("div"), _renderQueue = [], _lookup = {}, //when a Draggable is created, the target gets a unique _gsDragID property that allows gets associated with the Draggable instance for quick lookups in Draggable.get(). This avoids circular references that could cause gc problems. _lookupCount = 0, _clickableTagExp = /^(?:a|input|textarea|button|select)$/i, _dragCount = 0, //total number of elements currently being dragged _prefix, _isMultiTouching, _isAndroid = (_gsScope.navigator && _gsScope.navigator.userAgent.toLowerCase().indexOf("android") !== -1), //Android handles touch events in an odd way and it's virtually impossible to "feature test" so we resort to UA sniffing _lastDragTime = 0, _temp1 = {}, // a simple object we reuse and populate (usually x/y properties) to conserve memory and improve performance. _windowProxy = {}, //memory/performance optimization - we reuse this object during autoScroll to store window-related bounds/offsets. _supportsPassive, _slice = function(a) { //don't use Array.prototype.slice.call(target, 0) because that doesn't work in IE8 with a NodeList that's returned by querySelectorAll() if (typeof(a) === "string") { a = TweenLite.selector(a); } if (!a || a.nodeType) { //if it's not an array, wrap it in one. return [a]; } var b = [], l = a.length, i; for (i = 0; i !== l; b.push(a[i++])); return b; }, _copy = function(obj, factor) { var copy = {}, p; if (factor) { for (p in obj) { copy[p] = obj[p] * factor; } } else { for (p in obj) { copy[p] = obj[p]; } } return copy; }, ThrowPropsPlugin, _renderQueueTick = function() { var i = _renderQueue.length; while (--i > -1) { _renderQueue[i](); } }, _addToRenderQueue = function(func) { _renderQueue.push(func); if (_renderQueue.length === 1) { TweenLite.ticker.addEventListener("tick", _renderQueueTick, this, false, 1); } }, _removeFromRenderQueue = function(func) { var i = _renderQueue.length; while (--i > -1) { if (_renderQueue[i] === func) { _renderQueue.splice(i, 1); } } TweenLite.to(_renderQueueTimeout, 0, {overwrite:"all", delay:15, onComplete:_renderQueueTimeout, data:"_draggable"}); //remove the "tick" listener only after the render queue is empty for 15 seconds (to improve performance). Adding/removing it constantly for every click/touch wouldn't deliver optimal speed, and we also don't want the ticker to keep calling the render method when things are idle for long periods of time (we want to improve battery life on mobile devices). }, _renderQueueTimeout = function() { if (!_renderQueue.length) { TweenLite.ticker.removeEventListener("tick", _renderQueueTick); } }, _extend = function(obj, defaults) { var p; for (p in defaults) { if (obj[p] === undefined) { obj[p] = defaults[p]; } } return obj; }, _getDocScrollTop = function() { return (window.pageYOffset != null) ? window.pageYOffset : (_doc.scrollTop != null) ? _doc.scrollTop : _docElement.scrollTop || _doc.body.scrollTop || 0; }, _getDocScrollLeft = function() { return (window.pageXOffset != null) ? window.pageXOffset : (_doc.scrollLeft != null) ? _doc.scrollLeft : _docElement.scrollLeft || _doc.body.scrollLeft || 0; }, _addScrollListener = function(e, callback) { _addListener(e, "scroll", callback); if (!_isRoot(e.parentNode)) { _addScrollListener(e.parentNode, callback); } }, _removeScrollListener = function(e, callback) { _removeListener(e, "scroll", callback); if (!_isRoot(e.parentNode)) { _removeScrollListener(e.parentNode, callback); } }, _isRoot = function (e) { return !!(!e || e === _docElement || e === _doc || e === _doc.body || e === window || !e.nodeType || !e.parentNode); }, _getMaxScroll = function(element, axis) { var dim = (axis === "x") ? "Width" : "Height", scroll = "scroll" + dim, client = "client" + dim, body = _doc.body; return Math.max(0, _isRoot(element) ? Math.max(_docElement[scroll], body[scroll]) - (window["inner" + dim] || _docElement[client] || body[client]) : element[scroll] - element[client]); }, _recordMaxScrolls = function(e) { //records _gsMaxScrollX and _gsMaxScrollY properties for the element and all ancestors up the chain so that we can cap it, otherwise dragging beyond the edges with autoScroll on can endlessly scroll. var isRoot = _isRoot(e), x = _getMaxScroll(e, "x"), y = _getMaxScroll(e, "y"); if (isRoot) { e = _windowProxy; } else { _recordMaxScrolls(e.parentNode); } e._gsMaxScrollX = x; e._gsMaxScrollY = y; e._gsScrollX = e.scrollLeft || 0; e._gsScrollY = e.scrollTop || 0; }, //just used for IE8 and earlier to normalize events and populate pageX/pageY _populateIEEvent = function(e, preventDefault) { e = e || window.event; _tempEvent.pageX = e.clientX + _doc.body.scrollLeft + _docElement.scrollLeft; _tempEvent.pageY = e.clientY + _doc.body.scrollTop + _docElement.scrollTop; if (preventDefault) { e.returnValue = false; } return _tempEvent; }, //grabs the first element it finds (and we include the window as an element), so if it's selector text, it'll feed that value to TweenLite.selector, if it's a jQuery object or some other selector engine's result, it'll grab the first one, and same for an array. If the value doesn't contain a DOM element, it'll just return null. _unwrapElement = function(value) { if (!value) { return value; } if (typeof(value) === "string") { value = TweenLite.selector(value); } if (value.length && value !== window && value[0] && value[0].style && !value.nodeType) { value = value[0]; } return (value === window || (value.nodeType && value.style)) ? value : null; }, _checkPrefix = function(e, p) { var s = e.style, capped, i, a; if (s[p] === undefined) { a = ["O","Moz","ms","Ms","Webkit"]; i = 5; capped = p.charAt(0).toUpperCase() + p.substr(1); while (--i > -1 && s[a[i]+capped] === undefined) { } if (i < 0) { return ""; } _prefix = (i === 3) ? "ms" : a[i]; p = _prefix + capped; } return p; }, _setStyle = function(e, p, value) { var s = e.style; if (!s) { return; } if (s[p] === undefined) { p = _checkPrefix(e, p); } if (value == null) { if (s.removeProperty) { s.removeProperty(p.replace(/([A-Z])/g, "-$1").toLowerCase()); } else { //note: old versions of IE use "removeAttribute()" instead of "removeProperty()" s.removeAttribute(p); } } else if (s[p] !== undefined) { s[p] = value; } }, _computedStyleScope = (typeof(window) !== "undefined" ? window : _doc.defaultView || {getComputedStyle:function() {}}), _getComputedStyle = function(e, s) { return _computedStyleScope.getComputedStyle((e instanceof Element) ? e : e.host || (e.parentNode || {}).host || e, s); //the "host" stuff helps to accommodate ShadowDom objects. }, _horizExp = /(?:Left|Right|Width)/i, _suffixExp = /(?:\d|\-|\+|=|#|\.)*/g, _convertToPixels = function(t, p, v, sfx, recurse) { if (sfx === "px" || !sfx) { return v; } if (sfx === "auto" || !v) { return 0; } var horiz = _horizExp.test(p), node = t, style = _tempDiv.style, neg = (v < 0), pix; if (neg) { v = -v; } if (sfx === "%" && p.indexOf("border") !== -1) { pix = (v / 100) * (horiz ? t.clientWidth : t.clientHeight); } else { style.cssText = "border:0 solid red;position:" + _getStyle(t, "position", true) + ";line-height:0;"; if (sfx === "%" || !node.appendChild) { node = t.parentNode || _doc.body; style[(horiz ? "width" : "height")] = v + sfx; } else { style[(horiz ? "borderLeftWidth" : "borderTopWidth")] = v + sfx; } node.appendChild(_tempDiv); pix = parseFloat(_tempDiv[(horiz ? "offsetWidth" : "offsetHeight")]); node.removeChild(_tempDiv); if (pix === 0 && !recurse) { pix = _convertToPixels(t, p, v, sfx, true); } } return neg ? -pix : pix; }, _calculateOffset = function(t, p) { //for figuring out "top" or "left" in px when it's "auto". We need to factor in margin with the offsetLeft/offsetTop if (_getStyle(t, "position", true) !== "absolute") { return 0; } var dim = ((p === "left") ? "Left" : "Top"), v = _getStyle(t, "margin" + dim, true); return t["offset" + dim] - (_convertToPixels(t, p, parseFloat(v), (v + "").replace(_suffixExp, "")) || 0); }, _getStyle = function(element, prop, keepUnits) { var rv = (element._gsTransform || {})[prop], cs; if (rv || rv === 0) { return rv; } else if (element.style && element.style[prop]) { //shadow dom elements don't have "style". rv = element.style[prop]; } else if ((cs = _getComputedStyle(element))) { rv = cs.getPropertyValue(prop.replace(/([A-Z])/g, "-$1").toLowerCase()); rv = (rv || cs.length) ? rv : cs[prop]; //Opera behaves VERY strangely - length is usually 0 and cs[prop] is the only way to get accurate results EXCEPT when checking for -o-transform which only works with cs.getPropertyValue()! } else if (element.currentStyle) { rv = element.currentStyle[prop]; } if (rv === "auto" && (prop === "top" || prop === "left")) { rv = _calculateOffset(element, prop); } return keepUnits ? rv : parseFloat(rv) || 0; }, _dispatchEvent = function(instance, type, callbackName) { var vars = instance.vars, callback = vars[callbackName], listeners = instance._listeners[type]; if (typeof(callback) === "function") { callback.apply(vars[callbackName + "Scope"] || vars.callbackScope || instance, vars[callbackName + "Params"] || [instance.pointerEvent]); } if (listeners) { instance.dispatchEvent(type); } }, _getBounds = function(obj, context) { //accepts any of the following: a DOM element, jQuery object, selector text, or an object defining bounds as {top, left, width, height} or {minX, maxX, minY, maxY}. Returns an object with left, top, width, and height properties. var e = _unwrapElement(obj), top, left, offset; if (!e) { if (obj.left !== undefined) { offset = _getOffsetTransformOrigin(context); //the bounds should be relative to the origin return {left: obj.left - offset.x, top: obj.top - offset.y, width: obj.width, height: obj.height}; } left = obj.min || obj.minX || obj.minRotation || 0; top = obj.min || obj.minY || 0; return {left:left, top:top, width:(obj.max || obj.maxX || obj.maxRotation || 0) - left, height:(obj.max || obj.maxY || 0) - top}; } return _getElementBounds(e, context); }, _svgBorderFactor, _svgBorderScales, _svgScrollOffset, _hasBorderBug, _hasReparentBug,//some browsers, like Chrome 49, alter the offsetTop/offsetLeft/offsetParent of elements when a non-identity transform is applied. _setEnvironmentVariables = function() { //some browsers factor the border into the SVG coordinate space, some don't (like Firefox). Some apply transforms to them, some don't. We feature-detect here so we know how to handle the border(s). We can't do this immediately - we must wait for the document.body to exist. if (!_doc.createElementNS) { _svgBorderFactor = 0; _svgBorderScales = false; return; } var div = _createElement("div"), svg = _doc.createElementNS("http://www.w3.org/2000/svg", "svg"), wrapper = _createElement("div"), style = div.style, parent = _doc.body || _docElement, isFlex = (_getStyle(parent, "display", true) === "flex"), //Firefox bug causes getScreenCTM() to return null when parent is display:flex and the element isn't rendered inside the window (like if it's below the scroll position) matrix, e1, point, oldValue; if (_doc.body && _transformProp) { style.position = "absolute"; parent.appendChild(wrapper); wrapper.appendChild(div); oldValue = div.offsetParent; wrapper.style[_transformProp] = "rotate(1deg)"; _hasReparentBug = (div.offsetParent === oldValue); wrapper.style.position = "absolute"; style.height = "10px"; oldValue = div.offsetTop; wrapper.style.border = "5px solid red"; _hasBorderBug = (oldValue !== div.offsetTop); //some browsers, like Firefox 38, cause the offsetTop/Left to be affected by a parent's border. parent.removeChild(wrapper); } style = svg.style; svg.setAttributeNS(null, "width", "400px"); svg.setAttributeNS(null, "height", "400px"); svg.setAttributeNS(null, "viewBox", "0 0 400 400"); style.display = "block"; style.boxSizing = "border-box"; style.border = "0px solid red"; style.transform = "none"; // in some browsers (like certain flavors of Android), the getScreenCTM() matrix is contaminated by the scroll position. We can run some logic here to detect that condition, but we ended up not needing this because we found another workaround using getBoundingClientRect(). div.style.cssText = "width:100px;height:100px;overflow:scroll;-ms-overflow-style:none;"; parent.appendChild(div); div.appendChild(svg); point = svg.createSVGPoint().matrixTransform(svg.getScreenCTM()); e1 = point.y; div.scrollTop = 100; point.x = point.y = 0; point = point.matrixTransform(svg.getScreenCTM()); _svgScrollOffset = (e1 - point.y < 100.1) ? 0 : e1 - point.y - 150; div.removeChild(svg); parent.removeChild(div); // -- end _svgScrollOffset calculation. parent.appendChild(svg); if (isFlex) { parent.style.display = "block"; //Firefox bug causes getScreenCTM() to return null when parent is display:flex and the element isn't rendered inside the window (like if it's below the scroll position) } matrix = svg.getScreenCTM(); e1 = matrix.e; style.border = "50px solid red"; matrix = svg.getScreenCTM(); if (e1 === 0 && matrix.e === 0 && matrix.f === 0 && matrix.a === 1) { //Opera has a bunch of bugs - it doesn't adjust the x/y of the matrix, nor does it scale when box-sizing is border-box but it does so elsewhere; to get the correct behavior we set _svgBorderScales to true. _svgBorderFactor = 1; _svgBorderScales = true; } else { _svgBorderFactor = (e1 !== matrix.e) ? 1 : 0; _svgBorderScales = (matrix.a !== 1); } if (isFlex) { parent.style.display = "flex"; } parent.removeChild(svg); }, _supports3D = (_checkPrefix(_tempDiv, "perspective") !== ""), // start matrix and point conversion methods... _transformOriginProp = _checkPrefix(_tempDiv, "transformOrigin").replace(/^ms/g, "Ms").replace(/([A-Z])/g, "-$1").toLowerCase(), _transformProp = _checkPrefix(_tempDiv, "transform"), _transformPropCSS = _transformProp.replace(/^ms/g, "Ms").replace(/([A-Z])/g, "-$1").toLowerCase(), _point1 = {}, //we reuse _point1 and _point2 objects inside matrix and point conversion methods to conserve memory and minimize garbage collection tasks. _point2 = {}, _SVGElement = _gsScope.SVGElement, _isSVG = function(e) { return !!(_SVGElement && typeof(e.getBBox) === "function" && e.getCTM && (!e.parentNode || (e.parentNode.getBBox && e.parentNode.getCTM))); }, _isIE10orBelow = (_gsScope.navigator && (((/MSIE ([0-9]{1,}[\.0-9]{0,})/).exec(_gsScope.navigator.userAgent) || (/Trident\/.*rv:([0-9]{1,}[\.0-9]{0,})/).exec(_gsScope.navigator.userAgent)) && parseFloat( RegExp.$1 ) < 11)), //Ideally we'd avoid user agent sniffing, but there doesn't seem to be a way to feature-detect and sense a border-related bug that only affects IE10 and IE9. _tempTransforms = [], _tempElements = [], _getSVGOffsets = function(e) { //SVG elements don't always report offsetTop/offsetLeft/offsetParent at all (I'm looking at you, Firefox 29 and Android), so we have to do some work to manufacture those values. You can pass any SVG element and it'll spit back an object with offsetTop, offsetLeft, offsetParent, scaleX, and scaleY properties. We need the scaleX and scaleY to handle the way SVG can resize itself based on the container. if (!e.getBoundingClientRect || !e.parentNode || !_transformProp) { return {offsetTop:0, offsetLeft:0, scaleX:1, scaleY:1, offsetParent:_docElement}; } if (Draggable.cacheSVGData !== false && e._dCache && e._dCache.lastUpdate === TweenLite.ticker.frame) { //performance optimization. Assume that if the offsets are requested again on the same tick, we can just feed back the values we already calculated (no need to keep recalculating until another tick elapses). return e._dCache; } var curElement = e, cache = _cache(e), eRect, parentRect, offsetParent, cs, m, i, point1, point2, borderWidth, borderHeight, width, height; cache.lastUpdate = TweenLite.ticker.frame; if (e.getBBox && !cache.isSVGRoot) { //if it's a nested/child SVG element, we must find the parent SVG canvas and measure the offset from there. curElement = e.parentNode; eRect = e.getBBox(); while (curElement && (curElement.nodeName + "").toLowerCase() !== "svg") { curElement = curElement.parentNode; } cs = _getSVGOffsets(curElement); cache.offsetTop = eRect.y * cs.scaleY; cache.offsetLeft = eRect.x * cs.scaleX; cache.scaleX = cs.scaleX; cache.scaleY = cs.scaleY; cache.offsetParent = curElement || _docElement; return cache; } //only root SVG elements continue here... offsetParent = cache.offsetParent; if (offsetParent === _doc.body) { offsetParent = _docElement; //avoids problems with margins/padding on the body } //walk up the ancestors and record any non-identity transforms (and reset them to "none") until we reach the offsetParent. We must do this so that the getBoundingClientRect() is accurate for measuring the offsetTop/offsetLeft. We'll revert the values later... _tempElements.length = _tempTransforms.length = 0; while (curElement && curElement.parentNode) { m = _getStyle(curElement, _transformProp, true); if (m !== "matrix(1, 0, 0, 1, 0, 0)" && m !== "none" && m !== "translate3d(0px, 0px, 0px)") { _tempElements.push(curElement); _tempTransforms.push(curElement.style[_transformProp]); curElement.style[_transformProp] = "none"; } curElement = curElement.parentNode; } parentRect = offsetParent.getBoundingClientRect(); m = e.getScreenCTM(); point2 = e.createSVGPoint(); point1 = point2.matrixTransform(m); cache.scaleX = Math.sqrt(m.a * m.a + m.b * m.b); cache.scaleY = Math.sqrt(m.d * m.d + m.c * m.c); if (_svgBorderFactor === undefined) { _setEnvironmentVariables(); } if (cache.borderBox && !_svgBorderScales && e.getAttribute("width")) { //some browsers (like Safari) don't properly scale the matrix to accommodate the border when box-sizing is border-box, so we must calculate it here... cs = _getComputedStyle(e) || {}; borderWidth = (parseFloat(cs.borderLeftWidth) + parseFloat(cs.borderRightWidth)) || 0; borderHeight = (parseFloat(cs.borderTopWidth) + parseFloat(cs.borderBottomWidth)) || 0; width = parseFloat(cs.width) || 0; height = parseFloat(cs.height) || 0; cache.scaleX *= (width - borderWidth) / width; cache.scaleY *= (height - borderHeight) / height; } if (_svgScrollOffset) { //some browsers (like Chrome for Android) have bugs in the way getScreenCTM() is reported (it doesn't factor in scroll position), so we must revert to a more expensive technique for calculating offsetTop/Left. eRect = e.getBoundingClientRect(); cache.offsetLeft = eRect.left - parentRect.left; cache.offsetTop = eRect.top - parentRect.top; } else { cache.offsetLeft = point1.x - parentRect.left; cache.offsetTop = point1.y - parentRect.top; } cache.offsetParent = offsetParent; i = _tempElements.length; while (--i > -1) { _tempElements[i].style[_transformProp] = _tempTransforms[i]; } return cache; }, _getOffsetTransformOrigin = function(e, decoratee) { //returns the x/y position of the transformOrigin of the element, in its own local coordinate system (pixels), offset from the top left corner. decoratee = decoratee || {}; if (!e || e === _docElement || !e.parentNode || e === window) { return {x:0, y:0}; } var cs = _getComputedStyle(e), v = (_transformOriginProp && cs) ? cs.getPropertyValue(_transformOriginProp) : "50% 50%", a = v.split(" "), x = (v.indexOf("left") !== -1) ? "0%" : (v.indexOf("right") !== -1) ? "100%" : a[0], y = (v.indexOf("top") !== -1) ? "0%" : (v.indexOf("bottom") !== -1) ? "100%" : a[1]; if (y === "center" || y == null) { y = "50%"; } if (x === "center" || isNaN(parseFloat(x))) { //remember, the user could flip-flop the values and say "bottom center" or "center bottom", etc. "center" is ambiguous because it could be used to describe horizontal or vertical, hence the isNaN(). If there's an "=" sign in the value, it's relative. x = "50%"; } if (e.getBBox && _isSVG(e)) { //SVG elements must be handled in a special way because their origins are calculated from the top left. if (!e._gsTransform) { TweenLite.set(e, {x:"+=0", overwrite:false}); //forces creation of the _gsTransform where we store all the transform components including xOrigin and yOrigin for SVG elements, as of GSAP 1.15.0 which also takes care of calculating the origin from the upper left corner of the SVG canvas. if (e._gsTransform.xOrigin === undefined) { console.log("Draggable requires at least GSAP 1.17.0"); } } v = e.getBBox(); decoratee.x = (e._gsTransform.xOrigin - v.x); decoratee.y = (e._gsTransform.yOrigin - v.y); } else { if (e.getBBox && (x + y).indexOf("%") !== -1) { //Firefox doesn't report offsetWidth/height on elements. e = e.getBBox(); e = {offsetWidth: e.width, offsetHeight: e.height}; } decoratee.x = ((x.indexOf("%") !== -1) ? e.offsetWidth * parseFloat(x) / 100 : parseFloat(x)); decoratee.y = ((y.indexOf("%") !== -1) ? e.offsetHeight * parseFloat(y) / 100 : parseFloat(y)); } return decoratee; }, _cache = function(e) { //computes some important values and stores them in a _dCache object attached to the element itself so that we can optimize performance if (Draggable.cacheSVGData !== false && e._dCache && e._dCache.lastUpdate === TweenLite.ticker.frame) { //performance optimization. Assume that if the offsets are requested again on the same tick, we can just feed back the values we already calculated (no need to keep recalculating until another tick elapses). return e._dCache; } var cache = e._dCache = e._dCache || {}, cs = _getComputedStyle(e), isSVG = (e.getBBox && _isSVG(e)), isSVGRoot = ((e.nodeName + "").toLowerCase() === "svg"), curSVG; cache.isSVG = isSVG; cache.isSVGRoot = isSVGRoot; cache.borderBox = (cs.boxSizing === "border-box"); cache.computedStyle = cs; if (isSVGRoot) { //some browsers don't report parentNode on SVG elements. curSVG = e.parentNode || _docElement; curSVG.insertBefore(_tempDiv, e); cache.offsetParent = _tempDiv.offsetParent || _docElement; //in some cases, Firefox still reports offsetParent as null. curSVG.removeChild(_tempDiv); } else if (isSVG) { curSVG = e.parentNode; while (curSVG && (curSVG.nodeName + "").toLowerCase() !== "svg") { //offsetParent is always the SVG canvas for SVG elements. curSVG = curSVG.parentNode; } cache.offsetParent = curSVG; } else { cache.offsetParent = e.offsetParent; } return cache; }, _getOffset2DMatrix = function(e, offsetOrigin, parentOffsetOrigin, zeroOrigin, isBase) { //"isBase" helps us discern context - it should only be true when the element is the base one (the one at which we're starting to walk up the chain). It only matters in cases when it's an element itself because that's a case when we don't apply scaling. if (e === window || !e || !e.style || !e.parentNode) { return [1,0,0,1,0,0]; } var cache = e._dCache || _cache(e), parent = e.parentNode, parentCache = parent._dCache || _cache(parent), cs = cache.computedStyle, parentOffsetParent = cache.isSVG ? parentCache.offsetParent : parent.offsetParent, m, isRoot, offsets, rect, t, sx, sy, offsetX, offsetY, parentRect, borderTop, borderLeft, borderTranslateX, borderTranslateY; m = (cache.isSVG && (e.style[_transformProp] + "").indexOf("matrix") !== -1) ? e.style[_transformProp] : cs ? cs.getPropertyValue(_transformPropCSS) : e.currentStyle ? e.currentStyle[_transformProp] : "1,0,0,1,0,0"; //some browsers (like Chrome 40) don't correctly report transforms that are applied inline on an SVG element (they don't get included in the computed style), so we double-check here and accept matrix values if (e.getBBox && (e.getAttribute("transform") + "").indexOf("matrix") !== -1) { //SVG can store transform data in its "transform" attribute instead of the CSS, so look for that here (only accept matrix()). m = e.getAttribute("transform"); } m = (m + "").match(/(?:\-|\.|\b)(\d|\.|e\-)+/g) || [1,0,0,1,0,0]; if (m.length > 6) { m = [m[0], m[1], m[4], m[5], m[12], m[13]]; } if (zeroOrigin) { m[4] = m[5] = 0; } else if (cache.isSVG && (t = e._gsTransform) && (t.xOrigin || t.yOrigin)) { //SVGs handle origin very differently. Factor in GSAP's handling of origin values here: m[0] = parseFloat(m[0]); m[1] = parseFloat(m[1]); m[2] = parseFloat(m[2]); m[3] = parseFloat(m[3]); m[4] = parseFloat(m[4]) - (t.xOrigin - (t.xOrigin * m[0] + t.yOrigin * m[2])); m[5] = parseFloat(m[5]) - (t.yOrigin - (t.xOrigin * m[1] + t.yOrigin * m[3])); } if (offsetOrigin) { if (_svgBorderFactor === undefined) { _setEnvironmentVariables(); } offsets = (cache.isSVG || cache.isSVGRoot) ? _getSVGOffsets(e) : e; if (cache.isSVG) { //don't just rely on "instanceof _SVGElement" because if the SVG is embedded via an object tag, it won't work (SVGElement is mapped to a different object)) rect = e.getBBox(); parentRect = (parentCache.isSVGRoot) ? {x:0, y:0} : parent.getBBox(); offsets = {offsetLeft:rect.x - parentRect.x, offsetTop:rect.y - parentRect.y, offsetParent:cache.offsetParent}; } else if (cache.isSVGRoot) { borderTop = parseInt(cs.borderTopWidth, 10) || 0; borderLeft = parseInt(cs.borderLeftWidth, 10) || 0; borderTranslateX = ((m[0] - _svgBorderFactor) * borderLeft + m[2] * borderTop); borderTranslateY = (m[1] * borderLeft + (m[3] - _svgBorderFactor) * borderTop); sx = offsetOrigin.x; sy = offsetOrigin.y; offsetX = (sx - (sx * m[0] + sy * m[2])); //accommodate the SVG root's transforms when the origin isn't in the top left. offsetY = (sy - (sx * m[1] + sy * m[3])); m[4] = parseFloat(m[4]) + offsetX; m[5] = parseFloat(m[5]) + offsetY; offsetOrigin.x -= offsetX; offsetOrigin.y -= offsetY; sx = offsets.scaleX; sy = offsets.scaleY; if (!isBase) { //when getting the matrix for a root element itself (NOT in the context of an SVG element that's nested inside of it like a ), we do NOT apply the scaling! offsetOrigin.x *= sx; offsetOrigin.y *= sy; } m[0] *= sx; m[1] *= sy; m[2] *= sx; m[3] *= sy; if (!_isIE10orBelow) { offsetOrigin.x += borderTranslateX; offsetOrigin.y += borderTranslateY; } if (parentOffsetParent === _doc.body && offsets.offsetParent === _docElement) { //to avoid issues with margin/padding on the , we always set the offsetParent to _docElement in the _getSVGOffsets() function but there's a condition we check later in this function for (parentOffsetParent === offsets.offsetParent) which would fail if we don't run this logic. In other words, parentOffsetParent may be and the 's offsetParent is also but artificially set to _docElement to avoid margin/padding issues. parentOffsetParent = _docElement; } } else if (!_hasBorderBug && e.offsetParent) { offsetOrigin.x += parseInt(_getStyle(e.offsetParent, "borderLeftWidth"), 10) || 0; offsetOrigin.y += parseInt(_getStyle(e.offsetParent, "borderTopWidth"), 10) || 0; } isRoot = (parent === _docElement || parent === _doc.body); m[4] = Number(m[4]) + offsetOrigin.x + (offsets.offsetLeft || 0) - parentOffsetOrigin.x - (isRoot ? 0 : parent.scrollLeft || 0); m[5] = Number(m[5]) + offsetOrigin.y + (offsets.offsetTop || 0) - parentOffsetOrigin.y - (isRoot ? 0 : parent.scrollTop || 0); if (parent && _getStyle(e, "position", true) === "fixed") { //fixed position elements should factor in the scroll position of the document. m[4] += _getDocScrollLeft(); m[5] += _getDocScrollTop(); parent = parent.offsetParent; while (parent) { m[4] -= parent.offsetLeft; m[5] -= parent.offsetTop; parent = parent.offsetParent; } } else if (parent && parent !== _docElement && parentOffsetParent === offsets.offsetParent && !parentCache.isSVG && (!_hasReparentBug || _getOffset2DMatrix(parent).join("") === "100100")) { offsets = (parentCache.isSVGRoot) ? _getSVGOffsets(parent) : parent; m[4] -= offsets.offsetLeft || 0; m[5] -= offsets.offsetTop || 0; if (!_hasBorderBug && parentCache.offsetParent && !cache.isSVG && !cache.isSVGRoot) { m[4] -= parseInt(_getStyle(parentCache.offsetParent, "borderLeftWidth"), 10) || 0; m[5] -= parseInt(_getStyle(parentCache.offsetParent, "borderTopWidth"), 10) || 0; } } } return m; }, _getConcatenatedMatrix = function(e, invert) { if (!e || e === window || !e.parentNode) { return [1,0,0,1,0,0]; } //note: we keep reusing _point1 and _point2 in order to minimize memory usage and garbage collection chores. var originOffset = _getOffsetTransformOrigin(e, _point1), parentOriginOffset = _getOffsetTransformOrigin(e.parentNode, _point2), m = _getOffset2DMatrix(e, originOffset, parentOriginOffset, false, !invert), a, b, c, d, tx, ty, m2, determinant; while ((e = e.parentNode) && e.parentNode && e !== _docElement) { originOffset = parentOriginOffset; parentOriginOffset = _getOffsetTransformOrigin(e.parentNode, (originOffset === _point1) ? _point2 : _point1); m2 = _getOffset2DMatrix(e, originOffset, parentOriginOffset); a = m[0]; b = m[1]; c = m[2]; d = m[3]; tx = m[4]; ty = m[5]; m[0] = a * m2[0] + b * m2[2]; m[1] = a * m2[1] + b * m2[3]; m[2] = c * m2[0] + d * m2[2]; m[3] = c * m2[1] + d * m2[3]; m[4] = tx * m2[0] + ty * m2[2] + m2[4]; m[5] = tx * m2[1] + ty * m2[3] + m2[5]; } if (invert) { a = m[0]; b = m[1]; c = m[2]; d = m[3]; tx = m[4]; ty = m[5]; determinant = (a * d - b * c); m[0] = d / determinant; m[1] = -b / determinant; m[2] = -c / determinant; m[3] = a / determinant; m[4] = (c * ty - d * tx) / determinant; m[5] = -(a * ty - b * tx) / determinant; } return m; }, _localToGlobal = function(e, p, fromTopLeft, decoratee) { e = _unwrapElement(e); var m = _getConcatenatedMatrix(e, false), x = p.x, y = p.y; if (fromTopLeft) { _getOffsetTransformOrigin(e, p); x -= p.x; y -= p.y; } decoratee = (decoratee === true) ? p : decoratee || {}; decoratee.x = x * m[0] + y * m[2] + m[4]; decoratee.y = x * m[1] + y * m[3] + m[5]; return decoratee; }, _localizePoint = function(p, localToGlobal, globalToLocal) { var x = p.x * localToGlobal[0] + p.y * localToGlobal[2] + localToGlobal[4], y = p.x * localToGlobal[1] + p.y * localToGlobal[3] + localToGlobal[5]; p.x = x * globalToLocal[0] + y * globalToLocal[2] + globalToLocal[4]; p.y = x * globalToLocal[1] + y * globalToLocal[3] + globalToLocal[5]; return p; }, _getElementBounds = function(e, context, fromTopLeft) { if (!(e = _unwrapElement(e))) { return null; } context = _unwrapElement(context); var isSVG = (e.getBBox && _isSVG(e)), origin, left, right, top, bottom, mLocalToGlobal, mGlobalToLocal, p1, p2, p3, p4, bbox, width, height, cache, borderLeft, borderTop, viewBox, viewBoxX, viewBoxY, computedDimensions, cs; if (e === window) { top = _getDocScrollTop(); left = _getDocScrollLeft(); right = left + (_docElement.clientWidth || e.innerWidth || _doc.body.clientWidth || 0); bottom = top + (((e.innerHeight || 0) - 20 < _docElement.clientHeight) ? _docElement.clientHeight : e.innerHeight || _doc.body.clientHeight || 0); //some browsers (like Firefox) ignore absolutely positioned elements, and collapse the height of the documentElement, so it could be 8px, for example, if you have just an absolutely positioned div. In that case, we use the innerHeight to resolve this. } else if (context === undefined || context === window) { return e.getBoundingClientRect(); } else { origin = _getOffsetTransformOrigin(e); left = -origin.x; top = -origin.y; if (isSVG) { bbox = e.getBBox(); width = bbox.width; height = bbox.height; } else if ((e.nodeName + "").toLowerCase() !== "svg" && e.offsetWidth) { //Chrome dropped support for "offsetWidth" on SVG elements width = e.offsetWidth; height = e.offsetHeight; } else { computedDimensions = _getComputedStyle(e); width = parseFloat(computedDimensions.width); height = parseFloat(computedDimensions.height); } right = left + width; bottom = top + height; if (e.nodeName.toLowerCase() === "svg" && !_isOldIE) { //root SVG elements are a special beast because they have 2 types of scaling - transforms on themselves as well as the stretching of the SVG canvas itself based on the outer size and the viewBox. If, for example, the SVG's viewbox is "0 0 100 100" but the CSS is set to width:200px; height:200px, that'd make it appear at 2x scale even though the element itself has no CSS transforms but the offsetWidth/offsetHeight are based on that css, not the viewBox so we need to adjust them accordingly. cache = _getSVGOffsets(e); cs = cache.computedStyle || {}; viewBox = (e.getAttribute("viewBox") || "0 0").split(" "); viewBoxX = parseFloat(viewBox[0]); viewBoxY = parseFloat(viewBox[1]); borderLeft = parseFloat(cs.borderLeftWidth) || 0; borderTop = parseFloat(cs.borderTopWidth) || 0; left /= cache.scaleX; top /= cache.scaleY; right = left + width - (width - ((width - borderLeft) / cache.scaleX) - viewBoxX); bottom = top + height - (height - ((height - borderTop) / cache.scaleY) - viewBoxY); left -= borderLeft / cache.scaleX - viewBoxX; top -= borderTop / cache.scaleY - viewBoxY; if (computedDimensions) { //when we had to use computed styles, factor in the border now. right += (parseFloat(cs.borderRightWidth) + borderLeft) / cache.scaleX; bottom += (borderTop + parseFloat(cs.borderBottomWidth)) / cache.scaleY; } } } if (e === context) { return {left:left, top:top, width: right - left, height: bottom - top}; } mLocalToGlobal = _getConcatenatedMatrix(e); mGlobalToLocal = _getConcatenatedMatrix(context, true); p1 = _localizePoint({x:left, y:top}, mLocalToGlobal, mGlobalToLocal); p2 = _localizePoint({x:right, y:top}, mLocalToGlobal, mGlobalToLocal); p3 = _localizePoint({x:right, y:bottom}, mLocalToGlobal, mGlobalToLocal); p4 = _localizePoint({x:left, y:bottom}, mLocalToGlobal, mGlobalToLocal); left = Math.min(p1.x, p2.x, p3.x, p4.x); top = Math.min(p1.y, p2.y, p3.y, p4.y); _temp1.x = _temp1.y = 0; if (fromTopLeft) { _getOffsetTransformOrigin(context, _temp1); } return {left:left + _temp1.x, top:top + _temp1.y, width:Math.max(p1.x, p2.x, p3.x, p4.x) - left, height:Math.max(p1.y, p2.y, p3.y, p4.y) - top}; }, // end matrix and point conversion methods _isArrayLike = function(e) { return (e && e.length && e[0] && ((e[0].nodeType && e[0].style && !e.nodeType) || (e[0].length && e[0][0]))) ? true : false; //could be an array of jQuery objects too, so accommodate that. }, _flattenArray = function(a) { var result = [], l = a.length, i, e, j; for (i = 0; i < l; i++) { e = a[i]; if (_isArrayLike(e)) { j = e.length; for (j = 0; j < e.length; j++) { result.push(e[j]); } } else if (e && e.length !== 0) { result.push(e); } } return result; }, _isTouchDevice = (typeof(window) !== "undefined" && ("ontouchstart" in _docElement) && ("orientation" in window)), _touchEventLookup = (function(types) { //we create an object that makes it easy to translate touch event types into their "pointer" counterparts if we're in a browser that uses those instead. Like IE10 uses "MSPointerDown" instead of "touchstart", for example. var standard = types.split(","), converted = ((_tempDiv.onpointerdown !== undefined) ? "pointerdown,pointermove,pointerup,pointercancel" : (_tempDiv.onmspointerdown !== undefined) ? "MSPointerDown,MSPointerMove,MSPointerUp,MSPointerCancel" : types).split(","), obj = {}, i = 4; while (--i > -1) { obj[standard[i]] = converted[i]; obj[converted[i]] = standard[i]; } //to avoid problems in iOS 9, test to see if the browser supports the "passive" option on addEventListener(). try { _docElement.addEventListener("test", null, Object.defineProperty({}, "passive", { get: function () { _supportsPassive = 1; } })); } catch (e) {} return obj; }("touchstart,touchmove,touchend,touchcancel")), _addListener = function(element, type, func, capture) { if (element.addEventListener) { var touchType = _touchEventLookup[type]; capture = capture || (_supportsPassive ? {passive:false} : null); element.addEventListener(touchType || type, func, capture); if (touchType && type !== touchType) { //some browsers actually support both, so must we. element.addEventListener(type, func, capture); } } else if (element.attachEvent) { element.attachEvent("on" + type, func); } }, _removeListener = function(element, type, func) { if (element.removeEventListener) { var touchType = _touchEventLookup[type]; element.removeEventListener(touchType || type, func); if (touchType && type !== touchType) { element.removeEventListener(type, func); } } else if (element.detachEvent) { element.detachEvent("on" + type, func); } }, _hasTouchID = function(list, ID) { var i = list.length; while (--i > -1) { if (list[i].identifier === ID) { return true; } } return false; }, _onMultiTouchDocumentEnd = function(e) { _isMultiTouching = (e.touches && _dragCount < e.touches.length); _removeListener(e.target, "touchend", _onMultiTouchDocumentEnd); }, _onMultiTouchDocument = function(e) { _isMultiTouching = (e.touches && _dragCount < e.touches.length); _addListener(e.target, "touchend", _onMultiTouchDocumentEnd); }, _parseThrowProps = function(draggable, snap, max, min, factor, forceZeroVelocity) { var vars = {}, a, i, l; if (snap) { if (factor !== 1 && snap instanceof Array) { //some data must be altered to make sense, like if the user passes in an array of rotational values in degrees, we must convert it to radians. Or for scrollLeft and scrollTop, we invert the values. vars.end = a = []; l = snap.length; if (typeof(snap[0]) === "object") { //if the array is populated with objects, like points ({x:100, y:200}), make copies before multiplying by the factor, otherwise we'll mess up the originals and the user may reuse it elsewhere. for (i = 0; i < l; i++) { a[i] = _copy(snap[i], factor); } } else { for (i = 0; i < l; i++) { a[i] = snap[i] * factor; } } max += 1.1; //allow 1.1 pixels of wiggle room when snapping in order to work around some browser inconsistencies in the way bounds are reported which can make them roughly a pixel off. For example, if "snap:[-$('#menu').width(), 0]" was defined and #menu had a wrapper that was used as the bounds, some browsers would be one pixel off, making the minimum -752 for example when snap was [-753,0], thus instead of snapping to -753, it would snap to 0 since -753 was below the minimum. min -= 1.1; } else if (typeof(snap) === "function") { vars.end = function(value) { var result = snap.call(draggable, value), copy, p; if (factor !== 1) { if (typeof(result) === "object") { copy = {}; for (p in result) { copy[p] = result[p] * factor; } result = copy; } else { result *= factor; } } return result; //we need to ensure that we can scope the function call to the Draggable instance itself so that users can access important values like maxX, minX, maxY, minY, x, and y from within that function. }; } else { vars.end = snap; } } if (max || max === 0) { vars.max = max; } if (min || min === 0) { vars.min = min; } if (forceZeroVelocity) { vars.velocity = 0; } return vars; }, _isClickable = function(e) { //sometimes it's convenient to mark an element as clickable by adding a data-clickable="true" attribute (in which case we won't preventDefault() the mouse/touch event). This method checks if the element is an , , or