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

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

310 lines 19.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 "use strict";
2 (function ($) {
3 $(window).on('elementor/frontend/init', () => {
4 elementorFrontend.hooks.addAction(
5 'frontend/element_ready/king-addons-timeline.default',
6 ($scope) => {
7 elementorFrontend.elementsHandler.addHandler(
8 elementorModules.frontend.handlers.Base.extend({
9 onInit() {
10 const $el = this.$element;
11
12 // Helper to safely find elements or return an empty jQuery object
13 const findOrEmpty = (sel) => {
14 const found = $el.find(sel);
15 return found.length ? found : $();
16 };
17
18 let iScrollTarget = findOrEmpty('.king-addons-timeline-centered'),
19 element = iScrollTarget,
20 pagination = findOrEmpty('.king-addons-grid-pagination'),
21 middleLine = findOrEmpty('.king-addons-middle-line'),
22 timelineFill = findOrEmpty('.king-addons-timeline-fill'),
23 lastIcon = findOrEmpty('.king-addons-main-line-icon.king-addons-icon:last'),
24 firstIcon = findOrEmpty('.king-addons-main-line-icon.king-addons-icon').first(),
25 scopeClass = '.elementor-element-' + $el.attr('data-id'),
26 aosOffset = +findOrEmpty('.king-addons-story-info-vertical').attr('data-animation-offset') || 0,
27 aosDuration = +findOrEmpty('.king-addons-story-info-vertical').attr('data-animation-duration') || 0;
28
29 // Remove "left aligned" class on smaller screens
30 const removeLeftAlignedClass = () => {
31 if ($el.find('.king-addons-centered').length) {
32 if (window.innerWidth <= 767) {
33 $el.find('.king-addons-wrapper .king-addons-timeline-centered')
34 .removeClass('king-addons-both-sided-timeline')
35 .addClass('king-addons-one-sided-timeline king-addons-remove-one-sided-later');
36 $el.find('.king-addons-wrapper .king-addons-left-aligned')
37 .removeClass('king-addons-left-aligned')
38 .addClass('king-addons-right-aligned king-addons-remove-right-aligned-later');
39 } else {
40 $el.find('.king-addons-wrapper .king-addons-timeline-centered.king-addons-remove-one-sided-later')
41 .removeClass('king-addons-one-sided-timeline king-addons-remove-one-sided-later')
42 .addClass('king-addons-both-sided-timeline');
43 $el.find('.king-addons-wrapper .king-addons-remove-right-aligned-later')
44 .removeClass('king-addons-right-aligned king-addons-remove-right-aligned-later')
45 .addClass('king-addons-left-aligned');
46 }
47 }
48 };
49
50 // Fill the timeline line as the user scrolls
51 const postsTimelineFill = (last, first) => {
52 if (!timelineFill.length) return;
53
54 // If first item is preceded by a year label, use that label instead
55 if ($el.find('.king-addons-timeline-entry:eq(0)').prev('.king-addons-year-wrap').length) {
56 first = $el.find('.king-addons-year-label').eq(0);
57 }
58
59 const fillHeight = parseFloat(timelineFill.css('height')),
60 docScrollTop = document.documentElement.scrollTop,
61 clientHeightHalf = document.documentElement.clientHeight / 2,
62 offsetFirst = first.offset().top,
63 offsetLast = last.offset().top,
64 lastHeight = parseFloat(last.css('height'));
65
66 // Only update fill if we haven't scrolled past the last icon
67 if ((docScrollTop + clientHeightHalf - offsetFirst) < (offsetLast - offsetFirst + lastHeight)) {
68 timelineFill.css(
69 'height',
70 (docScrollTop + clientHeightHalf - offsetFirst) + 'px'
71 );
72 }
73
74 // Highlight icons if they are "behind" the fill
75 $el.find('.king-addons-main-line-icon.king-addons-icon').each(function () {
76 const $icon = $(this);
77 $icon.toggleClass(
78 'king-addons-change-border-color',
79 $icon.offset().top < offsetFirst + fillHeight
80 );
81 });
82 };
83
84 // Adjust the vertical line in the center (and its fill) to match icons' positions
85 const adjustMiddleLineHeight = (midLine, tFill, last, first, el) => {
86 if (
87 !$el.find('.king-addons-both-sided-timeline').length &&
88 !$el.find('.king-addons-one-sided-timeline').length &&
89 !$el.find('.king-addons-one-sided-timeline-left').length
90 ) {
91 return;
92 }
93 if ($el.find('.king-addons-timeline-entry:eq(0)').prev('.king-addons-year-wrap').length) {
94 first = $el.find('.king-addons-year-label').eq(0);
95 }
96
97 const offsetEl = el.offset().top,
98 offsetFirst = first.offset().top,
99 offsetLast = last.offset().top,
100 lastHeight = parseFloat(last.css('height')),
101 topPos = (offsetFirst - offsetEl) + 'px',
102 heightVal = (offsetLast - offsetFirst + lastHeight);
103
104 midLine.css({
105 top: topPos,
106 height: heightVal
107 });
108 if (tFill.length) tFill.css('top', topPos);
109 };
110
111 // Only proceed if we have a timeline
112 if (iScrollTarget.length) {
113 // Combine redundant window resize bindings
114 $(window).on('resize smartresize', () => {
115 removeLeftAlignedClass();
116 adjustMiddleLineHeight(middleLine, timelineFill, lastIcon, firstIcon, element);
117 });
118
119 // Fire alignment and size adjustments with a short delay
120 setTimeout(() => {
121 removeLeftAlignedClass();
122 $(window).trigger('resize');
123 }, 500);
124
125 setTimeout(() => {
126 adjustMiddleLineHeight(middleLine, timelineFill, lastIcon, firstIcon, element);
127 $(window).trigger('resize');
128 }, 500);
129
130 // Hide pagination if not "load-more"
131 if (iScrollTarget.attr('data-pagination') !== 'load-more') {
132 pagination.css('visibility', 'hidden');
133 }
134
135 // Initialize AOS
136 // noinspection JSUnresolvedReference
137 AOS.init({
138 offset: aosOffset,
139 duration: aosDuration,
140 once: true,
141 });
142
143 // Fill the timeline on scroll
144 postsTimelineFill(lastIcon, firstIcon);
145 $(window).on('scroll', () => postsTimelineFill(lastIcon, firstIcon));
146
147 // Infinite scroll / load-more logic
148 // noinspection JSJQueryEfficiency
149 if (
150 !$el.find('.elementor-repeater-items').length &&
151 !$('body').hasClass('elementor-editor-active') &&
152 (iScrollTarget.data('pagination') === 'load-more' ||
153 iScrollTarget.data('pagination') === 'infinite-scroll')
154 ) {
155 const threshold =
156 iScrollTarget.data('pagination') === 'load-more' ? false : 10;
157
158 // noinspection JSUnresolvedReference
159 iScrollTarget.infiniteScroll({
160 path: scopeClass + ' .king-addons-grid-pagination a',
161 hideNav: false,
162 append: scopeClass + '.king-addons-timeline-entry',
163 history: false,
164 scrollThreshold: threshold,
165 status: scopeClass + ' .page-load-status',
166 });
167
168 iScrollTarget.on('request.infiniteScroll', () => {
169 $el.find('.king-addons-load-more-btn').hide();
170 $el.find('.king-addons-pagination-loading').css('display', 'inline-block');
171 });
172
173 let pagesLoaded = 0;
174 iScrollTarget.on('load.infiniteScroll', (event, response) => {
175 pagesLoaded++;
176 const items = $(response).find(scopeClass).find('.king-addons-timeline-entry');
177 // noinspection JSUnresolvedReference
178 iScrollTarget.infiniteScroll('appendItems', items);
179
180 // Re-align new items
181 if (
182 !$el.find('.king-addons-one-sided-timeline').length &&
183 !$el.find('.king-addons-one-sided-timeline-left').length
184 ) {
185 $el.find('.king-addons-timeline-entry').each(function (idx) {
186 const $entry = $(this);
187 $entry.removeClass('king-addons-right-aligned king-addons-left-aligned');
188 if (idx % 2 === 0) {
189 $entry.addClass('king-addons-left-aligned');
190 $entry
191 .find('.king-addons-story-info-vertical')
192 .attr('data-aos', $entry.find('.king-addons-story-info-vertical').attr('data-aos-left'));
193 } else {
194 $entry.addClass('king-addons-right-aligned');
195 $entry
196 .find('.king-addons-story-info-vertical')
197 .attr('data-aos', $entry.find('.king-addons-story-info-vertical').attr('data-aos-right'));
198 }
199 });
200 }
201
202 // Re-init AOS for newly added items
203 // noinspection JSUnresolvedReference
204 AOS.init({ offset: aosOffset, duration: aosDuration, once: true });
205 $(window).scroll();
206 $el.find('.king-addons-pagination-loading').hide();
207
208 // Show load more button or finish message
209 if (iScrollTarget.data('max-pages') - 1 !== pagesLoaded) {
210 if (iScrollTarget.attr('data-pagination') === 'load-more') {
211 $el.find('.king-addons-load-more-btn').fadeIn();
212 }
213 } else {
214 $el.find('.king-addons-pagination-finish').fadeIn(1000);
215 pagination.delay(2000).fadeOut(1000);
216 }
217
218 // Update references after new items are appended
219 middleLine = $el.find('.king-addons-middle-line');
220 timelineFill = $el.find(".king-addons-timeline-fill");
221 lastIcon = $el.find('.king-addons-main-line-icon.king-addons-icon:last');
222 firstIcon = $el.find('.king-addons-main-line-icon.king-addons-icon').first();
223 element = $el.find('.king-addons-timeline-centered');
224
225 adjustMiddleLineHeight(middleLine, timelineFill, lastIcon, firstIcon, element);
226 $(window).trigger('resize');
227 postsTimelineFill(lastIcon, firstIcon);
228 });
229
230 // Manual load more
231 if (!$('body').hasClass('elementor-editor-active')) {
232 $el.find('.king-addons-load-more-btn').on('click', () => {
233 // noinspection JSUnresolvedReference
234 iScrollTarget.infiniteScroll('loadNextPage');
235 return false;
236 });
237 if (iScrollTarget.attr('data-pagination') === 'infinite-scroll') {
238 // noinspection JSUnresolvedReference
239 iScrollTarget.infiniteScroll('loadNextPage');
240 }
241 }
242 }
243 }
244
245 // Swiper logic (horizontal timeline)
246 if ($el.find('.swiper-wrapper').length) {
247 const swiperLoader = (swiperElement, swiperConfig) => {
248 const asyncSwiper = elementorFrontend.utils.swiper;
249 return new asyncSwiper(swiperElement, swiperConfig).then(
250 (instance) => instance
251 );
252 };
253
254 // Identify correct horizontal selector
255 const horizontal = $el.find('.king-addons-horizontal-bottom').length
256 ? '.king-addons-horizontal-bottom'
257 : '.king-addons-horizontal';
258 const swiperSlider = $el.find(horizontal + ".swiper");
259 const slidesToShow = swiperSlider.data("slidestoshow");
260
261 swiperLoader(swiperSlider, {
262 spaceBetween: +swiperSlider.data('swiper-space-between'),
263 loop: swiperSlider.data('loop') === 'yes',
264 autoplay:
265 swiperSlider.data("autoplay") === 'yes'
266 ? {
267 delay: +swiperSlider.attr('data-swiper-delay'),
268 disableOnInteraction: false,
269 pauseOnMouseEnter:
270 swiperSlider.data('swiper-poh') === 'yes',
271 }
272 : false,
273 on: {
274 init: () => {
275 const timelineOuter = $el.find('.king-addons-timeline-outer-container');
276 if (timelineOuter.length) timelineOuter.css('opacity', 1);
277 },
278 },
279 speed: +swiperSlider.attr('data-swiper-speed'),
280 slidesPerView: slidesToShow,
281 direction: 'horizontal',
282 pagination: {
283 el: '.king-addons-swiper-pagination',
284 type: 'progressbar',
285 },
286 navigation: {
287 nextEl: '.king-addons-button-next',
288 prevEl: '.king-addons-button-prev',
289 },
290 breakpoints: {
291 320: { slidesPerView: 1 },
292 480: { slidesPerView: 2 },
293 769: { slidesPerView: slidesToShow },
294 },
295 });
296 } else {
297 // If no slider, just make timeline visible
298 $(document).ready(() => {
299 const timelineOuter = $el.find('.king-addons-timeline-outer-container');
300 if (timelineOuter.length) timelineOuter.css('opacity', 1);
301 });
302 }
303 },
304 }),
305 { $element: $scope }
306 );
307 }
308 );
309 });
310 })(jQuery);