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

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

404 lines 14.1 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 const lerp = (start, end, amount) => start + (end - start) * amount;
6 const getNumber = (value, fallback) => {
7 const parsed = parseFloat(value);
8 return Number.isFinite(parsed) ? parsed : fallback;
9 };
10
11 const resolvePerformanceMode = (mode, cardCount, reducedMotion) => {
12 if (mode && mode !== "auto") {
13 return mode;
14 }
15
16 if (reducedMotion) {
17 return "performance";
18 }
19
20 const isMobile = window.matchMedia && window.matchMedia("(max-width: 767px)").matches;
21 const lowMemory = navigator.deviceMemory && navigator.deviceMemory <= 4;
22 const lowCores = navigator.hardwareConcurrency && navigator.hardwareConcurrency <= 4;
23
24 if (isMobile && cardCount > 8) {
25 return "performance";
26 }
27
28 if (lowMemory || lowCores) {
29 return "balanced";
30 }
31
32 return "quality";
33 };
34
35 const capRootVar = (root, name, cap, unit) => {
36 const value = getNumber(getComputedStyle(root).getPropertyValue(name), 0);
37 if (value > cap) {
38 root.style.setProperty(name, `${cap}${unit || ""}`);
39 }
40 };
41
42 const applyPerformanceOverrides = (root, mode) => {
43 if (mode === "balanced") {
44 capRootVar(root, "--kng-liquid-blur", 16, "px");
45 capRootVar(root, "--kng-liquid-noise-opacity", 0.12, "");
46 capRootVar(root, "--kng-liquid-highlight-opacity", 0.35, "");
47 }
48
49 if (mode === "performance") {
50 capRootVar(root, "--kng-liquid-blur", 10, "px");
51 capRootVar(root, "--kng-liquid-noise-opacity", 0.08, "");
52 capRootVar(root, "--kng-liquid-highlight-opacity", 0.25, "");
53 }
54 };
55
56 const initLiquidGlass = ($scope) => {
57 const root = $scope[0]?.querySelector(".king-liquid-glass");
58 if (!root) {
59 return;
60 }
61
62 let settings = {};
63 try {
64 settings = JSON.parse(root.dataset.settings || "{}");
65 } catch (error) {
66 settings = {};
67 }
68
69 const tilt = settings.tilt || {};
70 const parallax = settings.parallax || {};
71 const performance = settings.performance || {};
72
73 const cards = Array.from(root.querySelectorAll(".king-liquid-glass__card"));
74 if (!cards.length) {
75 return;
76 }
77
78 const reducedMotion = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
79 const reduceTransparency = window.matchMedia && window.matchMedia("(prefers-reduced-transparency: reduce)").matches;
80 const highContrast = window.matchMedia && window.matchMedia("(prefers-contrast: more)").matches;
81
82 if (reduceTransparency) {
83 root.classList.add("king-liquid-glass--reduced-transparency");
84 }
85
86 if (highContrast) {
87 root.classList.add("king-liquid-glass--high-contrast");
88 }
89
90 const perfMode = resolvePerformanceMode(performance.mode || "auto", settings.cardCount || cards.length, reducedMotion);
91 applyPerformanceOverrides(root, perfMode);
92
93 if (reducedMotion || perfMode === "performance") {
94 tilt.enabled = false;
95 parallax.enabled = false;
96 }
97
98 const hasTilt = !!tilt.enabled;
99 const hasParallax = !!parallax.enabled;
100
101 if (!hasTilt && !hasParallax) {
102 return;
103 }
104
105 const isEditMode = window.elementorFrontend && elementorFrontend.isEditMode && elementorFrontend.isEditMode();
106 let tiltInput = tilt.input || "pointer";
107
108 if (tiltInput === "orientation") {
109 if (isEditMode || !("DeviceOrientationEvent" in window)) {
110 tiltInput = "pointer";
111 } else if (typeof DeviceOrientationEvent.requestPermission === "function") {
112 tiltInput = "pointer";
113 }
114 }
115
116 const glareEnabled = !!(tilt.glare && tilt.glare.enabled);
117 const glareStrength = glareEnabled ? getNumber(tilt.glare.intensity, 0.35) : 0;
118 if (glareEnabled) {
119 root.style.setProperty("--kng-liquid-glare-size", `${getNumber(tilt.glare.size, 70)}%`);
120 root.style.setProperty("--kng-liquid-glare-blend", tilt.glare.blend || "screen");
121 }
122
123 const scale = perfMode === "balanced" ? 0.8 : 1;
124 const maxTilt = clamp(getNumber(tilt.max, 8) * scale, 0, 20);
125 const smoothing = clamp(getNumber(tilt.smoothing, 0.12), 0.02, 0.5);
126 const liftDistance = tilt.lift && tilt.lift.enabled ? getNumber(tilt.lift.distance, 10) : 0;
127 const parallaxScale = perfMode === "balanced" ? 0.7 : 1;
128 const pointerParallax = hasParallax && (parallax.mode || "pointer") === "pointer";
129 const scrollParallax = hasParallax && (parallax.mode || "pointer") === "scroll";
130
131 if (isEditMode) {
132 return;
133 }
134
135 const cardData = cards.map((card) => ({
136 card,
137 layers: Array.from(card.querySelectorAll("[data-depth]")),
138 }));
139
140 const state = {
141 active: null,
142 rect: null,
143 targetX: 0,
144 targetY: 0,
145 currentX: 0,
146 currentY: 0,
147 rafPointer: null,
148 rafScroll: null,
149 inView: true,
150 };
151
152 const resetCard = (data) => {
153 if (!data) {
154 return;
155 }
156 data.card.classList.remove("is-tilting");
157 data.card.style.transform = "";
158 data.card.style.removeProperty("--kng-liquid-highlight-x");
159 data.card.style.removeProperty("--kng-liquid-highlight-y");
160 data.card.style.removeProperty("--kng-liquid-glare-strength");
161 if (pointerParallax) {
162 data.layers.forEach((layer) => {
163 layer.style.transform = "";
164 });
165 }
166 };
167
168 const setActiveCard = (data) => {
169 if (state.active === data) {
170 return;
171 }
172 resetCard(state.active);
173 state.active = data;
174 state.currentX = 0;
175 state.currentY = 0;
176 state.targetX = 0;
177 state.targetY = 0;
178 if (data) {
179 state.rect = data.card.getBoundingClientRect();
180 data.card.classList.add("is-tilting");
181 } else {
182 state.rect = null;
183 }
184 };
185
186 const updateTargets = (clientX, clientY) => {
187 if (!state.rect) {
188 return;
189 }
190 const x = clamp((clientX - state.rect.left) / state.rect.width, 0, 1) * 2 - 1;
191 const y = clamp((clientY - state.rect.top) / state.rect.height, 0, 1) * 2 - 1;
192 state.targetX = x;
193 state.targetY = y;
194 };
195
196 const applyCardTransforms = (data, normX, normY) => {
197 if (!data) {
198 return;
199 }
200
201 if (hasTilt) {
202 const tiltX = clamp(-normY * maxTilt, -maxTilt, maxTilt);
203 const tiltY = clamp(normX * maxTilt, -maxTilt, maxTilt);
204 const translateY = liftDistance ? -liftDistance : 0;
205 data.card.style.transform = `translate3d(0, ${translateY}px, 0) rotateX(${tiltX}deg) rotateY(${tiltY}deg)`;
206
207 if (glareEnabled) {
208 const glareX = clamp((normX + 1) * 50, 0, 100);
209 const glareY = clamp((normY + 1) * 50, 0, 100);
210 data.card.style.setProperty("--kng-liquid-highlight-x", `${glareX}%`);
211 data.card.style.setProperty("--kng-liquid-highlight-y", `${glareY}%`);
212 data.card.style.setProperty("--kng-liquid-glare-strength", String(glareStrength));
213 }
214 }
215
216 if (pointerParallax) {
217 data.layers.forEach((layer) => {
218 const depth = getNumber(layer.dataset.depth, 0) * parallaxScale;
219 if (!depth) {
220 layer.style.transform = "";
221 return;
222 }
223 const translateX = normX * depth;
224 const translateY = normY * depth;
225 const scaleValue = 1 + Math.min(Math.abs(depth) / 120, 0.06);
226 layer.style.transform = `translate3d(${translateX}px, ${translateY}px, 0) scale(${scaleValue})`;
227 });
228 }
229 };
230
231 const animatePointer = () => {
232 if (!state.active) {
233 state.rafPointer = null;
234 return;
235 }
236
237 state.currentX = lerp(state.currentX, state.targetX, smoothing);
238 state.currentY = lerp(state.currentY, state.targetY, smoothing);
239
240 applyCardTransforms(state.active, state.currentX, state.currentY);
241
242 const deltaX = Math.abs(state.targetX - state.currentX);
243 const deltaY = Math.abs(state.targetY - state.currentY);
244
245 if (deltaX > 0.001 || deltaY > 0.001) {
246 state.rafPointer = requestAnimationFrame(animatePointer);
247 } else {
248 state.rafPointer = null;
249 }
250 };
251
252 const requestPointerTick = () => {
253 if (state.rafPointer) {
254 return;
255 }
256 state.rafPointer = requestAnimationFrame(animatePointer);
257 };
258
259 const onPointerMove = (event) => {
260 if (!state.inView) {
261 return;
262 }
263 const card = event.target.closest(".king-liquid-glass__card");
264 if (!card || !root.contains(card)) {
265 onPointerLeave();
266 return;
267 }
268 const data = cardData.find((item) => item.card === card);
269 if (!data) {
270 return;
271 }
272 if (state.active !== data) {
273 setActiveCard(data);
274 }
275 updateTargets(event.clientX, event.clientY);
276 requestPointerTick();
277 };
278
279 const onPointerLeave = () => {
280 if (!state.active) {
281 return;
282 }
283 resetCard(state.active);
284 state.active = null;
285 state.rect = null;
286 };
287
288 const setupPointer = () => {
289 if (!hasTilt && !pointerParallax) {
290 return;
291 }
292 root.addEventListener("pointermove", onPointerMove);
293 root.addEventListener("pointerleave", onPointerLeave);
294 };
295
296 const setupOrientation = () => {
297 if (!hasTilt || tiltInput !== "orientation") {
298 return;
299 }
300
301 const handleOrientation = (event) => {
302 if (!state.inView) {
303 return;
304 }
305 const gamma = getNumber(event.gamma, 0);
306 const beta = getNumber(event.beta, 0);
307 state.targetX = clamp(gamma / 25, -1, 1);
308 state.targetY = clamp(beta / 25, -1, 1);
309
310 if (!state.rafPointer) {
311 state.rafPointer = requestAnimationFrame(() => {
312 state.currentX = lerp(state.currentX, state.targetX, smoothing);
313 state.currentY = lerp(state.currentY, state.targetY, smoothing);
314 cardData.forEach((data) => applyCardTransforms(data, state.currentX, state.currentY));
315 state.rafPointer = null;
316 });
317 }
318 };
319
320 window.addEventListener("deviceorientation", handleOrientation, { passive: true });
321 };
322
323 const setupScrollParallax = () => {
324 if (!scrollParallax) {
325 return;
326 }
327
328 const updateScroll = () => {
329 if (!state.inView) {
330 state.rafScroll = null;
331 return;
332 }
333
334 const viewportHeight = window.innerHeight || 0;
335
336 cardData.forEach((data) => {
337 const rect = data.card.getBoundingClientRect();
338 const progress = clamp((viewportHeight - rect.top) / (viewportHeight + rect.height), 0, 1);
339 const offset = (progress - 0.5) * 2;
340
341 data.layers.forEach((layer) => {
342 const depth = getNumber(layer.dataset.depth, 0) * parallaxScale;
343 if (!depth) {
344 layer.style.transform = "";
345 return;
346 }
347 const translateY = -offset * depth;
348 const scaleValue = 1 + Math.min(Math.abs(depth) / 120, 0.06);
349 layer.style.transform = `translate3d(0, ${translateY}px, 0) scale(${scaleValue})`;
350 });
351 });
352
353 state.rafScroll = null;
354 };
355
356 const onScroll = () => {
357 if (state.rafScroll) {
358 return;
359 }
360 state.rafScroll = requestAnimationFrame(updateScroll);
361 };
362
363 window.addEventListener("scroll", onScroll, { passive: true });
364 window.addEventListener("resize", onScroll);
365 updateScroll();
366 };
367
368 const setupObserver = () => {
369 if (!("IntersectionObserver" in window)) {
370 return;
371 }
372 const observer = new IntersectionObserver(
373 (entries) => {
374 entries.forEach((entry) => {
375 state.inView = entry.isIntersecting;
376 if (!state.inView) {
377 onPointerLeave();
378 }
379 });
380 },
381 {
382 threshold: 0.1,
383 }
384 );
385 observer.observe(root);
386 };
387
388 setupObserver();
389 if (tiltInput === "orientation") {
390 setupOrientation();
391 } else {
392 setupPointer();
393 }
394 setupScrollParallax();
395 };
396
397 $(window).on("elementor/frontend/init", () => {
398 elementorFrontend.hooks.addAction(
399 "frontend/element_ready/king-addons-liquid-glass-cards.default",
400 initLiquidGlass
401 );
402 });
403 })(jQuery);
404