includes/widgets/Woo_Product_Images_Gallery
Woo_Product_Images_Gallery.php
19.8 KB
2 weeks ago
script.js
12.9 KB
2 weeks ago
style.css
7.4 KB
2 weeks ago
script.js in King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder 51.1.79, at includes/widgets/Woo_Product_Images_Gallery/script.js
| 1 | /** |
| 2 | * Woo Product Images Gallery widget behavior. |
| 3 | * |
| 4 | * Provides gallery navigation, optional lightbox, and optional zoom. |
| 5 | */ |
| 6 | (function ($) { |
| 7 | "use strict"; |
| 8 | |
| 9 | const INIT_FLAG = "kaWooGalleryInit"; |
| 10 | const KEYUP_FLAG = "kaWooGalleryKeyupBound"; |
| 11 | |
| 12 | // Global active lightbox controller (single active at a time). |
| 13 | window.kaWooGalleryActive = window.kaWooGalleryActive || null; |
| 14 | |
| 15 | const lockScroll = () => { |
| 16 | document.body.classList.add('ka-woo-gallery--lock'); |
| 17 | }; |
| 18 | |
| 19 | const unlockScroll = () => { |
| 20 | document.body.classList.remove('ka-woo-gallery--lock'); |
| 21 | }; |
| 22 | |
| 23 | const enableZoom = (slide) => { |
| 24 | if (!slide || slide.dataset.zoomBound === 'yes') return; |
| 25 | if (window.matchMedia('(hover: none)').matches) return; // avoid on touch-only |
| 26 | const full = slide.dataset.full; |
| 27 | if (!full) return; |
| 28 | const lens = document.createElement('div'); |
| 29 | lens.className = 'ka-woo-gallery__zoom-lens'; |
| 30 | lens.style.backgroundImage = `url(${full})`; |
| 31 | slide.appendChild(lens); |
| 32 | slide.dataset.zoomBound = 'yes'; |
| 33 | |
| 34 | const move = (event) => { |
| 35 | const rect = slide.getBoundingClientRect(); |
| 36 | const x = event.clientX - rect.left; |
| 37 | const y = event.clientY - rect.top; |
| 38 | const xPercent = Math.max(0, Math.min(100, (x / rect.width) * 100)); |
| 39 | const yPercent = Math.max(0, Math.min(100, (y / rect.height) * 100)); |
| 40 | lens.style.left = `${x - lens.offsetWidth / 2}px`; |
| 41 | lens.style.top = `${y - lens.offsetHeight / 2}px`; |
| 42 | lens.style.backgroundPosition = `${xPercent}% ${yPercent}%`; |
| 43 | lens.classList.add('is-visible'); |
| 44 | }; |
| 45 | |
| 46 | slide.addEventListener('mousemove', move); |
| 47 | slide.addEventListener('mouseenter', () => lens.classList.add('is-visible')); |
| 48 | slide.addEventListener('mouseleave', () => lens.classList.remove('is-visible')); |
| 49 | }; |
| 50 | |
| 51 | const initGallery = (wrapper) => { |
| 52 | if (!wrapper || !wrapper.dataset) return; |
| 53 | if (wrapper.dataset[INIT_FLAG] === "1") return; |
| 54 | wrapper.dataset[INIT_FLAG] = "1"; |
| 55 | |
| 56 | const slides = wrapper.querySelectorAll('.ka-woo-gallery__slide'); |
| 57 | const thumbs = wrapper.querySelectorAll('.ka-woo-gallery__thumb'); |
| 58 | if (!slides.length) return; |
| 59 | |
| 60 | const layout = wrapper.dataset.layout || 'slider'; |
| 61 | const mobileLayout = wrapper.dataset.mobileLayout || ''; |
| 62 | const loop = wrapper.dataset.loop === 'yes'; |
| 63 | const autoplay = wrapper.dataset.autoplay === 'yes'; |
| 64 | const speed = parseInt(wrapper.dataset.speed || '4000', 10); |
| 65 | const captions = wrapper.dataset.captions === 'yes'; |
| 66 | const lightboxEnabled = wrapper.dataset.lightbox === 'yes'; |
| 67 | const zoomEnabled = wrapper.classList.contains('ka-woo-gallery--zoom'); |
| 68 | const lightboxSkin = wrapper.dataset.lightboxSkin || 'dark'; |
| 69 | const lightboxCounterEnabled = wrapper.dataset.lightboxCounter === 'yes'; |
| 70 | const lightboxThumbsEnabled = wrapper.dataset.lightboxThumbs === 'yes'; |
| 71 | let activeIndex = 0; |
| 72 | let timer = null; |
| 73 | |
| 74 | const overlayId = `ka-woo-gallery-lightbox-${Math.random().toString(36).slice(2)}`; |
| 75 | |
| 76 | const lightbox = document.createElement('div'); |
| 77 | lightbox.className = 'ka-woo-gallery__lightbox'; |
| 78 | lightbox.id = overlayId; |
| 79 | lightbox.innerHTML = ` |
| 80 | <button type="button" class="ka-woo-gallery__lightbox-close" aria-label="Close">×</button> |
| 81 | <div class="ka-woo-gallery__lightbox-body"> |
| 82 | <div class="ka-woo-gallery__lightbox-top"> |
| 83 | <div class="ka-woo-gallery__lightbox-counter"></div> |
| 84 | <div class="ka-woo-gallery__lightbox-nav"> |
| 85 | <button class="ka-woo-gallery__lb-prev" aria-label="Previous">❮</button> |
| 86 | <button class="ka-woo-gallery__lb-next" aria-label="Next">❯</button> |
| 87 | </div> |
| 88 | </div> |
| 89 | <div class="ka-woo-gallery__lightbox-media"> |
| 90 | <img alt="" /> |
| 91 | <div class="ka-woo-gallery__lightbox-caption"></div> |
| 92 | </div> |
| 93 | <div class="ka-woo-gallery__lightbox-thumbs"></div> |
| 94 | </div> |
| 95 | `; |
| 96 | if (lightboxEnabled) { |
| 97 | document.body.appendChild(lightbox); |
| 98 | lightbox.classList.add('ka-woo-gallery__lightbox--' + lightboxSkin); |
| 99 | } |
| 100 | |
| 101 | const lightboxImg = lightbox.querySelector('img'); |
| 102 | const lightboxCaption = lightbox.querySelector('.ka-woo-gallery__lightbox-caption'); |
| 103 | const lightboxThumbs = lightbox.querySelector('.ka-woo-gallery__lightbox-thumbs'); |
| 104 | const lightboxCounter = lightbox.querySelector('.ka-woo-gallery__lightbox-counter'); |
| 105 | const lbPrev = lightbox.querySelector('.ka-woo-gallery__lb-prev'); |
| 106 | const lbNext = lightbox.querySelector('.ka-woo-gallery__lb-next'); |
| 107 | const lbClose = lightbox.querySelector('.ka-woo-gallery__lightbox-close'); |
| 108 | const lightboxThumbButtons = []; |
| 109 | |
| 110 | const updateCounter = () => { |
| 111 | if (!lightboxCounterEnabled || !lightboxCounter) return; |
| 112 | lightboxCounter.textContent = `${activeIndex + 1} / ${slides.length}`; |
| 113 | }; |
| 114 | |
| 115 | const syncLightboxThumbs = () => { |
| 116 | if (!lightboxThumbsEnabled || !lightboxThumbButtons.length) return; |
| 117 | lightboxThumbButtons.forEach((btn, i) => btn.classList.toggle('is-active', i === activeIndex)); |
| 118 | }; |
| 119 | |
| 120 | const setActive = (index) => { |
| 121 | activeIndex = index; |
| 122 | slides.forEach((slide, i) => { |
| 123 | const isGrid = layout === 'grid' || layout === 'masonry' || (mobileLayout === 'grid' && window.matchMedia('(max-width: 767px)').matches); |
| 124 | slide.style.display = i === activeIndex || isGrid ? 'block' : 'none'; |
| 125 | }); |
| 126 | thumbs.forEach((thumb, i) => { |
| 127 | thumb.classList.toggle('is-active', i === activeIndex); |
| 128 | }); |
| 129 | dots.forEach((dot, i) => { |
| 130 | dot.classList.toggle('is-active', i === activeIndex); |
| 131 | }); |
| 132 | if (lightboxEnabled && captions && lightboxCaption) { |
| 133 | const cap = slides[activeIndex]?.dataset.caption || ''; |
| 134 | lightboxCaption.textContent = cap; |
| 135 | lightboxCaption.style.display = cap ? 'block' : 'none'; |
| 136 | } |
| 137 | updateCounter(); |
| 138 | syncLightboxThumbs(); |
| 139 | if (zoomEnabled) { |
| 140 | enableZoom(slides[activeIndex]); |
| 141 | } |
| 142 | }; |
| 143 | |
| 144 | thumbs.forEach((thumb, index) => { |
| 145 | thumb.addEventListener('click', () => setActive(index)); |
| 146 | }); |
| 147 | |
| 148 | const prev = wrapper.querySelector('.ka-woo-gallery__arrow--prev'); |
| 149 | const next = wrapper.querySelector('.ka-woo-gallery__arrow--next'); |
| 150 | const dots = wrapper.querySelectorAll('.ka-woo-gallery__dot'); |
| 151 | |
| 152 | const goTo = (delta) => { |
| 153 | const total = slides.length; |
| 154 | let target = activeIndex + delta; |
| 155 | if (loop) { |
| 156 | target = (target + total) % total; |
| 157 | } else { |
| 158 | target = Math.min(Math.max(target, 0), total - 1); |
| 159 | } |
| 160 | setActive(target); |
| 161 | }; |
| 162 | |
| 163 | if (prev) prev.addEventListener('click', () => goTo(-1)); |
| 164 | if (next) next.addEventListener('click', () => goTo(1)); |
| 165 | dots.forEach((dot, idx) => dot.addEventListener('click', () => setActive(idx))); |
| 166 | |
| 167 | // Lightbox |
| 168 | const openLightbox = (index) => { |
| 169 | if (!lightboxEnabled || !lightboxImg) return; |
| 170 | const slide = slides[index]; |
| 171 | if (!slide) return; |
| 172 | const src = slide.dataset.full || ''; |
| 173 | if (!src) return; |
| 174 | activeIndex = index; |
| 175 | lightboxImg.src = src; |
| 176 | if (captions && lightboxCaption) { |
| 177 | const cap = slide.dataset.caption || ''; |
| 178 | lightboxCaption.textContent = cap; |
| 179 | lightboxCaption.style.display = cap ? 'block' : 'none'; |
| 180 | } |
| 181 | updateCounter(); |
| 182 | syncLightboxThumbs(); |
| 183 | lightbox.classList.add('is-open'); |
| 184 | lockScroll(); |
| 185 | |
| 186 | // Set global active controller |
| 187 | window.kaWooGalleryActive = { |
| 188 | isOpen: () => lightbox.classList.contains("is-open"), |
| 189 | close: closeLightbox, |
| 190 | prev: () => { |
| 191 | goTo(-1); |
| 192 | openLightbox(activeIndex); |
| 193 | }, |
| 194 | next: () => { |
| 195 | goTo(1); |
| 196 | openLightbox(activeIndex); |
| 197 | }, |
| 198 | }; |
| 199 | }; |
| 200 | |
| 201 | const closeLightbox = () => { |
| 202 | lightbox.classList.remove('is-open'); |
| 203 | unlockScroll(); |
| 204 | if (window.kaWooGalleryActive && window.kaWooGalleryActive.close === closeLightbox) { |
| 205 | window.kaWooGalleryActive = null; |
| 206 | } |
| 207 | }; |
| 208 | |
| 209 | if (lightboxEnabled) { |
| 210 | lightbox.addEventListener('click', (event) => { |
| 211 | const isBody = event.target.classList.contains('ka-woo-gallery__lightbox'); |
| 212 | const isClose = event.target === lbClose; |
| 213 | if (isBody || isClose) { |
| 214 | closeLightbox(); |
| 215 | } |
| 216 | }); |
| 217 | lightbox.querySelector('.ka-woo-gallery__lightbox-body')?.addEventListener('click', (e) => e.stopPropagation()); |
| 218 | |
| 219 | if (lightboxThumbsEnabled && lightboxThumbs) { |
| 220 | slides.forEach((slide, idx) => { |
| 221 | const imgEl = slide.querySelector('img'); |
| 222 | const src = slide.dataset.full || imgEl?.src || ''; |
| 223 | const btn = document.createElement('button'); |
| 224 | btn.type = 'button'; |
| 225 | btn.className = 'ka-woo-gallery__lightbox-thumb'; |
| 226 | btn.innerHTML = `<span style="background-image:url('${src}')"></span>`; |
| 227 | btn.addEventListener('click', (e) => { |
| 228 | e.stopPropagation(); |
| 229 | setActive(idx); |
| 230 | openLightbox(idx); |
| 231 | }); |
| 232 | lightboxThumbButtons.push(btn); |
| 233 | lightboxThumbs.appendChild(btn); |
| 234 | }); |
| 235 | } |
| 236 | |
| 237 | if (!lightboxCounterEnabled && lightboxCounter) { |
| 238 | lightboxCounter.style.display = 'none'; |
| 239 | } |
| 240 | |
| 241 | if (!lightboxThumbsEnabled && lightboxThumbs) { |
| 242 | lightboxThumbs.style.display = 'none'; |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | slides.forEach((slide, idx) => { |
| 247 | slide.addEventListener('click', () => { |
| 248 | if (!lightboxEnabled) return; |
| 249 | openLightbox(idx); |
| 250 | }); |
| 251 | if (zoomEnabled) { |
| 252 | enableZoom(slide); |
| 253 | } |
| 254 | }); |
| 255 | |
| 256 | if (lbPrev) lbPrev.addEventListener('click', (e) => { e.stopPropagation(); goTo(-1); openLightbox(activeIndex); }); |
| 257 | if (lbNext) lbNext.addEventListener('click', (e) => { e.stopPropagation(); goTo(1); openLightbox(activeIndex); }); |
| 258 | |
| 259 | // Global key handler is bound once for all instances below. |
| 260 | |
| 261 | // Autoplay |
| 262 | const startAutoplay = () => { |
| 263 | if (!autoplay || slides.length <= 1) return; |
| 264 | stopAutoplay(); |
| 265 | timer = setInterval(() => { |
| 266 | const nextIndex = loop ? (activeIndex + 1) % slides.length : Math.min(activeIndex + 1, slides.length - 1); |
| 267 | setActive(nextIndex); |
| 268 | }, speed > 500 ? speed : 4000); |
| 269 | }; |
| 270 | const stopAutoplay = () => { |
| 271 | if (timer) { |
| 272 | clearInterval(timer); |
| 273 | timer = null; |
| 274 | } |
| 275 | }; |
| 276 | wrapper.addEventListener('mouseenter', stopAutoplay); |
| 277 | wrapper.addEventListener('mouseleave', startAutoplay); |
| 278 | |
| 279 | setActive(0); |
| 280 | startAutoplay(); |
| 281 | }; |
| 282 | |
| 283 | const bindGlobalKeyupOnce = () => { |
| 284 | if (window[KEYUP_FLAG]) { |
| 285 | return; |
| 286 | } |
| 287 | window[KEYUP_FLAG] = true; |
| 288 | |
| 289 | document.addEventListener("keyup", (e) => { |
| 290 | const active = window.kaWooGalleryActive; |
| 291 | if (!active || typeof active.isOpen !== "function" || !active.isOpen()) { |
| 292 | return; |
| 293 | } |
| 294 | if (e.key === "Escape") { |
| 295 | active.close(); |
| 296 | } |
| 297 | if (e.key === "ArrowLeft") { |
| 298 | active.prev(); |
| 299 | } |
| 300 | if (e.key === "ArrowRight") { |
| 301 | active.next(); |
| 302 | } |
| 303 | }); |
| 304 | }; |
| 305 | |
| 306 | const initAll = (root) => { |
| 307 | const ctx = root && root.querySelectorAll ? root : document; |
| 308 | bindGlobalKeyupOnce(); |
| 309 | ctx.querySelectorAll(".ka-woo-gallery").forEach(initGallery); |
| 310 | }; |
| 311 | |
| 312 | document.addEventListener("DOMContentLoaded", () => initAll(document)); |
| 313 | |
| 314 | $(window).on("elementor/frontend/init", function () { |
| 315 | elementorFrontend.hooks.addAction( |
| 316 | "frontend/element_ready/woo_product_images_gallery.default", |
| 317 | function ($scope) { |
| 318 | initAll($scope && $scope[0] ? $scope[0] : document); |
| 319 | } |
| 320 | ); |
| 321 | }); |
| 322 | })(jQuery); |
| 323 | |
| 324 | |
| 325 | |
| 326 | |
| 327 | |
| 328 | |
| 329 |