/*! * Observer 3.12.2 * https://greensock.com * * @license Copyright 2008-2023, GreenSock. All rights reserved. * Subject to the terms at https://greensock.com/standard-license or for * Club GreenSock members, the agreement issued with that membership. * @author: Jack Doyle, jack@greensock.com */ /* eslint-disable */ let gsap, _coreInitted, _clamp, _win, _doc, _docEl, _body, _isTouch, _pointerType, ScrollTrigger, _root, _normalizer, _eventTypes, _context, _getGSAP = () => gsap || (typeof(window) !== "undefined" && (gsap = window.gsap) && gsap.registerPlugin && gsap), _startup = 1, _observers = [], _scrollers = [], _proxies = [], _getTime = Date.now, _bridge = (name, value) => value, _integrate = () => { let core = ScrollTrigger.core, data = core.bridge || {}, scrollers = core._scrollers, proxies = core._proxies; scrollers.push(..._scrollers); proxies.push(..._proxies); _scrollers = scrollers; _proxies = proxies; _bridge = (name, value) => data[name](value); }, _getProxyProp = (element, property) => ~_proxies.indexOf(element) && _proxies[_proxies.indexOf(element) + 1][property], _isViewport = el => !!~_root.indexOf(el), _addListener = (element, type, func, nonPassive, capture) => element.addEventListener(type, func, {passive: !nonPassive, capture: !!capture}), _removeListener = (element, type, func, capture) => element.removeEventListener(type, func, !!capture), _scrollLeft = "scrollLeft", _scrollTop = "scrollTop", _onScroll = () => (_normalizer && _normalizer.isPressed) || _scrollers.cache++, _scrollCacheFunc = (f, doNotCache) => { let cachingFunc = value => { // since reading the scrollTop/scrollLeft/pageOffsetY/pageOffsetX can trigger a layout, this function allows us to cache the value so it only gets read fresh after a "scroll" event fires (or while we're refreshing because that can lengthen the page and alter the scroll position). when "soft" is true, that means don't actually set the scroll, but cache the new value instead (useful in ScrollSmoother) if (value || value === 0) { _startup && (_win.history.scrollRestoration = "manual"); // otherwise the new position will get overwritten by the browser onload. let isNormalizing = _normalizer && _normalizer.isPressed; value = cachingFunc.v = Math.round(value) || (_normalizer && _normalizer.iOS ? 1 : 0); //TODO: iOS Bug: if you allow it to go to 0, Safari can start to report super strange (wildly inaccurate) touch positions! f(value); cachingFunc.cacheID = _scrollers.cache; isNormalizing && _bridge("ss", value); // set scroll (notify ScrollTrigger so it can dispatch a "scrollStart" event if necessary } else if (doNotCache || _scrollers.cache !== cachingFunc.cacheID || _bridge("ref")) { cachingFunc.cacheID = _scrollers.cache; cachingFunc.v = f(); } return cachingFunc.v + cachingFunc.offset; }; cachingFunc.offset = 0; return f && cachingFunc; }, _horizontal = {s: _scrollLeft, p: "left", p2: "Left", os: "right", os2: "Right", d: "width", d2: "Width", a: "x", sc: _scrollCacheFunc(function(value) { return arguments.length ? _win.scrollTo(value, _vertical.sc()) : _win.pageXOffset || _doc[_scrollLeft] || _docEl[_scrollLeft] || _body[_scrollLeft] || 0})}, _vertical = {s: _scrollTop, p: "top", p2: "Top", os: "bottom", os2: "Bottom", d: "height", d2: "Height", a: "y", op: _horizontal, sc: _scrollCacheFunc(function(value) { return arguments.length ? _win.scrollTo(_horizontal.sc(), value) : _win.pageYOffset || _doc[_scrollTop] || _docEl[_scrollTop] || _body[_scrollTop] || 0})}, _getTarget = (t, self) => ((self && self._ctx && self._ctx.selector) || gsap.utils.toArray)(t)[0] || (typeof(t) === "string" && gsap.config().nullTargetWarn !== false ? console.warn("Element not found:", t) : null), _getScrollFunc = (element, {s, sc}) => { // we store the scroller functions in an alternating sequenced Array like [element, verticalScrollFunc, horizontalScrollFunc, ...] so that we can minimize memory, maximize performance, and we also record the last position as a ".rec" property in order to revert to that after refreshing to ensure things don't shift around. _isViewport(element) && (element = _doc.scrollingElement || _docEl); let i = _scrollers.indexOf(element), offset = sc === _vertical.sc ? 1 : 2; !~i && (i = _scrollers.push(element) - 1); _scrollers[i + offset] || _addListener(element, "scroll", _onScroll); // clear the cache when a scroll occurs let prev = _scrollers[i + offset], func = prev || (_scrollers[i + offset] = _scrollCacheFunc(_getProxyProp(element, s), true) || (_isViewport(element) ? sc : _scrollCacheFunc(function(value) { return arguments.length ? (element[s] = value) : element[s]; }))); func.target = element; prev || (func.smooth = gsap.getProperty(element, "scrollBehavior") === "smooth"); // only set it the first time (don't reset every time a scrollFunc is requested because perhaps it happens during a refresh() when it's disabled in ScrollTrigger. return func; }, _getVelocityProp = (value, minTimeRefresh, useDelta) => { let v1 = value, v2 = value, t1 = _getTime(), t2 = t1, min = minTimeRefresh || 50, dropToZeroTime = Math.max(500, min * 3), update = (value, force) => { let t = _getTime(); if (force || t - t1 > min) { v2 = v1; v1 = value; t2 = t1; t1 = t; } else if (useDelta) { v1 += value; } else { // not totally necessary, but makes it a bit more accurate by adjusting the v1 value according to the new slope. This way we're not just ignoring the incoming data. Removing for now because it doesn't seem to make much practical difference and it's probably not worth the kb. v1 = v2 + (value - v2) / (t - t2) * (t1 - t2); } }, reset = () => { v2 = v1 = useDelta ? 0 : v1; t2 = t1 = 0; }, getVelocity = latestValue => { let tOld = t2, vOld = v2, t = _getTime(); (latestValue || latestValue === 0) && latestValue !== v1 && update(latestValue); return (t1 === t2 || t - t2 > dropToZeroTime) ? 0 : (v1 + (useDelta ? vOld : -vOld)) / ((useDelta ? t : t1) - tOld) * 1000; }; return {update, reset, getVelocity}; }, _getEvent = (e, preventDefault) => { preventDefault && !e._gsapAllow && e.preventDefault(); return e.changedTouches ? e.changedTouches[0] : e; }, _getAbsoluteMax = a => { let max = Math.max(...a), min = Math.min(...a); return Math.abs(max) >= Math.abs(min) ? max : min; }, _setScrollTrigger = () => { ScrollTrigger = gsap.core.globals().ScrollTrigger; ScrollTrigger && ScrollTrigger.core && _integrate(); }, _initCore = core => { gsap = core || _getGSAP(); if (gsap && typeof(document) !== "undefined" && document.body) { _win = window; _doc = document; _docEl = _doc.documentElement; _body = _doc.body; _root = [_win, _doc, _docEl, _body]; _clamp = gsap.utils.clamp; _context = gsap.core.context || function() {}; _pointerType = "onpointerenter" in _body ? "pointer" : "mouse"; // isTouch is 0 if no touch, 1 if ONLY touch, and 2 if it can accommodate touch but also other types like mouse/pointer. _isTouch = Observer.isTouch = _win.matchMedia && _win.matchMedia("(hover: none), (pointer: coarse)").matches ? 1 : ("ontouchstart" in _win || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0) ? 2 : 0; _eventTypes = Observer.eventTypes = ("ontouchstart" in _docEl ? "touchstart,touchmove,touchcancel,touchend" : !("onpointerdown" in _docEl) ? "mousedown,mousemove,mouseup,mouseup" : "pointerdown,pointermove,pointercancel,pointerup").split(","); setTimeout(() => _startup = 0, 500); _setScrollTrigger(); _coreInitted = 1; } return _coreInitted; }; _horizontal.op = _vertical; _scrollers.cache = 0; export class Observer { constructor(vars) { this.init(vars); } init(vars) { _coreInitted || _initCore(gsap) || console.warn("Please gsap.registerPlugin(Observer)"); ScrollTrigger || _setScrollTrigger(); let {tolerance, dragMinimum, type, target, lineHeight, debounce, preventDefault, onStop, onStopDelay, ignore, wheelSpeed, event, onDragStart, onDragEnd, onDrag, onPress, onRelease, onRight, onLeft, onUp, onDown, onChangeX, onChangeY, onChange, onToggleX, onToggleY, onHover, onHoverEnd, onMove, ignoreCheck, isNormalizer, onGestureStart, onGestureEnd, onWheel, onEnable, onDisable, onClick, scrollSpeed, capture, allowClicks, lockAxis, onLockAxis} = vars; this.target = target = _getTarget(target) || _docEl; this.vars = vars; ignore && (ignore = gsap.utils.toArray(ignore)); tolerance = tolerance || 1e-9; dragMinimum = dragMinimum || 0; wheelSpeed = wheelSpeed || 1; scrollSpeed = scrollSpeed || 1; type = type || "wheel,touch,pointer"; debounce = debounce !== false; lineHeight || (lineHeight = parseFloat(_win.getComputedStyle(_body).lineHeight) || 22); // note: browser may report "normal", so default to 22. let id, onStopDelayedCall, dragged, moved, wheeled, locked, axis, self = this, prevDeltaX = 0, prevDeltaY = 0, scrollFuncX = _getScrollFunc(target, _horizontal), scrollFuncY = _getScrollFunc(target, _vertical), scrollX = scrollFuncX(), scrollY = scrollFuncY(), limitToTouch = ~type.indexOf("touch") && !~type.indexOf("pointer") && _eventTypes[0] === "pointerdown", // for devices that accommodate mouse events and touch events, we need to distinguish. isViewport = _isViewport(target), ownerDoc = target.ownerDocument || _doc, deltaX = [0, 0, 0], // wheel, scroll, pointer/touch deltaY = [0, 0, 0], onClickTime = 0, clickCapture = () => onClickTime = _getTime(), _ignoreCheck = (e, isPointerOrTouch) => (self.event = e) && (ignore && ~ignore.indexOf(e.target)) || (isPointerOrTouch && limitToTouch && e.pointerType !== "touch") || (ignoreCheck && ignoreCheck(e, isPointerOrTouch)), onStopFunc = () => { self._vx.reset(); self._vy.reset(); onStopDelayedCall.pause(); onStop && onStop(self); }, update = () => { let dx = self.deltaX = _getAbsoluteMax(deltaX), dy = self.deltaY = _getAbsoluteMax(deltaY), changedX = Math.abs(dx) >= tolerance, changedY = Math.abs(dy) >= tolerance; onChange && (changedX || changedY) && onChange(self, dx, dy, deltaX, deltaY); // in ScrollTrigger.normalizeScroll(), we need to know if it was touch/pointer so we need access to the deltaX/deltaY Arrays before we clear them out. if (changedX) { onRight && self.deltaX > 0 && onRight(self); onLeft && self.deltaX < 0 && onLeft(self); onChangeX && onChangeX(self); onToggleX && ((self.deltaX < 0) !== (prevDeltaX < 0)) && onToggleX(self); prevDeltaX = self.deltaX; deltaX[0] = deltaX[1] = deltaX[2] = 0 } if (changedY) { onDown && self.deltaY > 0 && onDown(self); onUp && self.deltaY < 0 && onUp(self); onChangeY && onChangeY(self); onToggleY && ((self.deltaY < 0) !== (prevDeltaY < 0)) && onToggleY(self); prevDeltaY = self.deltaY; deltaY[0] = deltaY[1] = deltaY[2] = 0 } if (moved || dragged) { onMove && onMove(self); if (dragged) { onDrag(self); dragged = false; } moved = false; } locked && !(locked = false) && onLockAxis && onLockAxis(self); if (wheeled) { onWheel(self); wheeled = false; } id = 0; }, onDelta = (x, y, index) => { deltaX[index] += x; deltaY[index] += y; self._vx.update(x); self._vy.update(y); debounce ? id || (id = requestAnimationFrame(update)) : update(); }, onTouchOrPointerDelta = (x, y) => { if (lockAxis && !axis) { self.axis = axis = Math.abs(x) > Math.abs(y) ? "x" : "y"; locked = true; } if (axis !== "y") { deltaX[2] += x; self._vx.update(x, true); // update the velocity as frequently as possible instead of in the debounced function so that very quick touch-scrolls (flicks) feel natural. If it's the mouse/touch/pointer, force it so that we get snappy/accurate momentum scroll. } if (axis !== "x") { deltaY[2] += y; self._vy.update(y, true); } debounce ? id || (id = requestAnimationFrame(update)) : update(); }, _onDrag = e => { if (_ignoreCheck(e, 1)) {return;} e = _getEvent(e, preventDefault); let x = e.clientX, y = e.clientY, dx = x - self.x, dy = y - self.y, isDragging = self.isDragging; self.x = x; self.y = y; if (isDragging || Math.abs(self.startX - x) >= dragMinimum || Math.abs(self.startY - y) >= dragMinimum) { onDrag && (dragged = true); isDragging || (self.isDragging = true); onTouchOrPointerDelta(dx, dy); isDragging || onDragStart && onDragStart(self); } }, _onPress = self.onPress = e => { if (_ignoreCheck(e, 1) || (e && e.button)) {return;} self.axis = axis = null; onStopDelayedCall.pause(); self.isPressed = true; e = _getEvent(e); // note: may need to preventDefault(?) Won't side-scroll on iOS Safari if we do, though. prevDeltaX = prevDeltaY = 0; self.startX = self.x = e.clientX; self.startY = self.y = e.clientY; self._vx.reset(); // otherwise the t2 may be stale if the user touches and flicks super fast and releases in less than 2 requestAnimationFrame ticks, causing velocity to be 0. self._vy.reset(); _addListener(isNormalizer ? target : ownerDoc, _eventTypes[1], _onDrag, preventDefault, true); self.deltaX = self.deltaY = 0; onPress && onPress(self); }, _onRelease = self.onRelease = e => { if (_ignoreCheck(e, 1)) {return;} _removeListener(isNormalizer ? target : ownerDoc, _eventTypes[1], _onDrag, true); let isTrackingDrag = !isNaN(self.y - self.startY), wasDragging = self.isDragging && (Math.abs(self.x - self.startX) > 3 || Math.abs(self.y - self.startY) > 3), // some touch devices need some wiggle room in terms of sensing clicks - the finger may move a few pixels. eventData = _getEvent(e); if (!wasDragging && isTrackingDrag) { self._vx.reset(); self._vy.reset(); if (preventDefault && allowClicks) { gsap.delayedCall(0.08, () => { // some browsers (like Firefox) won't trust script-generated clicks, so if the user tries to click on a video to play it, for example, it simply won't work. Since a regular "click" event will most likely be generated anyway (one that has its isTrusted flag set to true), we must slightly delay our script-generated click so that the "real"/trusted one is prioritized. Remember, when there are duplicate events in quick succession, we suppress all but the first one. Some browsers don't even trigger the "real" one at all, so our synthetic one is a safety valve that ensures that no matter what, a click event does get dispatched. if (_getTime() - onClickTime > 300 && !e.defaultPrevented) { if (e.target.click) { //some browsers (like mobile Safari) don't properly trigger the click event e.target.click(); } else if (ownerDoc.createEvent) { let syntheticEvent = ownerDoc.createEvent("MouseEvents"); syntheticEvent.initMouseEvent("click", true, true, _win, 1, eventData.screenX, eventData.screenY, eventData.clientX, eventData.clientY, false, false, false, false, 0, null); e.target.dispatchEvent(syntheticEvent); } } }); } } self.isDragging = self.isGesturing = self.isPressed = false; onStop && !isNormalizer && onStopDelayedCall.restart(true); onDragEnd && wasDragging && onDragEnd(self); onRelease && onRelease(self, wasDragging); }, _onGestureStart = e => e.touches && e.touches.length > 1 && (self.isGesturing = true) && onGestureStart(e, self.isDragging), _onGestureEnd = () => (self.isGesturing = false) || onGestureEnd(self), onScroll = e => { if (_ignoreCheck(e)) {return;} let x = scrollFuncX(), y = scrollFuncY(); onDelta((x - scrollX) * scrollSpeed, (y - scrollY) * scrollSpeed, 1); scrollX = x; scrollY = y; onStop && onStopDelayedCall.restart(true); }, _onWheel = e => { if (_ignoreCheck(e)) {return;} e = _getEvent(e, preventDefault); onWheel && (wheeled = true); let multiplier = (e.deltaMode === 1 ? lineHeight : e.deltaMode === 2 ? _win.innerHeight : 1) * wheelSpeed; onDelta(e.deltaX * multiplier, e.deltaY * multiplier, 0); onStop && !isNormalizer && onStopDelayedCall.restart(true); }, _onMove = e => { if (_ignoreCheck(e)) {return;} let x = e.clientX, y = e.clientY, dx = x - self.x, dy = y - self.y; self.x = x; self.y = y; moved = true; (dx || dy) && onTouchOrPointerDelta(dx, dy); }, _onHover = e => {self.event = e; onHover(self);}, _onHoverEnd = e => {self.event = e; onHoverEnd(self);}, _onClick = e => _ignoreCheck(e) || (_getEvent(e, preventDefault) && onClick(self)); onStopDelayedCall = self._dc = gsap.delayedCall(onStopDelay || 0.25, onStopFunc).pause(); self.deltaX = self.deltaY = 0; self._vx = _getVelocityProp(0, 50, true); self._vy = _getVelocityProp(0, 50, true); self.scrollX = scrollFuncX; self.scrollY = scrollFuncY; self.isDragging = self.isGesturing = self.isPressed = false; _context(this); self.enable = e => { if (!self.isEnabled) { _addListener(isViewport ? ownerDoc : target, "scroll", _onScroll); type.indexOf("scroll") >= 0 && _addListener(isViewport ? ownerDoc : target, "scroll", onScroll, preventDefault, capture); type.indexOf("wheel") >= 0 && _addListener(target, "wheel", _onWheel, preventDefault, capture); if ((type.indexOf("touch") >= 0 && _isTouch) || type.indexOf("pointer") >= 0) { _addListener(target, _eventTypes[0], _onPress, preventDefault, capture); _addListener(ownerDoc, _eventTypes[2], _onRelease); _addListener(ownerDoc, _eventTypes[3], _onRelease); allowClicks && _addListener(target, "click", clickCapture, false, true); onClick && _addListener(target, "click", _onClick); onGestureStart && _addListener(ownerDoc, "gesturestart", _onGestureStart); onGestureEnd && _addListener(ownerDoc, "gestureend", _onGestureEnd); onHover && _addListener(target, _pointerType + "enter", _onHover); onHoverEnd && _addListener(target, _pointerType + "leave", _onHoverEnd); onMove && _addListener(target, _pointerType + "move", _onMove); } self.isEnabled = true; e && e.type && _onPress(e); onEnable && onEnable(self); } return self; }; self.disable = () => { if (self.isEnabled) { // only remove the _onScroll listener if there aren't any others that rely on the functionality. _observers.filter(o => o !== self && _isViewport(o.target)).length || _removeListener(isViewport ? ownerDoc : target, "scroll", _onScroll); if (self.isPressed) { self._vx.reset(); self._vy.reset(); _removeListener(isNormalizer ? target : ownerDoc, _eventTypes[1], _onDrag, true); } _removeListener(isViewport ? ownerDoc : target, "scroll", onScroll, capture); _removeListener(target, "wheel", _onWheel, capture); _removeListener(target, _eventTypes[0], _onPress, capture); _removeListener(ownerDoc, _eventTypes[2], _onRelease); _removeListener(ownerDoc, _eventTypes[3], _onRelease); _removeListener(target, "click", clickCapture, true); _removeListener(target, "click", _onClick); _removeListener(ownerDoc, "gesturestart", _onGestureStart); _removeListener(ownerDoc, "gestureend", _onGestureEnd); _removeListener(target, _pointerType + "enter", _onHover); _removeListener(target, _pointerType + "leave", _onHoverEnd); _removeListener(target, _pointerType + "move", _onMove); self.isEnabled = self.isPressed = self.isDragging = false; onDisable && onDisable(self); } }; self.kill = self.revert = () => { self.disable(); let i = _observers.indexOf(self); i >= 0 && _observers.splice(i, 1); _normalizer === self && (_normalizer = 0); } _observers.push(self); isNormalizer && _isViewport(target) && (_normalizer = self); self.enable(event); } get velocityX() { return this._vx.getVelocity(); } get velocityY() { return this._vy.getVelocity(); } } Observer.version = "3.12.2"; Observer.create = vars => new Observer(vars); Observer.register = _initCore; Observer.getAll = () => _observers.slice(); Observer.getById = id => _observers.filter(o => o.vars.id === id)[0]; _getGSAP() && gsap.registerPlugin(Observer); export { Observer as default, _isViewport, _scrollers, _getScrollFunc, _getProxyProp, _proxies, _getVelocityProp, _vertical, _horizontal, _getTarget };