PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.49
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.49
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 / Rotating_Image_Tiles / script.js

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

443 lines 14.6 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 try {
8 return JSON.parse(root.dataset.settings || "{}");
9 } catch (e) {
10 return null;
11 }
12 };
13
14 const applyCssVars = (root, settings) => {
15 const columns = settings?.layout?.columns || 3;
16 const gap = settings?.layout?.gap || 0;
17 const duration = settings?.animation?.transitionDuration || 800;
18 const easing = settings?.animation?.easing || "ease-in-out";
19 const imageFit = settings?.visual?.imageFit || "cover";
20 const scaleHover = settings?.visual?.imageScaleHover || 1.05;
21 const imageOpacity = settings?.visual?.imageOpacity ?? 1;
22 const imageOpacityHover =
23 settings?.visual?.imageOpacityHover ?? settings?.visual?.imageOpacity ?? 1;
24
25 root.style.setProperty("--rit-columns", columns);
26 root.style.setProperty("--rit-gap", `${gap}px`);
27 root.style.setProperty("--rit-transition-duration", `${duration}ms`);
28 root.style.setProperty("--rit-easing", easing);
29 root.style.setProperty("--rit-image-fit", imageFit);
30 root.style.setProperty("--rit-image-scale-hover", scaleHover);
31 root.style.setProperty("--rit-image-opacity", imageOpacity);
32 root.style.setProperty("--rit-image-opacity-hover", imageOpacityHover);
33 };
34
35 const getAlternateNext = (current, length) => {
36 const sequence = [];
37 for (let i = 0; i < length; i++) {
38 const left = i;
39 const right = length - 1 - i;
40 if (left < length) {
41 sequence.push(left);
42 }
43 if (right !== left && right >= 0) {
44 sequence.push(right);
45 }
46 if (sequence.length >= length) {
47 break;
48 }
49 }
50 const currentIndex = sequence.indexOf(current);
51 if (currentIndex === -1) {
52 return (current + 1) % length;
53 }
54 return sequence[(currentIndex + 1) % sequence.length];
55 };
56
57 const getNextIndex = (current, mode, length) => {
58 if (length <= 1) {
59 return current;
60 }
61
62 switch (mode) {
63 case "reverse":
64 return (current - 1 + length) % length;
65 case "alternate":
66 return getAlternateNext(current, length);
67 case "random": {
68 let next = current;
69 const safety = 6;
70 for (let i = 0; i < safety && next === current; i++) {
71 next = Math.floor(Math.random() * length);
72 }
73 return next;
74 }
75 case "sequential":
76 default:
77 return (current + 1) % length;
78 }
79 };
80
81 const setLayerImage = (layer, image, mask) => {
82 if (!layer) {
83 return;
84 }
85 const img = layer.querySelector("img");
86 if (img) {
87 img.src = image.url;
88 img.alt = image.alt || "";
89 }
90 if (mask) {
91 layer.style.clipPath = mask;
92 layer.style.webkitClipPath = mask;
93 }
94 };
95
96 const updateCaption = (captionEl, image, showDescription) => {
97 if (!captionEl) {
98 return;
99 }
100 const titleEl = captionEl.querySelector(
101 ".king-addons-rotating-image-tiles__caption-title"
102 );
103 const descriptionEl = captionEl.querySelector(
104 ".king-addons-rotating-image-tiles__caption-description"
105 );
106
107 if (titleEl) {
108 titleEl.textContent = image.title || "";
109 }
110 if (descriptionEl) {
111 descriptionEl.textContent = showDescription ? image.description || "" : "";
112 descriptionEl.style.display =
113 showDescription && image.description ? "block" : "none";
114 }
115 };
116
117 const swapLayers = (state, nextIndex, settings, images, mask) => {
118 const nextImage = images[nextIndex];
119 if (!nextImage) {
120 return;
121 }
122
123 setLayerImage(state.layers.next, nextImage, mask);
124 state.tile.classList.add("is-animating");
125 state.layers.next.classList.add("is-entering");
126 state.layers.current.classList.add("is-leaving");
127
128 const duration = settings?.animation?.transitionDuration || 800;
129
130 window.setTimeout(() => {
131 state.layers.next.classList.remove("is-entering");
132 state.layers.current.classList.remove("is-leaving");
133
134 state.layers.current.classList.remove("is-current");
135 state.layers.next.classList.remove("is-next");
136
137 state.layers.current.classList.add("is-next");
138 state.layers.next.classList.add("is-current");
139
140 const temp = state.layers.current;
141 state.layers.current = state.layers.next;
142 state.layers.next = temp;
143
144 state.currentIndex = nextIndex;
145 state.transitionsDone += 1;
146
147 state.tile.classList.remove("is-animating");
148
149 if (state.captionEl) {
150 const showDescription =
151 settings?.interaction?.captionSource === "title_description";
152 updateCaption(state.captionEl, nextImage, showDescription);
153 }
154 }, duration);
155 };
156
157 const openLightbox = (image) => {
158 if (!image?.url) {
159 return;
160 }
161
162 if (window.elementorFrontend?.utils?.lightbox) {
163 window.elementorFrontend.utils.lightbox.showImages([
164 {
165 url: image.url,
166 title: image.title || "",
167 description: image.description || "",
168 },
169 ]);
170 return;
171 }
172
173 window.open(image.url, "_blank", "noopener");
174 };
175
176 const handleClickAction = (action, image) => {
177 if (action === "open_link" && image?.link?.url) {
178 const target = image.link.is_external ? "_blank" : "_self";
179 const relParts = [];
180 if (image.link.nofollow) {
181 relParts.push("nofollow");
182 }
183 if (image.link.is_external) {
184 relParts.push("noopener", "noreferrer");
185 }
186 const rel = relParts.join(" ");
187 const anchor = document.createElement("a");
188 anchor.href = image.link.url;
189 anchor.target = target;
190 if (rel) {
191 anchor.rel = rel;
192 }
193 anchor.style.display = "none";
194 document.body.appendChild(anchor);
195 anchor.click();
196 anchor.remove();
197 return;
198 }
199
200 if (action === "lightbox") {
201 openLightbox(image);
202 }
203 };
204
205 const buildMask = (tileSettings, settings) => {
206 const useGlobal = settings?.circle?.useGlobal === "yes";
207 const radius = useGlobal
208 ? clamp(settings?.circle?.radius ?? 40, 5, 100)
209 : clamp(tileSettings.radius ?? 40, 5, 100);
210 const centerX = clamp(tileSettings.centerX ?? 50, 0, 100);
211 const centerY = clamp(tileSettings.centerY ?? 50, 0, 100);
212 return `circle(${radius}% at ${centerX}% ${centerY}%)`;
213 };
214
215 const buildTileState = (tile, index, settings, images) => {
216 const layers = {
217 current: tile.querySelector(".king-addons-rotating-image-tiles__image-layer.is-current"),
218 next: tile.querySelector(".king-addons-rotating-image-tiles__image-layer.is-next"),
219 };
220
221 const captionEl = tile.querySelector(".king-addons-rotating-image-tiles__caption");
222 const initialIndex = clamp(
223 parseInt(tile.dataset.initialIndex, 10) || 0,
224 0,
225 Math.max(images.length - 1, 0)
226 );
227 const tileData = settings.tiles?.[index] || {};
228 const hoverScale = parseFloat(tile.dataset.hoverScale || tileData.hoverScale || 1.05);
229 const mask = buildMask(
230 {
231 centerX: parseFloat(tile.dataset.centerX || tileData.centerX || 50),
232 centerY: parseFloat(tile.dataset.centerY || tileData.centerY || 50),
233 radius: parseFloat(tile.dataset.radius || tileData.radius || 40),
234 },
235 settings
236 );
237
238 setLayerImage(layers.current, images[initialIndex], mask);
239 setLayerImage(layers.next, images[initialIndex], mask);
240 if (captionEl) {
241 const showDescription = settings?.interaction?.captionSource === "title_description";
242 updateCaption(captionEl, images[initialIndex], showDescription);
243 }
244
245 tile.style.setProperty("--rit-hover-scale", hoverScale);
246
247 return {
248 tile,
249 layers,
250 captionEl,
251 currentIndex: initialIndex,
252 transitionsDone: 0,
253 delay: parseInt(tile.dataset.delay || "0", 10) || 0,
254 paused: false,
255 hoverScale,
256 timers: {
257 interval: null,
258 delay: null,
259 },
260 };
261 };
262
263 const clearTimers = (state) => {
264 if (state.timers.interval) {
265 window.clearInterval(state.timers.interval);
266 state.timers.interval = null;
267 }
268 if (state.timers.delay) {
269 window.clearTimeout(state.timers.delay);
270 state.timers.delay = null;
271 }
272 };
273
274 const initBehavior = (root, states, settings, images) => {
275 const mode = settings?.animation?.mode || "sequential";
276 const behavior = settings?.animation?.behavior || "autoplay";
277 const interval = settings?.animation?.interval || 2500;
278 const loop = settings?.animation?.loop !== "no";
279 const pauseOnHover = settings?.animation?.pauseOnHover === "yes";
280 const tileEffect = settings?.hover?.tileEffect || "none";
281
282 if (tileEffect && tileEffect !== "none") {
283 states.forEach((state) => {
284 state.tile.classList.add(`is-hover-${tileEffect}`);
285 });
286 }
287
288 const stepTile = (state) => {
289 if (state.paused) {
290 return;
291 }
292
293 if (!loop && state.transitionsDone >= Math.max(images.length - 1, 0)) {
294 return;
295 }
296
297 const nextIndex = getNextIndex(state.currentIndex, mode, images.length);
298 if (nextIndex === state.currentIndex) {
299 return;
300 }
301
302 const tileIndex = parseInt(state.tile.dataset.tileIndex || "0", 10) || 0;
303 const mask = buildMask(settings.tiles?.[tileIndex] || {}, settings);
304 swapLayers(state, nextIndex, settings, images, mask);
305 };
306
307 const startAutoplay = () => {
308 states.forEach((state) => {
309 clearTimers(state);
310 const tick = () => stepTile(state);
311 if (state.delay > 0) {
312 state.timers.delay = window.setTimeout(() => {
313 tick();
314 state.timers.interval = window.setInterval(tick, interval);
315 }, state.delay);
316 } else {
317 tick();
318 state.timers.interval = window.setInterval(tick, interval);
319 }
320 });
321 };
322
323 const stopAutoplay = () => {
324 states.forEach((state) => clearTimers(state));
325 };
326
327 if (behavior === "autoplay") {
328 startAutoplay();
329
330 if (pauseOnHover) {
331 root.addEventListener("mouseenter", () => {
332 states.forEach((state) => {
333 state.paused = true;
334 });
335 });
336 root.addEventListener("mouseleave", () => {
337 states.forEach((state) => {
338 state.paused = false;
339 });
340 });
341 }
342 } else if (behavior === "on_hover") {
343 const onEnter = () => {
344 startAutoplay();
345 };
346 const onLeave = () => {
347 stopAutoplay();
348 };
349 root.addEventListener("mouseenter", onEnter);
350 root.addEventListener("mouseleave", onLeave);
351 } else if (behavior === "on_click") {
352 stopAutoplay();
353 states.forEach((state) => {
354 state.tile.addEventListener("click", () => {
355 stepTile(state);
356 });
357 });
358 }
359
360 return () => {
361 stopAutoplay();
362 };
363 };
364
365 const initInteractions = (states, settings, images) => {
366 const clickAction = settings?.interaction?.clickAction || "none";
367 if (clickAction === "none") {
368 return () => {};
369 }
370
371 const listeners = [];
372 states.forEach((state) => {
373 const handler = () => {
374 const image = images[state.currentIndex] || images[0];
375 handleClickAction(clickAction, image);
376 };
377 state.tile.addEventListener("click", handler);
378 listeners.push({ el: state.tile, handler });
379 });
380
381 return () => {
382 listeners.forEach(({ el, handler }) => {
383 el.removeEventListener("click", handler);
384 });
385 };
386 };
387
388 const initWidget = ($scope) => {
389 const root = $scope[0]?.querySelector(".king-addons-rotating-image-tiles");
390 if (!root) {
391 return;
392 }
393
394 const settings = parseSettings(root);
395 if (!settings || !Array.isArray(settings.images) || !settings.images.length) {
396 return;
397 }
398
399 applyCssVars(root, settings);
400
401 const tiles = Array.from(
402 root.querySelectorAll(".king-addons-rotating-image-tiles__tile")
403 );
404 if (!tiles.length) {
405 return;
406 }
407
408 const states = tiles.map((tile, index) =>
409 buildTileState(tile, index, settings, settings.images)
410 );
411
412 const disableAnimationOnMobile =
413 settings?.animation?.disableOnMobile === "yes" &&
414 (window.innerWidth || 0) < 768;
415
416 const destroyBehavior = disableAnimationOnMobile
417 ? () => {}
418 : initBehavior(root, states, settings, settings.images);
419 const destroyInteractions = initInteractions(states, settings, settings.images);
420
421 $scope.on("destroy", () => {
422 destroyBehavior();
423 destroyInteractions();
424 states.forEach((state) => clearTimers(state));
425 });
426 };
427
428 $(window).on("elementor/frontend/init", () => {
429 elementorFrontend.hooks.addAction(
430 "frontend/element_ready/king-addons-rotating-image-tiles.default",
431 ($scope) => {
432 initWidget($scope);
433 }
434 );
435 });
436 })(jQuery);
437
438
439
440
441
442
443