/*! * VERSION: 0.1.9 * DATE: 2019-02-07 * UPDATES AND DOCS AT: http://greensock.com * * @license Copyright (c) 2008-2019, GreenSock. All rights reserved. * GSDevTools 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 */ 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("GSDevTools", ["TweenLite", "core.Animation", "core.SimpleTimeline", "TimelineLite", "utils.Draggable", "plugins.CSSPlugin"], function(TweenLite, Animation, SimpleTimeline, TimelineLite, Draggable) { var _doc = document, _docElement = _doc.documentElement, _svgNS = "http://www.w3.org/2000/svg", _domNS = "http://www.w3.org/1999/xhtml", _idSeed = 0, //we assign an ID to each GSDevTools instance so that we can segregate the sessionStorage data accordingly. _lookup = {}, _createElement = function(type, container, cssText) { var element = _doc.createElementNS ? _doc.createElementNS(type === "svg" ? _svgNS : _domNS, type) : _doc.createElement(type); if (container) { if (typeof(container) === "string") { container = _doc.querySelector(container); } container.appendChild(element); } if (type === "svg") { element.setAttribute("xmlns", _svgNS); element.setAttribute("xmlns:xlink", _domNS); } if (cssText) { element.style.cssText = cssText; } return element; }, _clearSelection = function() { if (_doc.selection) { _doc.selection.empty() } else if (window.getSelection) { window.getSelection().removeAllRanges(); } }, _globalTimeline = Animation._rootTimeline, _getChildrenOf = function(timeline, includeTimelines) { var a = [], cnt = 0, tween = timeline._first; while (tween) { if (tween instanceof TweenLite) { if (tween.vars.id) { a[cnt++] = tween; } } else { if (includeTimelines && tween.vars.id) { a[cnt++] = tween; } a = a.concat(_getChildrenOf(tween, includeTimelines)); cnt = a.length; } tween = tween._next; } return a; }, //eliminates any very long repeating children (direct children, not deeply nested) and returns the duration accordingly. _getClippedDuration = function(animation, excludeRootRepeats) { var max = 0, repeat = Math.max(0, animation._repeat), t = animation._first; if (!t) { max = animation.duration(); } while (t) { max = Math.max(max, t.totalDuration() > 999 ? t.endTime(false) : t._startTime + t._totalDuration / t._timeScale); t = t._next; } return (!excludeRootRepeats && repeat) ? max * (repeat + 1) + (animation._repeatDelay * repeat) : max; }, _getAnimationById = function(id) { if (!id) { return null; } if (id instanceof Animation) { return id; } var a = _getChildrenOf(_globalTimeline, true), i = a.length; while (--i > -1) { if (a[i].vars.id === id) { return a[i]; } } }, _timeToProgress = function(time, animation, defaultValue, relativeProgress) { var add, i, a; if (typeof(time) === "string") { if (time.charAt(1) === "=") { add = parseInt(time.charAt(0) + "1", 10) * parseFloat(time.substr(2)); if (add < 0 && relativeProgress === 0) { //if something like inTime:"-=2", we measure it from the END, not the beginning relativeProgress = 100; } time = (relativeProgress / 100 * animation.duration()) + add; } else if (isNaN(time) && animation.getLabelTime && animation.getLabelTime(time) !== -1) { time = animation.getLabelTime(time); } else if (animation === _recordedRoot) { //perhaps they defined an id of an animation, like "myAnimation+=2" i = time.indexOf("="); if (i > 0) { add = parseInt(time.charAt(i-1) + "1", 10) * parseFloat(time.substr(i+1)); time = time.substr(0, i-1); } else { add = 0; } a = _getAnimationById(time); if (a) { time = _getGlobalTime(a, defaultValue / 100 * a.duration()) + add; } } } time = isNaN(time) ? defaultValue : parseFloat(time); return Math.min(100, Math.max(0, time / animation.duration() * 100)); }, _getGlobalTime = function(animation, time) { var tl = animation; time = time || 0; if (tl.timeline) { while (tl.timeline.timeline) { time = (time / tl._timeScale) + tl._startTime; tl = tl.timeline; } } return time; }, _addedCSS, _createRootElement = function(element, minimal, css) { if (!_addedCSS) { _createElement("style", _docElement).innerHTML = '.gs-dev-tools{height:51px;bottom:0;left:0;right:0;display:block;position:fixed;overflow:visible;padding:0}.gs-dev-tools *{box-sizing:content-box;visibility:visible}.gs-dev-tools .gs-top{position:relative;z-index:499}.gs-dev-tools .gs-bottom{display:flex;align-items:center;justify-content:space-between;background-color:rgba(0,0,0,.6);height:42px;border-top:1px solid #999;position:relative}.gs-dev-tools .timeline{position:relative;height:8px;margin-left:15px;margin-right:15px;overflow:visible}.gs-dev-tools .progress-bar,.gs-dev-tools .timeline-track{height:8px;width:100%;position:absolute;top:0;left:0}.gs-dev-tools .timeline-track{background-color:#999;opacity:.6}.gs-dev-tools .progress-bar{background-color:#91e600;height:8px;top:0;width:0;pointer-events:none}.gs-dev-tools .seek-bar{width:100%;position:absolute;height:24px;top:-12px;left:0;background-color:transparent}.gs-dev-tools .in-point,.gs-dev-tools .out-point{width:15px;height:26px;position:absolute;top:-18px}.gs-dev-tools .in-point-shape{fill:#6d9900;stroke:rgba(0,0,0,.5);stroke-width:1}.gs-dev-tools .out-point-shape{fill:#994242;stroke:rgba(0,0,0,.5);stroke-width:1}.gs-dev-tools .in-point{transform:translateX(-100%)}.gs-dev-tools .out-point{left:100%}.gs-dev-tools .grab{stroke:rgba(255,255,255,.3);stroke-width:1}.gs-dev-tools .playhead{position:absolute;top:-5px;transform:translate(-50%,0);left:0;border-radius:50%;width:16px;height:16px;border:1px solid #6d9900;background-color:#91e600}.gs-dev-tools .gs-btn-white{fill:#fff}.gs-dev-tools .pause{opacity:0}.gs-dev-tools .select-animation{vertical-align:middle;position:relative;padding:6px 10px}.gs-dev-tools .select-animation-container{flex-grow:4;width:40%}.gs-dev-tools .select-arrow{display:inline-block;width:12px;height:7px;margin:0 7px;transform:translate(0,-2px)}.gs-dev-tools .select-arrow-shape{stroke:rgba(255,255,255,.6);stroke-width:2px;fill:none}.gs-dev-tools .rewind{height:16px;width:19px;padding:10px 4px;min-width:24px}.gs-dev-tools .rewind-path{opacity:.6}.gs-dev-tools .play-pause{width:24px;height:24px;padding:6px 10px;min-width:24px}.gs-dev-tools .ease{width:30px;height:30px;padding:10px;min-width:30px;display:none}.gs-dev-tools .ease-path{fill:none;stroke:rgba(255,255,255,.6);stroke-width:2px}.gs-dev-tools .ease-border{fill:rgba(255,255,255,.25)}.gs-dev-tools .time-scale{font-family:monospace;font-size:18px;text-align:center;color:rgba(255,255,255,.6);padding:4px 4px 4px 0;min-width:30px;margin-left:7px}.gs-dev-tools .loop{width:20px;padding:5px;min-width:20px}.gs-dev-tools .loop-path{fill:rgba(255,255,255,.6)}.gs-dev-tools label span{color:#fff;font-family:monospace;text-decoration:none;font-size:16px;line-height:18px}.gs-dev-tools .time-scale span{color:rgba(255,255,255,.6)}.gs-dev-tools button:focus,.gs-dev-tools select:focus{outline:0}.gs-dev-tools label{position:relative;cursor:pointer}.gs-dev-tools label.locked{text-decoration:none;cursor:auto}.gs-dev-tools label input,.gs-dev-tools label select{position:absolute;left:0;top:0;z-index:1;font:inherit;font-size:inherit;line-height:inherit;height:100%;width:100%;color:#000!important;opacity:0;background:0 0;border:none;padding:0;margin:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;cursor:pointer}.gs-dev-tools label input+.display{position:relative;z-index:2}.gs-dev-tools .gs-bottom-right{vertical-align:middle;display:flex;align-items:center;flex-grow:4;width:40%;justify-content:flex-end}.gs-dev-tools .time-container{font-size:18px;font-family:monospace;color:rgba(255,255,255,.6);margin:0 5px}.gs-dev-tools .logo{width:32px;height:32px;position:relative;top:2px;margin:0 12px}.gs-dev-tools .gs-hit-area{background-color:transparent;width:100%;height:100%;top:0;position:absolute}.gs-dev-tools.minimal{height:auto;display:flex;align-items:stretch}.gs-dev-tools.minimal .gs-top{order:2;flex-grow:4;background-color:rgba(0,0,0,1)}.gs-dev-tools.minimal .gs-bottom{background-color:rgba(0,0,0,1);border-top:none}.gs-dev-tools.minimal .timeline{top:50%;transform:translate(0,-50%)}.gs-dev-tools.minimal .in-point,.gs-dev-tools.minimal .out-point{display:none}.gs-dev-tools.minimal .select-animation-container{display:none}.gs-dev-tools.minimal .rewind{display:none}.gs-dev-tools.minimal .play-pause{width:20px;height:20px;padding:4px 6px;margin-left:14px}.gs-dev-tools.minimal .time-scale{min-width:26px}.gs-dev-tools.minimal .loop{width:18px;min-width:18px;display:none}.gs-dev-tools.minimal .gs-bottom-right{display:none}@media only screen and (max-width:600px){.gs-dev-tools{height:auto;display:flex;align-items:stretch}.gs-dev-tools .gs-top{order:2;flex-grow:4;background-color:rgba(0,0,0,1);height:42px}.gs-dev-tools .gs-bottom{background-color:rgba(0,0,0,1);border-top:none}.gs-dev-tools .timeline{top:50%;transform:translate(0,-50%)}.gs-dev-tools .in-point,.gs-dev-tools .out-point{display:none}.gs-dev-tools .select-animation-container{display:none}.gs-dev-tools .rewind{display:none}.gs-dev-tools .play-pause{width:20px;height:20px;padding:4px 6px;margin-left:14px}.gs-dev-tools .time-scale{min-width:26px}.gs-dev-tools .loop{width:18px;min-width:18px;display:none}.gs-dev-tools .gs-bottom-right{display:none}}'; _addedCSS = true; } if (typeof(element) === "string") { element = document.querySelector(element); } var root = _createElement("div", element || _docElement.getElementsByTagName("body")[0] || _docElement); root.setAttribute("class", "gs-dev-tools" + (minimal ? " minimal" : "")); root.innerHTML = '
0.00 / 0.00
'; if (element) { root.style.position = "absolute"; root.style.top = minimal ? "calc(100% - 42px)" : "calc(100% - 51px)"; } if (css) { if (typeof(css) === "string") { root.style.cssText = css; } else if (typeof(css) === "object") { css.data = "root"; TweenLite.set(root, css).kill(); } if (root.style.top) { root.style.bottom = "auto"; } if (root.style.width) { TweenLite.set(root, {xPercent: -50, left: "50%", right: "auto", data:"root"}).kill(); } } if (!minimal && root.offsetWidth < 600) { root.setAttribute("class", "gs-dev-tools minimal"); if (element) { root.style.top = "calc(100% - 42px)"; } } return root; }, _clickedOnce, //we don't preventDefault() on the first mousedown/touchstart/pointerdown so that iframes get focus properly. _addListener = function(e, type, callback, capture) { var handler, altType; if (type === "mousedown" || type === "mouseup") { e.style.cursor = "pointer"; } if (type === "mousedown") { //some browsers call BOTH mousedown AND touchstart, for example, on a single interaction so we need to skip one of them if both are called within 100ms. altType = e.onpointerdown !== undefined ? "pointerdown" : e.ontouchstart !== undefined ? "touchstart" : null; if (altType) { handler = function(event) { if (event.target.nodeName.toLowerCase() !== "select" && event.type === altType) { //don't preventDefault() on a up or down by a certain amount. _shiftSelectedValue = function(element, amount, label) { var options = element.options, i = Math.min(options.length - 1, Math.max(0, element.selectedIndex + amount)); element.selectedIndex = i; if (label) { label.innerHTML = options[i].innerHTML; } return options[i].value; }, _recordedRoot = new TimelineLite({data:"root", id:"Global Timeline", autoRemoveChildren:false, smoothChildTiming:true}), _recordedTemp = new TimelineLite({data:"root", id:"Global Temp", autoRemoveChildren:false, smoothChildTiming:true}), _rootTween = TweenLite.to(_recordedRoot, 1, {time:1, ease:Linear.easeNone, data:"root", id:"_rootTween", paused:true, immediateRender:false}), _rootInstance, _rootIsDirty, _keyboardInstance, //moves everything from _recordedTemp into _recordedRoot and updates the _rootTween if it is currently controlling the Global timeline (_recordedRoot). _recordedTemp is just a temporary recording area for anything that happens while _recordedRoot is paused. Returns true if the _recordedRoot's duration changed due to the merge. _merge = function() { var t = _recordedTemp._first, duration, next; if (t) { if (_rootInstance && _rootInstance.animation() === _recordedRoot) { duration = _recordedRoot._duration; while (t) { next = t._next; if (!(typeof(t.target) === "function" && t.target === t.vars.onComplete && !t._duration) && !(t.target && t.target._gsIgnore)) { //typically, delayedCalls aren't included in the _recordedTemp, but since the hijacked add() below fires BEFORE TweenLite's constructor sets the target, we couldn't check that target === vars.onComplete there. And Draggable creates a tween with just an onComplete (no onReverseComplete), thus it fails that test. Therefore, we test again here to avoid merging that in. _recordedRoot.add(t, t._startTime - t._delay); } else { SimpleTimeline.prototype.add.call(_globalTimeline, t, t._startTime - t._delay); } t = next; } return (duration !== _recordedRoot.duration()); } else { //if it's not the _recordedRoot that's currently playing ("global timeline"), merge the temp tweens back into the real global timeline instead and kill any that are already completed. No need to keep them recorded. while (t) { next = t._next; if (t._gc || t._totalTime === t._totalDuration) { t.kill(); } else { //SimpleTimeline.prototype.add.call(_globalTimeline, t, t._recordedTime); //_globalTimeline.add(t, t._startTime - t._delay - _recordedTemp._startTime); SimpleTimeline.prototype.add.call(_globalTimeline, t, t._startTime - t._delay); } t = next; } } } }, _updateRootDuration = function() { if (_rootInstance) { _rootInstance.update(); _rootIsDirty = false; } TweenLite.ticker.removeEventListener("tick", _updateRootDuration); }, _buildPlayPauseMorph = function(svg) { var tl = new TimelineLite({data:"root", onComplete:function() { tl.kill(); }}); tl.to(svg.querySelector(".play-1"), 0.5, {attr:{d:"M5.75,3.13 C5.75,9.79 5.75,16.46 5.75,23.13 4.08,23.13 2.41,23.13 0.75,23.13 0.75,16.46 0.75,9.79 0.75,3.12 2.41,3.12 4.08,3.12 5.75,3.12"}, ease:Power3.easeInOut, rotation:360, transformOrigin:"50% 50%"}) .to(svg.querySelector(".play-2"), 0.5, {attr:{d:"M16.38,3.13 C16.38,9.79 16.38,16.46 16.38,23.13 14.71,23.13 13.04,23.13 11.38,23.13 11.38,16.46 11.38,9.79 11.38,3.12 13.04,3.12 14.71,3.12 16.38,3.12"}, ease:Power3.easeInOut, rotation:360, transformOrigin:"50% 50%"}, 0.05); return tl; }, _buildLoopAnimation = function(svg) { var tl = new TimelineLite({data:"root", paused:true, onComplete:function() { tl.kill(); }}); tl.to(svg, 0.5, {rotation:360, ease:Power3.easeInOut, transformOrigin:"50% 50%"}) .to(svg.querySelectorAll(".loop-path"), 0.5, {fill:"#91e600", ease:Linear.easeNone}, 0); return tl; }, GSDevTools = function(vars) { this.vars = vars = vars || {}; vars.id = vars.id || (typeof(vars.animation) === "string" ? vars.animation : _idSeed++); //try to find a unique ID so that sessionStorage can be mapped to it (otherwise, for example, all the embedded codepens on a page would share the same settings). So if no id is defined, see if there's a string-based "animation" defined. Last of all, we default to a numeric counter that we increment. _lookup[vars.id + ""] = this; if (vars.animation && !_recording && vars.globalSync !== true) { //if the user calls create() and passes in an animation AFTER the initial recording time has elapsed, there's a good chance the animation won't be in the recordedRoot, so we change the default globalSync to false because that's the most intuitive behavior. vars.globalSync = false; } //GENERAL/UTILITY var _self = this, root = _createRootElement(vars.container, vars.minimal, vars.css), find = function(s) { return root.querySelector(s); }, record = function(key, value) { if (vars.persist !== false && typeof(sessionStorage) !== "undefined") { sessionStorage.setItem("gs-dev-" + key + vars.id, value); } return value; }, recall = function(key) { var value; if (vars.persist !== false && typeof(sessionStorage) !== "undefined") { value = sessionStorage.getItem("gs-dev-" + key + vars.id); return (key === "animation") ? value : (key === "loop") ? (value === "true") : parseFloat(value); // handle data typing too. } }, //SCRUBBER/PROGRESS playhead = find(".playhead"), timelineTrack = find(".timeline-track"), progressBar = find(".progress-bar"), timeLabel = find(".time"), durationLabel = find(".duration"), pixelToTimeRatio, timeAtDragStart, dragged, skipDragUpdates, progress = 0, //spits back a common onPress function for anything that's dragged along the timeline (playhead, inPoint, outPoint). The originRatio is a value from 0-1 indicating how far along the x-axis the origin is located (0.5 is in the center, 0 is left, 1 is on right side). limitElement is optional, and sets the bounds such that the element can't be dragged past the limitElement. onPressTimeline = function(element, originRatio, limitToInOut) { return function(e) { var trackBounds = timelineTrack.getBoundingClientRect(), elementBounds = element.getBoundingClientRect(), left = elementBounds.width * originRatio, x = element._gsTransform.x, minX = trackBounds.left - elementBounds.left - left + x, maxX = trackBounds.right - elementBounds.right + (elementBounds.width - left) + x, unlimitedMinX = minX, limitBounds; if (limitToInOut) { if (element !== inPoint) { limitBounds = inPoint.getBoundingClientRect(); if (limitBounds.left) { //if inPoint is hidden (like display:none), ignore. minX += (limitBounds.left + limitBounds.width) - trackBounds.left; } } if (element !== outPoint) { limitBounds = outPoint.getBoundingClientRect(); if (limitBounds.left) { //if outPoint is hidden (like display:none), ignore. maxX -= (trackBounds.left + trackBounds.width) - limitBounds.left; } } } pausedWhenDragStarted = paused; this.applyBounds({minX:minX, maxX:maxX}); //_merge(); pixelToTimeRatio = linkedAnimation.duration() / trackBounds.width; timeAtDragStart = -unlimitedMinX * pixelToTimeRatio; if (!skipDragUpdates) { linkedAnimation.pause(timeAtDragStart + pixelToTimeRatio * this.x); } else { linkedAnimation.pause(); } if (this.target === playhead) { if (this.activated) { this.allowEventDefault = false; } this.activated = true; } dragged = true; }; }, progressDrag = Draggable.create(playhead, { type:"x", cursor: "ew-resize", allowNativeTouchScrolling: false, allowEventDefault:true, //otherwise, when dragged outside an iframe, the mouseup doesn't bubble up so it could seem "stuck" to the mouse. onPress: onPressTimeline(playhead, 0.5, true), onDrag: function() { var time = timeAtDragStart + pixelToTimeRatio * this.x; if (time < 0) { time = 0; } else if (time > linkedAnimation._duration) { time = linkedAnimation._duration; } if (!skipDragUpdates) { linkedAnimation.time(time); } progressBar.style.width = Math.min(outProgress - inProgress, Math.max(0, (time / linkedAnimation._duration) * 100 - inProgress)) + "%"; timeLabel.innerHTML = time.toFixed(2); }, onRelease: function(e) { if (!paused) { linkedAnimation.resume(); } } })[0], inPoint = find(".in-point"), outPoint = find(".out-point"), resetInOut = function() { inProgress = 0; outProgress = 100; inPoint.style.left = "0%"; outPoint.style.left = "100%"; record("in", inProgress); record("out", outProgress); updateProgress(true); }, inProgress = 0, outProgress = 100, pausedWhenDragStarted, inDrag = Draggable.create(inPoint, { type:"x", cursor:"ew-resize", zIndexBoost:false, allowNativeTouchScrolling: false, allowEventDefault:true, //otherwise, when dragged outside an iframe, the mouseup doesn't bubble up so it could seem "stuck" to the mouse. onPress:onPressTimeline(inPoint, 1, true), onDoubleClick: resetInOut, onDrag: function() { inProgress = (timeAtDragStart + pixelToTimeRatio * this.x) / linkedAnimation.duration() * 100; linkedAnimation.progress(inProgress / 100); updateProgress(true); }, onRelease: function() { if (inProgress < 0) { inProgress = 0; } _clearSelection(); //for responsiveness, convert the px-based transform into %-based left position. inPoint.style.left = inProgress + "%"; record("in", inProgress); TweenLite.set(inPoint, {x:0, data:"root", display:"block"}); //set display:block so that it remains visible even when the minimal skin is enabled. if (!paused) { linkedAnimation.resume(); } } })[0], outDrag = Draggable.create(outPoint, { type:"x", cursor:"ew-resize", allowNativeTouchScrolling: false, allowEventDefault:true, //otherwise, when dragged outside an iframe, the mouseup doesn't bubble up so it could seem "stuck" to the mouse. zIndexBoost:false, onPress:onPressTimeline(outPoint, 0, true), onDoubleClick: resetInOut, onDrag: function() { outProgress = (timeAtDragStart + pixelToTimeRatio * this.x) / linkedAnimation.duration() * 100; linkedAnimation.progress(outProgress / 100); updateProgress(true); }, onRelease: function() { if (outProgress > 100) { outProgress = 100; } _clearSelection(); //for responsiveness, convert the px-based transform into %-based left position. outPoint.style.left = outProgress + "%"; record("out", outProgress); TweenLite.set(outPoint, {x:0, data:"root", display:"block"}); //set display:block so that it remains visible even when the minimal skin is enabled. if (!pausedWhenDragStarted) { play(); linkedAnimation.resume(); } } })[0], updateProgress = function(force) { if (progressDrag.isPressed && !force) { return; } var p = (!loopEnabled && selectedAnimation._repeat === -1) ? selectedAnimation.totalTime() / selectedAnimation.duration() * 100 : (linkedAnimation.progress() * 100) || 0, repeatDelayPhase = (selectedAnimation._repeat && selectedAnimation._repeatDelay && selectedAnimation.totalTime() % (selectedAnimation.duration() + selectedAnimation._repeatDelay) > selectedAnimation.duration()); if (p > 100) { p = 100; } if (p >= outProgress) { if (loopEnabled && !linkedAnimation.paused() && !progressDrag.isDragging) { if (!repeatDelayPhase) { p = inProgress; if (linkedAnimation.target === selectedAnimation) { //in case there are callbacks on the timeline, when we jump back to the start we should seek() so that the playhead doesn't drag [backward] past those and trigger them. linkedAnimation.target.seek(startTime + ((endTime - startTime) * inProgress / 100)); } if (selectedAnimation._repeat > 0 && !inProgress && outProgress === 100) { if (selectedAnimation.totalProgress() === 1) { linkedAnimation.totalProgress(0, true).resume(); } } else { linkedAnimation.progress(p / 100, true).resume(); } } } else { if (p !== outProgress || selectedAnimation._repeat === -1) { p = outProgress; linkedAnimation.progress(p / 100); } if (!paused && (selectedAnimation.totalProgress() === 1 || selectedAnimation._repeat === -1)) { pause(); } } } else if (p < inProgress) { p = inProgress; linkedAnimation.progress(p / 100, true); } if (p !== progress || force) { progressBar.style.left = inProgress + "%"; progressBar.style.width = Math.max(0, p - inProgress) + "%"; playhead.style.left = p + "%"; timeLabel.innerHTML = linkedAnimation._time.toFixed(2); durationLabel.innerHTML = linkedAnimation._duration.toFixed(2); if (dragged) { playhead.style.transform = "translate(-50%,0)"; playhead._gsTransform.x = 0; playhead._gsTransform.xPercent = -50; dragged = false; } progress = p; } else if (linkedAnimation._paused !== paused) { //like if the user has an addPause() in the middle of the animation. togglePlayPause(); } }, onPressSeekBar = function(e) { if (progressDrag.isPressed) { return; } var bounds = e.target.getBoundingClientRect(), x = (e.changedTouches ? e.changedTouches[0] : e).clientX, p = ((x - bounds.left) / bounds.width) * 100; if (p < inProgress) { inProgress = p = Math.max(0, p); inPoint.style.left = inProgress + "%"; inDrag.startDrag(e); return; } else if (p > outProgress) { outProgress = p = Math.min(100, p); outPoint.style.left = outProgress + "%"; outDrag.startDrag(e); return; } linkedAnimation.progress(p / 100).pause(); updateProgress(true); progressDrag.startDrag(e); }, //PLAY/PAUSE button playPauseButton = find(".play-pause"), playPauseMorph = _buildPlayPauseMorph(playPauseButton), paused = false, play = function() { if (linkedAnimation.progress() >= outProgress / 100) { if (linkedAnimation.target === selectedAnimation) { //in case there are callbacks on the timeline, when we jump back to the start we should seek() so that the playhead doesn't drag [backward] past those and trigger them. linkedAnimation.target.seek(startTime + ((endTime - startTime) * inProgress / 100)); } if (linkedAnimation._repeat && !inProgress) { linkedAnimation.totalProgress(0, true); //for repeating animations, don't get stuck in the last iteration - jump all the way back to the start. } else { linkedAnimation.progress(inProgress / 100, true); } } playPauseMorph.play(); linkedAnimation.resume(); if (paused) { _self.update(); } paused = false; }, pause = function() { playPauseMorph.reverse(); if (linkedAnimation) { linkedAnimation.pause(); } paused = true; }, togglePlayPause = function() { if (paused) { play(); } else { pause(); } }, //REWIND button onPressRewind = function(e) { if (progressDrag.isPressed) { return; } //_self.update(); if (linkedAnimation.target === selectedAnimation) { //in case there are callbacks on the timeline, when we jump back to the start we should seek() so that the playhead doesn't drag [backward] past those and trigger them. linkedAnimation.target.seek(startTime + ((endTime - startTime) * inProgress / 100)); } linkedAnimation.progress(inProgress / 100, true); if (!paused) { linkedAnimation.resume(); } }, //LOOP button loopButton = find(".loop"), loopAnimation = _buildLoopAnimation(loopButton), loopEnabled, loop = function(value) { loopEnabled = value; record("loop", loopEnabled); if (loopEnabled) { loopAnimation.play(); if (linkedAnimation.progress() >= outProgress / 100) { if (linkedAnimation.target === selectedAnimation) { //in case there are callbacks on the timeline, when we jump back to the start we should seek() so that the playhead doesn't drag [backward] past those and trigger them. linkedAnimation.target.seek(startTime + ((endTime - startTime) * inProgress / 100)); } if (selectedAnimation._repeat && !inProgress && outProgress === 100) { linkedAnimation.totalProgress(0, true); } else { linkedAnimation.progress(inProgress / 100, true); } play(); } } else { loopAnimation.reverse(); } }, toggleLoop = function() { loop(!loopEnabled); }, //ANIMATIONS list list = find(".animation-list"), animationLabel = find(".animation-label"), selectedAnimation, //the currently selected animation linkedAnimation, //the animation that's linked to all the controls and scrubber. This is always _rootTween if globalSync is true, so it can be different than the selectedAnimation! declaredAnimation, //whatever the user defines in the config object initially (often this will be null). If the user defines a string, it'll be resolved to a real Animation instance for this variable. startTime, endTime, updateList = function() { var animations = _getChildrenOf((declaredAnimation && vars.globalSync === false) ? declaredAnimation : _recordedRoot, true), options = list.children, matches = 0, option, i; if (declaredAnimation && vars.globalSync === false) { animations.unshift(declaredAnimation); } else if (!vars.hideGlobalTimeline) { animations.unshift(_recordedRoot); } for (i = 0; i < animations.length; i++) { option = options[i] || _createElement("option", list); option.animation = animations[i]; matches = (i && animations[i].vars.id === animations[i-1].vars.id) ? matches + 1 : 0; option.setAttribute("value", (option.innerHTML = animations[i].vars.id + (matches ? " [" + matches + "]" : (animations[i+1] && animations[i+1].vars.id === animations[i].vars.id) ? " [0]" : ""))); } for (; i < options.length; i++) { list.removeChild(options[i]); } }, animation = function(anim) { var ts = parseFloat(timeScale.options[timeScale.selectedIndex].value) || 1, tl, maxDuration; if (!arguments.length) { return selectedAnimation; } if (typeof(anim) === "string") { anim = _getAnimationById(anim); } //console.log("animation() ", anim.vars.id); if (!(anim instanceof Animation)) { console.log("GSDevTools error: invalid animation."); } if (anim === selectedAnimation) { return; } if (selectedAnimation) { selectedAnimation._inProgress = inProgress; selectedAnimation._outProgress = outProgress; } selectedAnimation = anim; if (linkedAnimation) { ts = linkedAnimation.timeScale(); if (linkedAnimation.target === declaredAnimation) { declaredAnimation.resume(); linkedAnimation.kill(); } } inProgress = selectedAnimation._inProgress || 0; outProgress = selectedAnimation._outProgress || 100; inPoint.style.left = inProgress + "%"; outPoint.style.left = outProgress + "%"; if (_fullyInitialized) { //don't record inProgress/outProgress unless we're fully instantiated because people may call GSDevTools.create() before creating/defining their animations, thus the inTime/outTime may not exist yet. record("animation", selectedAnimation.vars.id); record("in", inProgress); record("out", outProgress); } startTime = 0; maxDuration = Math.min(1000, vars.maxDuration || 1000, _getClippedDuration(selectedAnimation)); if (selectedAnimation === _recordedRoot || vars.globalSync !== false) { _merge(); linkedAnimation = _rootTween; if (_rootInstance && _rootInstance !== _self) { console.log("Error: GSDevTools can only have one instance that's globally synchronized."); } _rootInstance = _self; //_recording = true; if (selectedAnimation !== _recordedRoot) { tl = selectedAnimation; endTime = tl.totalDuration(); if (endTime > 99999999) { //in the case of an infinitely repeating animation, just use a single iteration's duration instead. endTime = tl.duration(); } while (tl.timeline.timeline) { startTime = (startTime / tl._timeScale) + tl._startTime; endTime = (endTime / tl._timeScale) + tl._startTime; tl = tl.timeline; } } else { endTime = _recordedRoot.duration(); } if (endTime - startTime > maxDuration) { //cap end time at 1000 because it doesn't seem reasonable to accommodate super long stuff. endTime = startTime + maxDuration; } _recordedRoot.pause(startTime); _rootTween.vars.time = endTime; _rootTween.invalidate(); _rootTween.duration(endTime - startTime).timeScale(ts); //wait for a tick before starting because some browsers freeze things immediately following a . e.target.blur(); } if (paused) { play(); } }, //TIMESCALE button timeScale = find(".time-scale select"), timeScaleLabel = find(".time-scale-label"), onChangeTimeScale = function(e) { var ts = parseFloat(timeScale.options[timeScale.selectedIndex].value) || 1; linkedAnimation.timeScale(ts); record("timeScale", ts); if (!paused) { if (linkedAnimation.progress() >= outProgress / 100) { if (linkedAnimation.target === selectedAnimation) { //in case there are callbacks on the timeline, when we jump back to the start we should seek() so that the playhead doesn't drag [backward] past those and trigger them. linkedAnimation.target.seek(startTime + ((endTime - startTime) * inProgress / 100)); } linkedAnimation.progress(inProgress / 100, true).pause(); } else { linkedAnimation.pause(); } TweenLite.delayedCall(0.01, function() { linkedAnimation.resume(); }); } timeScaleLabel.innerHTML = ts + "x"; if (timeScale.blur) { //so that if an option is selected, and then the user tries to hit the up/down arrow, it doesn't just try selecting something else in the , or