UNPKG

scroll-captain

Version:

Scroll Captain is a JS library for creating scroll-triggered animations.

1 lines 31.3 kB
class ScrollCaptain { static instancesCompleted = 0; static totalInstances = 0; constructor(t, i) { ScrollCaptain.instanceCount++, this.defaultOptions = { initAttr: "data-scrollcaptain", triggerSuffix: null, triggerIndex: -1, triggerPosition: 0, top: null, bottom: null, cssSpace: null, onInit: null, onEnter: null, onLeave: null, onScroll: null, onResize: null, updateOnResize: null, devMode: !1, breakpoints: null }, this.cssProperties = { transform: ["translateX", "translateY", "translateZ", "rotate", "rotateX", "rotateY", "rotateZ", "scale", "scaleX", "scaleY", "scaleZ", "skewX", "skewY", "perspective"], colors: ["backgroundColor", "color", "borderColor", "borderTopColor", "borderRightColor", "borderBottomColor", "borderLeftColor", "fill", "stroke"], others: ["opacity"] }, this.options = i, this.settings = this.mergeOptions(this.defaultOptions, this.options), this.currentSettings = this.deepCopy(this.settings), this.settingsBackup = this.deepCopy(this.settings), this.$root = t, this.$trigger = null !== this.settings.triggerSuffix ? this.$root.querySelector(`[${this.settings.initAttr}=${this.settings.triggerSuffix}]`) : this.$root, this.initial = !0, this.isResizing = !1, this.isObserverConnected = !1, this.triggerSuffix = this.$trigger.getAttribute(this.settings.initAttr), this.onscroll = !1, this.initialCssProperties = {}, this.animations = {}, this.viewportHeight = window.innerHeight, this.viewportWidth = window.innerWidth, this.viewportOuterWidth = window.outerWidth, this.currentBreakpoint = null, this.stickyAnimations = [], this.requiredUpdates = { observer: !1, cssSpace: !1, onscroll: [], stickyElements: [], animations: [], transition: [] }, this.startPosition = null, this.top = [0, "px"], this.bottom = [0, "px"], this.animationArea = null, this.animationProgress = null, this.storedProgress = null, this.animationActive = !1, this.passed = !1, this.isCrossingBorder = !1, this.isEntering = !1, this.isEnteringFirstTime = null, this.isEnteringOnTop = !1, this.isEnteringOnBottom = !1, this.isLeaving = !1, this.isLeavingOnTop = !1, this.isLeavingOnBottom = !1, this.$trigger && this.initialize(); } initialize() { this.prepareAnimation(), this.setEvents(), window.setTimeout((() => { this.observeTrigger(); }), 300); } update(t) { const i = this.currentBreakpoint; let s = !1; if (this.requiredUpdates = { observer: !1, cssSpace: !1, onscroll: [], stickyElements: [], animations: [], transition: [] }, this.isResizing) { s = t, s && null !== this.settings.breakpoints && (this.currentSettings = this.deepCopy(this.settings), this.mergeBreakpointOptions()), null !== this.settings.updateOnResize && this.settings.updateOnResize(); for (const t in this.animations) (this.stickyAnimations.includes(t) || !this.requiredUpdates.stickyElements.includes(t) && this.settings[t].sticky) && (this.requiredUpdates.stickyElements.push(t), this[`${t}Targets`].forEach((i => { this.setSticky(i, this.settings[t].sticky); }))); } if (this.isResizing && i !== this.currentBreakpoint || !this.isResizing || null !== this.settings.updateOnResize) { this.compareAndDefineUpdates(this.currentSettings, this.settings), this.requiredUpdates.animations.forEach((t => { this.defineAnimations(t); })), this.requiredUpdates.transition.forEach((t => { this[`${t}Targets`].forEach(((i, s) => { this.setTransition(t, i, s); })); })), this.onscroll = !("function" != typeof this.settings.onScroll && !Object.keys(this.animations).some((t => this.settings[t].onscroll || this.settings[t].devMode))), this.defineAnimationArea(); for (const t in this.requiredUpdates) "boolean" == typeof this.requiredUpdates[t] && ("observer" === t && (this.isObserverConnected && (this.observer.disconnect(this.$trigger), this.isObserverConnected = !1), this.defineObserverOptions()), "cssSpace" === t && this.requiredUpdates[t] && ("string" == typeof this.settings.cssSpace ? this.$root.style.setProperty(`--${this.settings.cssSpace}`, `${this.animationArea}px`) : this.$root.style.removeProperty(`--${this.currentSettings.cssSpace}`))); this.requiredUpdates.stickyElements.forEach((t => { this[`${t}Targets`].forEach((i => { this.setSticky(i, this.settings[t].sticky); })); })); } this.isObserverConnected || this.observeTrigger(), null !== this.settings.onResize && this.settings.onResize(this.animationProgress), this.currentSettings = this.deepCopy(this.settings); } mergeOptions(t, i) { for (const s of Object.keys(i)) if (i[s] instanceof Object) { const e = { targetSuffix: "target", globalTarget: !1, sticky: !1, class: null, onscroll: !1, resetOnScrollDown: !0, resetOnScrollUp: !0, easing: "linear", duration: .2, delay: null, improvePerformance: !0 }; for (const t of Object.keys(this.cssProperties)) for (let i = 0; i < this.cssProperties[t].length; i++)e[this.cssProperties[t][i]] = null; s.includes("animate") && (t[s] = e, Object.assign(i[s], this.mergeOptions(t[s] || {}, i[s]))); } return Object.assign(t || {}, i), t; } mergeBreakpointOptions() { const t = (i, s, e) => { const n = void 0 !== e ? e : []; for (const e in s) "initAttr" !== e && "triggerSuffix" !== e && "breakpoints" !== e && "targetSuffix" !== e && "globalTarget" !== e && "onInit" !== e && "onScroll" !== e && "onEnter" !== e && "onLeave" !== e && "updateOnResize" !== e && "onResize" !== e && "devMode" !== e && "cssSpace" !== e && "class" !== e && ("object" == typeof s[e] && null !== s[e] && "object" == typeof i[e] && null !== i[e] ? (n.push(e), t(i[e], s[e], n)) : i[e] !== s[e] && (i[e] = s[e])); }; for (const i in this.animations) t(this.settings[i], this.settingsBackup[i]); let i, s = null; const e = Object.keys(this.settings.breakpoints).filter((t => !isNaN(parseInt(t)))); if (!(e.length < 1)) { if (e.map(Number), e.forEach((t => { null === s ? s = this.viewportWidth >= t ? t : s : this.viewportWidth >= t && this.viewportWidth - t < this.viewportWidth - s && (s = t); })), null !== s) for (let n = 0; n < e.length; n++)Number(s) >= Number(e[n]) && (i = this.settingsBackup.breakpoints[e[n]], t(this.settings, i)); else i = this.settingsBackup, t(this.settings, i); this.currentBreakpoint = s; } } checkValidity() { const t = [], i = { strings: {}, arraysOrStrings: {}, colors: {}, numbers: {}, relativeNumbers: {}, booleans: {}, callbacks: {} }, s = (t, s) => { if ("initAttr" === t || "triggerSuffix" === t || "cssSpace" === t || "easing" === t || "class" === t || "top" === t || "bottom" === t) { const e = Object.keys(i.strings).length + 1; i.strings[e] = [t, s]; } if ("targetSuffix" === t) { const e = Object.keys(i.arraysOrStrings).length + 1; i.arraysOrStrings[e] = [t, s]; } if (-1 !== this.cssProperties.colors.indexOf(t)) { const e = Object.keys(i.colors).length + 1; i.colors[e] = [t, s]; } if (-1 !== this.cssProperties.transform.indexOf(t)) { const e = Object.keys(i.numbers).length + 1; i.numbers[e] = [t, s]; } if ("opacity" === t || "triggerPosition" === t) { const e = Object.keys(i.relativeNumbers).length + 1; i.relativeNumbers[e] = [t, s]; } if ("onscroll" === t || "resetOnScrollDown" === t || "resetOnScrollUp" === t || "sticky" === t || "devMode" === t || "globalTarget" === t) { const e = Object.keys(i.booleans).length + 1; i.booleans[e] = [t, s]; } if ("onInit" === t || "onEnter" === t || "onLeave" === t || "onScroll" === t || "onResize" === t || "updateOnResize" === t) { const e = Object.keys(i.callbacks).length + 1; i.callbacks[e] = [t, s]; } }, e = t => { for (const i in t) null !== t[i] && "object" != typeof t[i] && s(i, t[i]); }; e(this.settings); const n = t => { for (const i in t) { const e = i; if (null !== t[i]) if ("object" != typeof t[i]) s(e, t[i]); else for (const n in t[i]) null !== t[i][n] && s(e, t[i][n]); } }; for (const t in this.animations) n(this.settings[t]); if (null !== this.settings.breakpoints && "object" == typeof this.settings.breakpoints) for (const t in this.settings.breakpoints) if (!isNaN(t)) { e(this.settings.breakpoints[t]); for (const i in this.animations) this.settings.breakpoints[t][i] && "object" == typeof this.settings.breakpoints[t][i] && n(this.settings.breakpoints[t][i]); } const o = t => { const i = /^(rgb|rgba)?\(\s*(\d+)\s*,?\s*(\d+)\s*,?\s*(\d+)(?:\s*,\s*([\d.]+))?\s*\)$/i; if (/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})?$/i.test(t) || /^#?([a-f\d])([a-f\d])([a-f\d])([a-f\d])?$/i.test(t)) return !0; if (i.test(t)) { const s = i.exec(t), e = parseInt(s[2], 10), n = parseInt(s[3], 10), o = parseInt(s[4], 10), r = void 0 === s[5] ? 1 : parseFloat(s[5]); return e >= 0 && e <= 255 && n >= 0 && n <= 255 && o >= 0 && o <= 255 && r >= 0 && r <= 1; } return !1; }; for (const s in i) { for (const t in i[s]) { if ("strings" === s && "string" == typeof i[s][t][1] && delete i[s][t], "arraysOrStrings" === s) if ("string" == typeof i[s][t][1]) delete i[s][t]; else if (Array.isArray(i[s][t][1])) { let e = !0; i[s][t][1].forEach((t => { "string" != typeof t && (e = !1); })), e && delete i[s][t]; } "colors" === s && o(i[s][t][1]) && delete i[s][t], "numbers" === s && (isNaN(i[s][t][1]) || delete i[s][t]), "relativeNumbers" === s && !isNaN(i[s][t][1]) && i[s][t][1] >= 0 && i[s][t][1] <= 1 && delete i[s][t], "booleans" === s && "boolean" == typeof i[s][t][1] && delete i[s][t], "callbacks" === s && "function" == typeof i[s][t][1] && delete i[s][t]; } if (Object.keys(s).length > 0) for (const e in i[s]) { const n = `'${i[s][e][1]}' is no valid value for '${i[s][e][0]}'.`; t.push(n); } } if (t.length > 0) throw new Error(`Error: ${t.length} invalid ${t.length > 1 ? "options" : "option"} were found: ${t.join(" ")}`); } getAnimationTargets() { for (const t in this.animations) { const i = Array.isArray(this.settings[t].targetSuffix) ? this.settings[t].targetSuffix : [this.settings[t].targetSuffix], s = this.settings[t].globalTarget ? document : this.$root; this[`${t}Targets`] = i.flatMap((t => Array.from(s.querySelectorAll(`[${this.settings.initAttr}='${t}']`)))); } } getInitialStyles() { for (const t in this.animations) { const i = {}; this[`${t}Targets`].forEach(((s, e) => { const n = {}, o = window.getComputedStyle(s); Object.entries(this.settings[t]).forEach((([t]) => { Object.prototype.hasOwnProperty.call(o, t) && (n[t] = o[t]); })), i[e] = n; })), this.initialCssProperties[t] = i; } } prepareAnimation() { for (const t in this.settings) t.toLowerCase().includes("animate") && (this.animations[t] = {}); null !== this.settings.breakpoints && this.mergeBreakpointOptions(), this.getAnimationTargets(), this.getInitialStyles(); for (const t in this.animations) this.defineAnimations(t); this.optimizeAnimationPerformance(), this.checkValidity(), this.defineAnimationArea(), this.defineObserverOptions(), this.onscroll = !("function" != typeof this.settings.onScroll && !Object.keys(this.animations).some((t => this.settings[t].onscroll || this.settings[t].devMode))); for (const t in this.animations) this[`${t}Targets`].forEach(((i, s) => { this.settings[t].sticky && (this.stickyAnimations.includes(t) || this.stickyAnimations.push(t), this.setSticky(i, !0)); const e = []; null !== this.settings[t].duration && this.settings[t].onscroll && window.setTimeout((() => { e.push(this.setTransition(t, i, s)); }), 400); })); } defineObserverOptions() { const t = "px" === this.top[1] ? -1 * this.top[0] : -1 * this.top[0] / this.viewportHeight * 100, i = "px" === this.bottom[1] ? this.bottom[0] : this.bottom[0] / this.viewportHeight * 100; this.rootMargin = `${t}${this.top[1]} 100% ${i}${this.bottom[1]} 100%`, this.observerOptions = { rootMargin: this.rootMargin, threshold: this.settings.triggerPosition }; } optimizeAnimationPerformance() { for (const t in this.animations) if (Object.entries(this.animations[t]).length > 0 && this.settings[t].improvePerformance) { const i = []; for (const s in this.settings[t]) if (null !== this.settings[t][s] && (-1 !== this.cssProperties.transform.indexOf(s) && -1 === i.indexOf("transform") && i.push("transform"), -1 !== this.cssProperties.colors.indexOf(s) || "opacity" === s)) { const t = s.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(); i.push(t); } i.length > 0 && this[`${t}Targets`].forEach((t => { t.style.willChange = i.join(", "); })); } } defineAnimationArea() { this.animationArea = null; for (const t in this.settings) if (("top" === t || "bottom" === t) && "string" == typeof this.settings[t]) { const i = /^(-?\d+(\.\d+)?)(.+)$/, s = this.settings[t].match(i); if (s && 4 === s.length) { const i = parseFloat(s[1]), e = s[3].trim(); isNaN(i) || (this[t][1] = e, this[t][0] = "%" === e ? this.viewportHeight * i / 100 : i); } } this.animationArea = this.viewportHeight + -1 * this.top[0] + this.bottom[0] + this.$trigger.clientHeight - 2 * this.$trigger.clientHeight * this.settings.triggerPosition, "string" == typeof this.settings.cssSpace && this.$root.style.setProperty(`--${this.settings.cssSpace}`, `${this.animationArea}px`); } defineAnimations(t) { const i = this.animations[t].currentState, s = !!this.animations[t]?.[0]; let e = this.deepCopy(this.settings[t]); for (const i in e) if (!Object.values(this.cssProperties).some((t => t.includes(i))) || null === e[i] && (s && void 0 === this.animations[t][0][i] || !s)) delete e[i]; else if ("object" == typeof e[i]) if (null !== e[i]) { for (const t in e[i]) (isNaN(t) || t < 0 || t > 100) && delete e[i][t]; if (Object.entries(e[i]).length <= 1) delete e[i]; else { if (void 0 === e[i][0]) { const t = Object.entries(e[i])[0][1]; e[i][0] = t; } if (void 0 === e[i][100]) { const t = Object.entries(e[i]), s = t[t.length - 1][1]; e[i][100] = s; } } } else e[i] = {}, e[i][0] = null, e[i][100] = null; else { const t = e[i]; e[i] = {}, e[i][0] = null, e[i][100] = t; } if (Object.entries(e).length > 0) { const s = new Set; if (!this.settings[t].onscroll) { const t = {}; for (const i in e) { -1 !== this.cssProperties.transform.indexOf(i) && s.add(i); for (const s in e[i]) t[s] || (t[s] = {}), t[s][i] = e[i][s]; } e = t; } this[`${t}Targets`].forEach(((i, n) => { const o = Object.fromEntries(Object.entries(e)); for (const i in o) if (this.settings[t].onscroll) { if (Object.values(o[i]).includes(null)) { let s; s = -1 !== this.cssProperties.transform.indexOf(i) ? i.includes("scale") ? 1 : 0 : this.initialCssProperties[t][n][i]; for (const t in o[i]) null === o[i][t] && (o[i][t] = s); } } else { for (const s in o[i]) { let e; e = -1 !== this.cssProperties.transform.indexOf(s) ? s.includes("scale") ? 1 : 0 : this.initialCssProperties[t][n][s], null === o[i][s] && (o[i][s] = e); } if (Array.from(s).some((t => Object.prototype.hasOwnProperty.call(o[i], t)))) for (const t of s) if (void 0 === o[i][t]) { const s = this.getAnimationSteps(o, i, t), e = o[s[0]][t], n = o[s[1]][t]; if (e !== n) { const s = this.calculateValue(e, n, null, i); o[i][t] = s; } else o[i][t] = n; } } if (!this.settings[t].onscroll) for (const t in o) for (const i in o[t]) -1 !== this.cssProperties.transform.indexOf(i) && (void 0 === o[t].transform && (o[t].transform = this.generateTransform(o[t])), delete o[t][i]); this.animations[t][n] = o; })), void 0 !== i && (this.animations[t].currentState = i); } else this.animations[t] = {}; } compareAndDefineUpdates(t, i) { const s = {}, e = (t, i, n = "") => { for (const o in t) null !== i && i && void 0 !== i[o] ? "object" == typeof t[o] && "object" == typeof i[o] ? null === t[o] && null !== i[o] || null !== t[o] && null === i[o] ? s[n + o] = t[o] : e(t[o], i[o], n + o + ".") : t[o] !== i[o] && (s[n + o] = t[o]) : s[n + o] = t[o]; }, n = t => { for (const i in t) if (Object.prototype.hasOwnProperty.call(this.animations, i) && "object" == typeof t[i]) { const s = i; for (const e in t[i]) this.requiredUpdates.stickyElements.includes(s) || "sticky" !== e || this.requiredUpdates.stickyElements.push(s), !this.requiredUpdates.animations.includes(s) && Object.values(this.cssProperties).some((t => t.includes(e))) && this.requiredUpdates.animations.push(s), !this.requiredUpdates.transition.includes(s) && ["duration", "delay", "easing"].includes(e) && this.requiredUpdates.transition.push(s); } else["top", "bottom", "triggerPosition"].includes(i) ? this.requiredUpdates.observer = !0 : "cssSpace" === i && (this.requiredUpdates.cssSpace = !0); }; e(t, i); const o = {}; for (const t in s) { const i = t.split("."), e = i.pop(); let n = o; for (const t of i) Object.prototype.hasOwnProperty.call(n, t) || (n[t] = {}), n = n[t]; n[e] = s[t]; } Object.entries(o).length > 0 && n(o); } initAnimation() { this.settings.devMode && this.initial && this.logDevInfo(); for (const t in this.animations) { let i = !0; if ((this.initial || this.isResizing) && this.settings[t].globalTarget) { let t = sessionStorage.getItem(`${this.settings.initAttr}-active-trigger`); null !== t ? (t = JSON.parse(t), t.triggerIndex !== this.settings.triggerIndex && (i = !1)) : i = !1; } let s = !1; if (this.initial && 0 === this.animationProgress) for (const i in this.settings[t]) Object.values(this.cssProperties).some((t => t.includes(i))) && null !== this.settings[t][i] && "object" == typeof this.settings[t][i] && void 0 !== this.settings[t][i][0] && (s = !0); if (i) { if (this.settings[t].onscroll && (s || this.isResizing || this.isLeaving) && (this.settings[t].resetOnScrollUp || !this.settings[t].resetOnScrollUp && 1 !== this.animations[t].currentState) && (this.animateOnscroll(t), this.animations[t].currentState = this.animationProgress), !this.settings[t].onscroll && Object.entries(this.animations[t]).length > 0) { let i = this.animations[t].currentState; const e = this.isEnteringFirstTime && this.isEnteringOnBottom || this.settings[t].resetOnScrollUp && this.isEnteringOnBottom || this.settings[t].resetOnScrollDown && this.isEnteringOnTop || this.animationActive && (this.initial || this.isResizing) || this.passed && !this.settings[t].resetOnScrollDown, n = this.isLeavingOnTop && this.settings[t].resetOnScrollDown || this.isLeavingOnBottom && this.settings[t].resetOnScrollUp || s && !this.animationActive; (e || n) && (e && (i = "normal"), n && (i = "reverse"), (this.isResizing && this.settings[t].globalTarget || this.animations[t].currentState !== i || this.animationActive) && (this.animateFrames(t, i, this.animationProgress), this.animations[t].currentState = i)); } if ("string" == typeof this.settings[t].class) { const i = this.animationActive || this.passed && !this.settings[t].resetOnScrollDown; this[`${t}Targets`].forEach((s => { i && s.classList.add(this.settings[t].class), (this.isLeavingOnTop && this.settings[t].resetOnScrollDown || this.isLeavingOnBottom && this.settings[t].resetOnScrollUp) && s.classList.remove(this.settings[t].class); })); } } } this.onscroll && (this.animationActive || this.passed) && this.controlOnscrollActions(), "function" == typeof this.settings.onInit && this.initial && this.settings.onInit(this.animationProgress), "function" == typeof this.settings.onEnter && this.animationActive && this.settings.onEnter(this.animationProgress), "function" == typeof this.settings.onLeave && this.isLeaving && this.settings.onLeave(this.animationProgress), this.isEntering && (this.isEnteringFirstTime = !1), this.isResizing && (this.isResizing = !1); } getAnimationProgress() { if (this.animationArea > 0) { this.startPosition = this.viewportHeight + this.bottom[0] - this.$trigger.clientHeight * this.settings.triggerPosition; const t = (this.startPosition - this.$trigger.getBoundingClientRect().top) / this.animationArea; t > .5 && this.isCrossingBorder && !this.animationActive || t > 1 ? this.animationProgress = 1 : t < .5 && this.isCrossingBorder && !this.animationActive || t < 0 ? this.animationProgress = 0 : this.animationProgress = parseFloat(t.toFixed(6)); } else this.animationProgress = 0; this.isCrossingBorder ? (this.passed = 1 === this.animationProgress, this.initial ? this.animationActive = this.animationProgress < 1 && (this.animationProgress > 0 || 0 === this.animationProgress && this.animationActive) : (this.isLeaving = !this.animationActive, this.isLeavingOnTop = this.isLeaving && this.animationProgress > .5, this.isLeavingOnBottom = this.isLeaving && this.animationProgress < .5, this.isEntering = this.animationActive, this.isEnteringFirstTime = !(!this.isEntering || null !== this.isEnteringFirstTime), this.isEnteringOnTop = this.isEntering && this.animationProgress > .5, this.isEnteringOnBottom = this.isEntering && this.animationProgress < .5)) : (this.isLeaving = !1, this.isLeavingOnTop = !1, this.isLeavingOnBottom = !1, this.isEntering = !1, this.isEnteringOnTop = !1, this.isEnteringOnBottom = !1); } observeTrigger() { this.observer = new IntersectionObserver((t => { t.forEach((t => { this.animationActive = t.isIntersecting, this.isCrossingBorder = !0, this.getAnimationProgress(), Object.keys(this.animations).some((t => this.settings[t].globalTarget)) && this.storeActiveTrigger(), this.initAnimation(), this.initial = !1, this.isCrossingBorder = !1; })); }), this.observerOptions), this.isObserverConnected || (this.observer.observe(this.$trigger), this.isObserverConnected = !0); } storeActiveTrigger() { if (!this.initial && !this.isResizing) return; let t, i = sessionStorage.getItem(`${this.settings.initAttr}-active-trigger`); if (null !== i && (i = JSON.parse(i)), this.animationActive && (null === i || Number(i.triggerIndex) < this.settings.triggerIndex || !i.active) && (t = { triggerIndex: this.settings.triggerIndex, active: !0 }), this.passed && (null === i || !i.active && Number(i.triggerIndex) < this.settings.triggerIndex) && (t = { triggerIndex: this.settings.triggerIndex, active: !1 }), t) { const i = JSON.stringify(t); sessionStorage.setItem(`${this.settings.initAttr}-active-trigger`, i); } } setColor(t, i, s, e) { const n = t => { let i; if (i = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})?$/i.exec(t), i) { return [parseInt(i[1], 16), parseInt(i[2], 16), parseInt(i[3], 16), i[4] ? parseInt(i[4], 16) / 255 : 1]; } if (i = /^#?([a-f\d])([a-f\d])([a-f\d])([a-f\d])?$/i.exec(t), i) { return [parseInt(i[1] + i[1], 16), parseInt(i[2] + i[2], 16), parseInt(i[3] + i[3], 16), i[4] ? parseInt(i[4] + i[4], 16) / 255 : 1]; } if (i = /^(rgb|rgba)?\(\s*(\d+)\s*,?\s*(\d+)\s*,?\s*(\d+)(?:\s*,\s*([\d.]+))?\s*\)$/i.exec(t), i) { return [parseInt(i[2]), parseInt(i[3]), parseInt(i[4]), i[5] ? parseFloat(i[5]) : 1]; } return null; }, o = this.animations[t][s][e], r = 100 * this.animationProgress; if (void 0 !== o[r]) i.style[e] = o[r]; else { const t = this.getAnimationSteps(o, r), s = o[t[0]], a = o[t[1]]; if (s !== a) { const o = n(s), r = n(a), h = o.map(((i, s) => this.calculateValue(i, r[s], t))); i.style[e] = `rgba(${h.join(", ")})`; } else i.style[e] = s || a; } } setOpacity(t, i, s) { const e = this.animations[t][s].opacity, n = 100 * this.animationProgress; if (void 0 !== e[n]) i.style.opacity = e[n]; else { const t = this.getAnimationSteps(e, n), s = e[t[0]], o = e[t[1]]; if (s !== o) { const e = this.calculateValue(s, o, t); i.style.opacity = e; } else i.style.opacity = s || o; } } setTransform(t, i, s) { const e = {}, n = 100 * this.animationProgress; for (const i in this.animations[t][s]) if (-1 !== this.cssProperties.transform.indexOf(i)) { const o = this.animations[t][s][i]; if (void 0 !== o[n]) e[i] = o[n]; else { const t = this.getAnimationSteps(o, n), s = o[t[0]], r = o[t[1]]; e[i] = s !== r ? this.calculateValue(s, r, t) : s || r; } } const o = this.generateTransform(e);["-webkit-", "-moz-", "-ms-", "-o-", ""].forEach((t => { i.style[`${t}transform`] = o; })); } generateTransform(t) { const i = []; let s = [null, null, null], e = "px"; for (const n in t) -1 !== this.cssProperties.transform.indexOf(n) && (e = n.includes("translate") || "perspective" === n ? "px" : e, e = n.includes("scale") ? "" : e, e = n.includes("rotate") || n.includes("skew") ? "deg" : e, n.includes("translate") ? (s[0] = "translateX" === n ? `${t[n]}${e}` : s[0], s[1] = "translateY" === n ? `${t[n]}${e}` : s[1], s[2] = "translateZ" === n ? `${t[n]}${e}` : s[2]) : i.push(`${n}(${t[n]}${e})`)); if (s.some((t => null !== t))) { s = s.map((t => null !== t ? t : "0px")); const t = s.join(", "); i.push(`translate3d(${t})`); } return i.join(" "); } setTransition(t, i, s) { const e = ["duration", "delay"]; for (let i = 0; i < e.length; i++) { const n = e[i]; null !== this.settings[t][n] ? "object" == typeof this.settings[t][n] && Object.prototype.hasOwnProperty.call(this.settings[t][n], "time") && Object.prototype.hasOwnProperty.call(this.settings[t][n], "multiplier") ? window[n] = this.settings[t][n].time * this.settings[t][n].multiplier * s : window[n] = this.settings[t][n] : window[n] = 0; } 0 !== window.duration ? i.style.transition = `all ${window.duration}s ${this.settings[t].easing} ${window.delay}s` : i.style.removeProperty("transition"); } getAnimationSteps(t, i, s) { const e = parseFloat(i), n = Object.assign({}, t); let o = null, r = null; if (s) for (const t in n) Object.prototype.hasOwnProperty.call(n[t], s) && t !== i || delete n[t]; for (const t in n) { const i = parseFloat(t); i < e && (i > o || null === o) && (o = i), i > e && (i < r || null === r) && (r = i); } return [o, r]; } calculateValue(t, i, s, e) { let n = null, o = this.animationProgress; return s && (o = Math.round((this.animationProgress - s[0] / 100) / ((s[1] - s[0]) / 100) * 1e3) / 1e3), e && (o = e / 100), n = Math.round(1e3 * (t - (t - i) * o)) / 1e3, n; } animateFrames(t, i) { this[`${t}Targets`].forEach(((s, e) => { const n = ["duration", "delay"], o = this.animations[t][e], r = []; o && Object.entries(o).forEach((([t, i]) => { if (!isNaN(t)) { const s = { offset: parseFloat(t) / 100, ...i }; r.push(s); } })); const a = { iterations: 1, easing: this.settings[t].easing, fill: "forwards", direction: i }; for (let i = 0; i < n.length; i++) { const s = n[i]; null !== this.settings[t][s] && ("object" == typeof this.settings[t][s] && this.settings[t][s].time && this.settings[t][s].multiplier ? window[s] = 1e3 * this.settings[t][s].time * this.settings[t][s].multiplier * e : window[s] = 1e3 * this.settings[t][s]), a[s] = this.initial || null === this.settings[t][s] || this.settings[t].globalTarget && this.isResizing ? 0 : window[s]; } s.animate(r, a); })); } controlOnscrollActions() { let t = 1, i = window.scrollY || document.documentElement.scrollTop, s = 0; const e = () => { const n = window.scrollY || document.documentElement.scrollTop; n !== i && this.getAnimationProgress(); if (this.animationProgress !== s || 1 === t) { "function" == typeof this.settings.onScroll && this.settings.onScroll(this.animationProgress); for (const t in this.animations) this.settings[t].onscroll && this.animations[t].currentState !== this.animationProgress && Object.entries(this.animations[t]).length > 0 && (this.settings[t].resetOnScrollUp || !this.settings[t].resetOnScrollUp && 1 !== this.animations[t].currentState) && (this.animateOnscroll(t), this.animations[t].currentState = this.animationProgress); this.settings.devMode && !this.initial && this.logDevInfo(), s = this.animationProgress, t++; } i = n, this.animationActive && requestAnimationFrame(e); }; requestAnimationFrame(e); } animateOnscroll(t) { this[`${t}Targets`].forEach(((i, s) => { let e = !1; for (const n in this.animations[t][s]) -1 === this.cssProperties.transform.indexOf(n) || e || (this.setTransform(t, i, s), e = !0), -1 !== this.cssProperties.colors.indexOf(n) && this.setColor(t, i, s, n), "opacity" === n && this.setOpacity(t, i, s); })); } setSticky(t, i) { if (i) { let i = t.closest("[data-sticky-wrapper]"), s = t.closest("[data-sticky-spacer]"); if (!i || !s) { const e = this.viewportHeight + this.bottom[0] - this.$trigger.clientHeight * this.settings.triggerPosition - (this.$trigger.getBoundingClientRect().top - t.getBoundingClientRect().top), n = t.clientHeight, o = this.animationArea + n; t.parentNode.style.overflow = "visible"; const r = document.createElement("div"); r.setAttribute("data-sticky-spacer", ""), r.style.height = `${o}px`, t.parentNode.insertBefore(r, t), t.style.height = `${n}px`, r.appendChild(t), i = document.createElement("div"), i.setAttribute("data-sticky-wrapper", ""), i.style.height = `${n}px`, i.style.width = "100%", r.parentNode.insertBefore(i, r), i.appendChild(r), t.style.position = "sticky", t.style.top = `${e}px`, s = r; } t.style.height = "", t.style.top = "", s.style.height = "100%", i.style.height = "100%"; const e = t.clientHeight, n = this.viewportHeight + this.bottom[0] - this.$trigger.clientHeight * this.settings.triggerPosition - (this.$trigger.getBoundingClientRect().top - t.getBoundingClientRect().top), o = this.animationArea + e; i.style.height = `${e}px`, t.style.height = `${e}px`, t.style.top = `${n}px`, s.style.height = `${o}px`; } else { const i = t.closest("[data-sticky-wrapper]"); i && i.replaceWith(t), t.style = "", t.parentNode.style.overflow = ""; } } deepCopy(t) { if (null === t || "object" != typeof t) return t; if (t instanceof Date) return new Date(t); if (t instanceof RegExp) return new RegExp(t); const i = Array.isArray(t) ? [] : {}; for (const s in t) Object.prototype.hasOwnProperty.call(t, s) && (i[s] = this.deepCopy(t[s])); return i; } logDevInfo() { const t = [this.settings.top, !1], i = [this.settings.bottom, !1]; for (let s = 0; s < 2; s++)for (let e = 0; e < [t, i][s].length; e++)null !== [t, i][s][0] && [t, i][s][0].includes("-") && ([t, i][s][1] = !0, [t, i][s][0] = [t, i][s][0].replace("-", "")); const s = null === t[0] ? "viewport top" : `${t[0]} ${t[1] ? "above viewport top" : "below viewport top"}`, e = null === i[0] ? "viewport bottom" : `${i[0]} ${i[1] ? "above viewport bottom" : "below viewport bottom"}`, n = 0 === this.settings.triggerPosition ? "as soon as at least one pixel of the trigger is" : "if the trigger is " + (1 === this.settings.triggerPosition ? "completely" : `at least ${100 * this.settings.triggerPosition}% `); console.log("------\n%cDev Information for ScrollCaptain", "font-weight: bold; text-transform: uppercase;"), console.log("The trigger element is:"), console.log(this.$trigger), console.log(`The animation area is defined from ${s} to ${e}.`), console.log(`The animation is triggered ${n} in the animation area.`), console.log(`The animation progress is: ${Math.round(100 * this.animationProgress)}%`); } setEvents() { let t, i = 0; const s = (e = () => { let t = !1, s = !1; window.innerWidth === this.viewportWidth && window.outerWidth === this.viewportOuterWidth || (t = !0, this.viewportWidth = window.innerWidth, this.viewportOuterWidth = window.outerWidth), window.innerHeight !== this.viewportHeight && Date.now() - i >= 2500 && (s = !0, this.viewportHeight = window.innerHeight), (s || t) && (this.isResizing = !0, this.isObserverConnected && (this.observer.disconnect(this.$trigger), this.isObserverConnected = !1), null !== sessionStorage.getItem(`${this.settings.initAttr}-active-trigger`) && sessionStorage.removeItem(`${this.settings.initAttr}-active-trigger`), this.update(t)); }, n = 250, function (...i) { clearTimeout(t), t = setTimeout((() => e(...i)), n); }); var e, n; window.addEventListener("touchstart", (() => { i = Date.now(); })), window.addEventListener("resize", s), window.addEventListener("beforeunload", (() => { null !== sessionStorage.getItem(`${this.settings.initAttr}-active-trigger`) && sessionStorage.removeItem(`${this.settings.initAttr}-active-trigger`); })); } } export { ScrollCaptain };