PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.78
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.78
51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 51.1.46 51.1.47 51.1.49 All 37 releases
king-addons / includes / widgets / Scroll_Story_Sections / script.js

script.js in King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder 51.1.78, at includes/widgets/Scroll_Story_Sections/script.js

273 lines 8.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 "use strict";
2
3 (function ($) {
4 const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
5
6 const parseSettings = (root) => {
7 if (!root || !root.dataset) {
8 return {};
9 }
10
11 try {
12 return JSON.parse(root.dataset.settings || "{}");
13 } catch (e) {
14 return {};
15 }
16 };
17
18 const getScrollTop = () =>
19 window.pageYOffset || document.documentElement.scrollTop || 0;
20
21 const getOffsetTop = (element) => {
22 const rect = element.getBoundingClientRect();
23 return rect.top + getScrollTop();
24 };
25
26 const initScrollStory = ($scope) => {
27 const root = $scope.find(".king-addons-scroll-story")[0];
28 if (!root || root.dataset.scrollStoryInit === "yes") {
29 return;
30 }
31
32 const steps = Array.from(
33 root.querySelectorAll(".king-addons-scroll-story__step")
34 );
35 const panels = Array.from(
36 root.querySelectorAll(".king-addons-scroll-story__panel")
37 );
38 const list = root.querySelector(".king-addons-scroll-story__steps");
39
40 if (!steps.length || steps.length !== panels.length) {
41 return;
42 }
43
44 root.dataset.scrollStoryInit = "yes";
45
46 const settings = parseSettings(root);
47 const activationOffset = clamp(
48 Number(settings.activationOffset) || 40,
49 10,
50 80
51 );
52 const smoothScroll =
53 settings.smoothScroll !== false &&
54 settings.smoothScroll !== "no" &&
55 settings.smoothScroll !== "0";
56 const scrollDuration = Math.max(
57 Number(settings.scrollDuration) || 600,
58 0
59 );
60
61 const prefersReduced =
62 window.matchMedia &&
63 window.matchMedia("(prefers-reduced-motion: reduce)").matches;
64 const isEditor =
65 window.elementorFrontend &&
66 elementorFrontend.isEditMode &&
67 elementorFrontend.isEditMode();
68
69 let activeIndex = -1;
70
71 const setActive = (index) => {
72 const nextIndex = clamp(index, 0, steps.length - 1);
73 if (nextIndex === activeIndex) {
74 return;
75 }
76
77 activeIndex = nextIndex;
78 root.setAttribute("data-active-index", String(activeIndex));
79
80 steps.forEach((step, idx) => {
81 const isActive = idx === activeIndex;
82 step.classList.toggle("is-active", isActive);
83 step.classList.toggle("is-completed", idx < activeIndex);
84 step.classList.toggle("is-upcoming", idx > activeIndex);
85
86 const button = step.querySelector(
87 ".king-addons-scroll-story__step-button"
88 );
89 if (button) {
90 if (isActive) {
91 button.setAttribute("aria-current", "step");
92 } else {
93 button.removeAttribute("aria-current");
94 }
95 }
96 });
97
98 panels.forEach((panel, idx) => {
99 const isActive = idx === activeIndex;
100 panel.classList.toggle("is-active", isActive);
101 panel.setAttribute("aria-hidden", isActive ? "false" : "true");
102 if (isActive) {
103 panel.removeAttribute("hidden");
104 } else {
105 panel.setAttribute("hidden", "hidden");
106 }
107 });
108 };
109
110 const getClosestIndex = () => {
111 const threshold = window.innerHeight * (activationOffset / 100);
112 let intersecting = null;
113 let lastAbove = null;
114
115 steps.forEach((step, idx) => {
116 const rect = step.getBoundingClientRect();
117 if (rect.top <= threshold && rect.bottom >= threshold) {
118 intersecting = idx;
119 }
120 if (rect.top <= threshold) {
121 lastAbove = idx;
122 }
123 });
124
125 if (intersecting !== null) {
126 return intersecting;
127 }
128
129 if (list) {
130 const listRect = list.getBoundingClientRect();
131 if (listRect.top >= 0 && listRect.bottom < threshold) {
132 return activeIndex >= 0 ? activeIndex : 0;
133 }
134 }
135
136 if (lastAbove !== null) {
137 return lastAbove;
138 }
139
140 return activeIndex >= 0 ? activeIndex : 0;
141 };
142
143 const smoothScrollTo = (targetY, duration) => {
144 if (prefersReduced || duration <= 0) {
145 window.scrollTo(0, targetY);
146 return;
147 }
148
149 const startY = getScrollTop();
150 const distance = targetY - startY;
151 const startTime =
152 (window.performance && performance.now()) || Date.now();
153
154 const easeInOutQuad = (t) =>
155 t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
156
157 const tick = (now) => {
158 const currentTime =
159 (window.performance && performance.now()) || now;
160 const elapsed = currentTime - startTime;
161 const progress = Math.min(elapsed / duration, 1);
162 const eased = easeInOutQuad(progress);
163
164 window.scrollTo(0, startY + distance * eased);
165
166 if (progress < 1) {
167 window.requestAnimationFrame(tick);
168 }
169 };
170
171 window.requestAnimationFrame(tick);
172 };
173
174 const scrollToStep = (index) => {
175 const target = steps[index];
176 if (!target) {
177 return;
178 }
179
180 const offsetPx = window.innerHeight * (activationOffset / 100);
181 const targetTop = Math.max(getOffsetTop(target) - offsetPx, 0);
182
183 if (!smoothScroll || prefersReduced) {
184 window.scrollTo(0, targetTop);
185 return;
186 }
187
188 smoothScrollTo(targetTop, scrollDuration);
189 };
190
191 steps.forEach((step, index) => {
192 const button = step.querySelector(
193 ".king-addons-scroll-story__step-button"
194 );
195 if (!button) {
196 return;
197 }
198
199 button.addEventListener("click", (event) => {
200 event.preventDefault();
201 setActive(index);
202 if (!isEditor) {
203 scrollToStep(index);
204 }
205 });
206
207 button.addEventListener("keydown", (event) => {
208 if (event.key !== "Enter" && event.key !== " ") {
209 return;
210 }
211 event.preventDefault();
212 setActive(index);
213 if (!isEditor) {
214 scrollToStep(index);
215 }
216 });
217 });
218
219 if (isEditor) {
220 setActive(0);
221 return;
222 }
223
224 setActive(getClosestIndex());
225
226 if ("IntersectionObserver" in window) {
227 const observerMargin = `-${activationOffset}% 0px -${
228 100 - activationOffset
229 }% 0px`;
230 const observer = new IntersectionObserver(
231 (entries) => {
232 const shouldUpdate = entries.some(
233 (entry) => entry.isIntersecting
234 );
235 if (shouldUpdate) {
236 setActive(getClosestIndex());
237 }
238 },
239 {
240 rootMargin: observerMargin,
241 threshold: 0,
242 }
243 );
244
245 steps.forEach((step) => observer.observe(step));
246 } else {
247 let ticking = false;
248 const onScroll = () => {
249 if (ticking) {
250 return;
251 }
252 ticking = true;
253 window.requestAnimationFrame(() => {
254 setActive(getClosestIndex());
255 ticking = false;
256 });
257 };
258 window.addEventListener("scroll", onScroll, { passive: true });
259 }
260
261 window.addEventListener("resize", () => {
262 setActive(getClosestIndex());
263 });
264 };
265
266 $(window).on("elementor/frontend/init", () => {
267 elementorFrontend.hooks.addAction(
268 "frontend/element_ready/king-addons-scroll-story-sections.default",
269 initScrollStory
270 );
271 });
272 })(jQuery);
273