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 / Values_Circle_Infographic / 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/Values_Circle_Infographic/script.js

323 lines 13.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (function () {
2 'use strict';
3
4 var SVG_NS = 'http://www.w3.org/2000/svg';
5
6 function clamp(value, min, max) {
7 return Math.min(max, Math.max(min, value));
8 }
9
10 function quadAt(a, b, c, t) {
11 var omt = 1 - t;
12 return (omt * omt * a) + (2 * omt * t * b) + (t * t * c);
13 }
14
15 function pointToViewBox(point, rect, viewBoxWidth, viewBoxHeight) {
16 return {
17 x: (point.x / rect.width) * viewBoxWidth,
18 y: (point.y / rect.height) * viewBoxHeight,
19 };
20 }
21
22 function getLocalRect(el, containerRect) {
23 var r = el.getBoundingClientRect();
24 return {
25 left: r.left - containerRect.left,
26 right: r.right - containerRect.left,
27 top: r.top - containerRect.top,
28 bottom: r.bottom - containerRect.top,
29 width: r.width,
30 height: r.height,
31 cx: (r.left - containerRect.left) + (r.width / 2),
32 cy: (r.top - containerRect.top) + (r.height / 2),
33 };
34 }
35
36 function createCircle(cx, cy, r) {
37 var circle = document.createElementNS(SVG_NS, 'circle');
38 circle.setAttribute('class', 'ka-values-circle-dot');
39 circle.setAttribute('cx', cx.toFixed(2));
40 circle.setAttribute('cy', cy.toFixed(2));
41 circle.setAttribute('r', r.toFixed(2));
42 return circle;
43 }
44
45 function drawDottedCurve(targetGroup, rect, viewBox, startPoint, controlPoint, endPoint, baseStep, dotRSmall, dotRLarge) {
46 var start = pointToViewBox(startPoint, rect, viewBox.width, viewBox.height);
47 var ctrl = pointToViewBox(controlPoint, rect, viewBox.width, viewBox.height);
48 var end = pointToViewBox(endPoint, rect, viewBox.width, viewBox.height);
49
50 var estimateLength = Math.hypot(end.x - start.x, end.y - start.y) * 1.08;
51 var step = clamp(baseStep, 20, 34);
52 var dotCount = clamp(Math.round(estimateLength / step), 5, 13);
53
54 for (var i = 0; i < dotCount; i++) {
55 var t = dotCount === 1 ? 0.5 : i / (dotCount - 1);
56 var x = quadAt(start.x, ctrl.x, end.x, t);
57 var y = quadAt(start.y, ctrl.y, end.y, t);
58 // Pattern: small, small, BIG — repeating every 3 dots
59 var radius = (i % 3 === 2) ? dotRLarge : dotRSmall;
60 targetGroup.appendChild(createCircle(x, y, radius));
61 }
62 }
63
64 function renderConnectors(widgetRoot) {
65 var svgLayer = widgetRoot.querySelector('.king-addons-values-circle-svg-layer');
66 var svg = svgLayer ? svgLayer.querySelector('svg') : null;
67 if (!svg) return;
68
69 var connectorsGroup = svg.querySelector('.ka-values-circle-connectors-dynamic');
70 if (!connectorsGroup) return;
71
72 var topLeft = widgetRoot.querySelector('.king-addons-values-circle-item--top-left');
73 var topRight = widgetRoot.querySelector('.king-addons-values-circle-item--top-right');
74 var middleLeft = widgetRoot.querySelector('.king-addons-values-circle-item--middle-left');
75 var middleRight = widgetRoot.querySelector('.king-addons-values-circle-item--middle-right');
76 var bottomLeft = widgetRoot.querySelector('.king-addons-values-circle-item--bottom-left');
77 var bottomRight = widgetRoot.querySelector('.king-addons-values-circle-item--bottom-right');
78
79 if (!topLeft || !topRight || !middleLeft || !middleRight || !bottomLeft || !bottomRight) {
80 return;
81 }
82
83 var wrapRect = widgetRoot.getBoundingClientRect();
84 if (!wrapRect.width || !wrapRect.height) {
85 return;
86 }
87
88 var tl = getLocalRect(topLeft, wrapRect);
89 var tr = getLocalRect(topRight, wrapRect);
90 var ml = getLocalRect(middleLeft, wrapRect);
91 var mr = getLocalRect(middleRight, wrapRect);
92 var bl = getLocalRect(bottomLeft, wrapRect);
93 var br = getLocalRect(bottomRight, wrapRect);
94
95 var viewBox = svg.viewBox && svg.viewBox.baseVal
96 ? { width: svg.viewBox.baseVal.width, height: svg.viewBox.baseVal.height }
97 : { width: 1000, height: 860 };
98
99 connectorsGroup.innerHTML = '';
100
101 // Global gap from block edges (px)
102 var gap = parseFloat(widgetRoot.dataset.connGap) || 20;
103 var dotRS = parseFloat(widgetRoot.dataset.dotRSmall) || 5;
104 var dotRL = parseFloat(widgetRoot.dataset.dotRLarge) || 9.5;
105
106 // Read per-connector settings from wrapper data attributes
107 function connData(name) {
108 var d = widgetRoot.dataset;
109 return {
110 bend: parseFloat(d['conn' + name + 'Bend']) || 0,
111 startX: parseFloat(d['conn' + name + 'StartX']) || 0,
112 startY: parseFloat(d['conn' + name + 'StartY']) || 0,
113 endX: parseFloat(d['conn' + name + 'EndX']) || 0,
114 endY: parseFloat(d['conn' + name + 'EndY']) || 0,
115 };
116 }
117
118 // camelCase keys match data-conn-{id}-{prop} HTML attributes
119 var cTop = connData('Top');
120 var cBottom = connData('Bottom');
121 var cLeftTop = connData('LeftTop');
122 var cLeftBottom = connData('LeftBottom');
123 var cRightTop = connData('RightTop');
124 var cRightBottom = connData('RightBottom');
125
126 // ── Top arc: tl right-edge → tr left-edge ────────────────────
127 var topStart = { x: tl.right + gap + cTop.startX, y: tl.cy + cTop.startY };
128 var topEnd = { x: tr.left - gap + cTop.endX, y: tr.cy + cTop.endY };
129 var topCtrlY = Math.min(topStart.y, topEnd.y) - cTop.bend;
130
131 // ── Bottom arc: bl right-edge → br left-edge ─────────────────
132 var botStart = { x: bl.right + gap + cBottom.startX, y: bl.cy + cBottom.startY };
133 var botEnd = { x: br.left - gap + cBottom.endX, y: br.cy + cBottom.endY };
134 var botCtrlY = Math.max(botStart.y, botEnd.y) + cBottom.bend;
135
136 // ── Left-Top arc: tl bottom → ml top ─────────────────────────
137 var ltStart = { x: tl.cx + cLeftTop.startX, y: tl.bottom + gap + cLeftTop.startY };
138 var ltEnd = { x: ml.cx + cLeftTop.endX, y: ml.top - gap + cLeftTop.endY };
139 var ltCtrlX = Math.min(ltStart.x, ltEnd.x) - cLeftTop.bend;
140
141 // ── Left-Bottom arc: ml bottom → bl top ──────────────────────
142 var lbStart = { x: ml.cx + cLeftBottom.startX, y: ml.bottom + gap + cLeftBottom.startY };
143 var lbEnd = { x: bl.cx + cLeftBottom.endX, y: bl.top - gap + cLeftBottom.endY };
144 var lbCtrlX = Math.min(lbStart.x, lbEnd.x) - cLeftBottom.bend;
145
146 // ── Right-Top arc: tr bottom → mr top ────────────────────────
147 var rtStart = { x: tr.cx + cRightTop.startX, y: tr.bottom + gap + cRightTop.startY };
148 var rtEnd = { x: mr.cx + cRightTop.endX, y: mr.top - gap + cRightTop.endY };
149 var rtCtrlX = Math.max(rtStart.x, rtEnd.x) + cRightTop.bend;
150
151 // ── Right-Bottom arc: mr bottom → br top ─────────────────────
152 var rbStart = { x: mr.cx + cRightBottom.startX, y: mr.bottom + gap + cRightBottom.startY };
153 var rbEnd = { x: br.cx + cRightBottom.endX, y: br.top - gap + cRightBottom.endY };
154 var rbCtrlX = Math.max(rbStart.x, rbEnd.x) + cRightBottom.bend;
155
156 drawDottedCurve(connectorsGroup, wrapRect, viewBox, topStart,
157 { x: (topStart.x + topEnd.x) / 2, y: topCtrlY }, topEnd, 27, dotRS, dotRL);
158
159 drawDottedCurve(connectorsGroup, wrapRect, viewBox, botStart,
160 { x: (botStart.x + botEnd.x) / 2, y: botCtrlY }, botEnd, 27, dotRS, dotRL);
161
162 drawDottedCurve(connectorsGroup, wrapRect, viewBox, ltStart,
163 { x: ltCtrlX, y: (ltStart.y + ltEnd.y) / 2 }, ltEnd, 25, dotRS, dotRL);
164
165 drawDottedCurve(connectorsGroup, wrapRect, viewBox, lbStart,
166 { x: lbCtrlX, y: (lbStart.y + lbEnd.y) / 2 }, lbEnd, 25, dotRS, dotRL);
167
168 drawDottedCurve(connectorsGroup, wrapRect, viewBox, rtStart,
169 { x: rtCtrlX, y: (rtStart.y + rtEnd.y) / 2 }, rtEnd, 25, dotRS, dotRL);
170
171 drawDottedCurve(connectorsGroup, wrapRect, viewBox, rbStart,
172 { x: rbCtrlX, y: (rbStart.y + rbEnd.y) / 2 }, rbEnd, 25, dotRS, dotRL);
173 }
174
175 function isMobileLayout() {
176 return window.innerWidth <= 767;
177 }
178
179 function renderMobileConnectors(widgetRoot) {
180 var groups = widgetRoot.querySelectorAll('.ka-vcm-dots');
181 if (!groups.length) return;
182
183 var dotRS = parseFloat(widgetRoot.dataset.mobileDotRSmall) || 5.5;
184 var dotRL = parseFloat(widgetRoot.dataset.mobileDotRLarge) || 8;
185 var edgeGap = parseFloat(widgetRoot.dataset.mobileConnGap) || 6;
186 var stepPx = parseFloat(widgetRoot.dataset.mobileDotStep) || 28;
187
188 groups.forEach(function (dotsGroup) {
189 dotsGroup.innerHTML = '';
190 var svg = dotsGroup.closest('svg');
191 if (!svg) return;
192
193 var rect = svg.getBoundingClientRect();
194 var renderedWidth = rect.width || 50;
195 var renderedHeight = rect.height || 50;
196
197 // Keep SVG coordinate space equal to rendered size,
198 // so connector length truly grows when CSS height grows.
199 svg.setAttribute('viewBox', '0 0 ' + renderedWidth + ' ' + renderedHeight);
200
201 var vb = { width: renderedWidth, height: renderedHeight };
202
203 var cx = vb.width / 2;
204 var startY = dotRL + edgeGap;
205 var endY = vb.height - dotRL - edgeGap;
206 var length = endY - startY;
207 if (length <= 0) return; // no room for dots
208 var dotCount = clamp(Math.round(length / stepPx), 1, 18);
209
210 for (var i = 0; i < dotCount; i++) {
211 var t = (dotCount === 1) ? 0.5 : i / (dotCount - 1);
212 var y = startY + length * t;
213 var r = (i % 3 === 2) ? dotRL : dotRS;
214 dotsGroup.appendChild(createCircle(cx, y, r));
215 }
216 });
217 }
218
219 function scheduleRender(widgetRoot) {
220 if (widgetRoot.__kaValuesCircleRaf) {
221 cancelAnimationFrame(widgetRoot.__kaValuesCircleRaf);
222 }
223
224 widgetRoot.__kaValuesCircleRaf = requestAnimationFrame(function () {
225 if (isMobileLayout()) {
226 renderMobileConnectors(widgetRoot);
227 } else {
228 renderConnectors(widgetRoot);
229 }
230 });
231 }
232
233 function initCenterLottie(widgetRoot) {
234 // Init ALL lottie divs (desktop centre + mobile view duplicate)
235 var els = widgetRoot.querySelectorAll('.king-addons-values-circle-lottie');
236 if (!els.length) return;
237
238 var lottieLib = (typeof lottie !== 'undefined') ? lottie
239 : (typeof window.lottie !== 'undefined') ? window.lottie
240 : (typeof window.Lottie !== 'undefined') ? window.Lottie
241 : null;
242 if (!lottieLib) return;
243
244 els.forEach(function (el) {
245 if (el.__kaLottieInited) return;
246
247 var cfg = {};
248 try { cfg = JSON.parse(el.getAttribute('data-settings') || '{}'); } catch (e) {}
249
250 var anim = lottieLib.loadAnimation({
251 container: el,
252 path: el.getAttribute('data-json-url'),
253 renderer: cfg.renderer || 'svg',
254 loop: cfg.loop !== 'no',
255 autoplay: cfg.autoplay !== 'no',
256 });
257 anim.setSpeed(parseFloat(cfg.speed) || 1);
258 if (cfg.reverse === 'yes') anim.setDirection(-1);
259 el.__kaLottieInited = true;
260 });
261 }
262
263 function initWidget(widgetRoot) {
264 if (!widgetRoot) return;
265 initCenterLottie(widgetRoot);
266 scheduleRender(widgetRoot);
267
268 if (!widgetRoot.__kaValuesCircleResizeBound) {
269 widgetRoot.__kaValuesCircleResizeBound = true;
270 window.addEventListener('resize', function () {
271 scheduleRender(widgetRoot);
272 }, { passive: true });
273 }
274
275 if (!widgetRoot.__kaValuesCircleObserver) {
276 var observer = new MutationObserver(function () {
277 scheduleRender(widgetRoot);
278 });
279
280 observer.observe(widgetRoot, {
281 attributes: true,
282 attributeFilter: ['style', 'class'],
283 childList: true,
284 subtree: true,
285 });
286
287 widgetRoot.__kaValuesCircleObserver = observer;
288 }
289 }
290
291 function initAll() {
292 var widgets = document.querySelectorAll('.king-addons-values-circle-wrap');
293 widgets.forEach(initWidget);
294 }
295
296 if (document.readyState === 'loading') {
297 document.addEventListener('DOMContentLoaded', initAll);
298 } else {
299 initAll();
300 }
301
302 window.addEventListener('load', initAll);
303
304 if (typeof window.elementorFrontend !== 'undefined') {
305 window.addEventListener('elementor/frontend/init', function () {
306 if (!window.elementorFrontend.hooks) return;
307
308 window.elementorFrontend.hooks.addAction(
309 'frontend/element_ready/king-addons-values-circle-infographic.default',
310 function ($scope) {
311 var widgetRoot = $scope && $scope[0]
312 ? $scope[0].querySelector('.king-addons-values-circle-wrap')
313 : null;
314
315 if (widgetRoot) {
316 initWidget(widgetRoot);
317 }
318 }
319 );
320 });
321 }
322 })();
323