1/** 2 * Spezielle Klasse für den Bilder-Slider in der Objekt-Detailansicht. 3 * Nutzt Swiper anstelle von Glide für bessere Unterstützung variabler Breiten. 4 */ 5export default class ObjectDetailSlider { 6 constructor(selector = '.object-images-slider') { 7 this.selector = selector; 8 this.container = document.querySelector(selector); 9 10 if (this.container) { 11 this.init(); 12 } 13 } 14 15 init() { 16 const thumbContainer = document.querySelector('.object-thumbnails-slider'); 17 const slideCount = this.container.querySelectorAll('.swiper-slide').length; 18 const infoContainer = document.querySelector('.image-info-container'); 19 const captionEl = document.querySelector('.image-swiper-caption'); 20 const photographerEl = document.querySelector('.image-photographer'); 21 22 let thumbnailSwiper = null; 23 if (thumbContainer) { 24 thumbnailSwiper = new Swiper(thumbContainer, { 25 spaceBetween: 10, 26 slidesPerView: 'auto', 27 slideToClickedSlide: true, 28 watchSlidesProgress: true, 29 centeredSlides: true, 30 initialSlide: 0, 31 loop: false, 32 centerInsufficientSlides: true, 33 observer: true, 34 observeParents: true, 35 }); 36 } 37 38 const updateImageText = (swiper) => { 39 const activeSlide = swiper.slides[swiper.activeIndex]; 40 if (!activeSlide) return; 41 42 const caption = activeSlide.getAttribute('data-caption'); 43 const photographer = activeSlide.getAttribute('data-photographer'); 44 const photographerUrl = activeSlide.getAttribute('data-photographer-url'); 45 const wikiurl = activeSlide.getAttribute('data-wikiurl'); 46 const wikitypeName = activeSlide.getAttribute('data-wikitype-name'); 47 const wikitypeUrl = activeSlide.getAttribute('data-wikitype-url'); 48 const currentIndex = swiper.activeIndex + 1; 49 const totalSlides = swiper.slides.length; 50 51 if (captionEl) captionEl.textContent = caption || ''; 52 if (photographerEl) { 53 let html = `Bild ${currentIndex}/${totalSlides}`; 54 if (photographer) { 55 let photographerLabel; 56 if (wikiurl) {
57 // Wikipedia-Foto: unverändert, Link bereits am "Foto:"-Label. 58 photographerLabel = `<a href="${wikiurl}" target="_blank" rel="noopener">Foto:</a> ${photographer}`; 59 } else if (photographerUrl) { 60 // Externe Website öffnet in neuem Tab, interne Ãbersicht im selben Tab. 61 const isExternal = /^https?:\/\//i.test(photographerUrl); 62 const targetAttr = isExternal ? ' target="_blank" rel="noopener"' : ''; 63 photographerLabel = `<a href="${photographerUrl}"${targetAttr}>${photographer}</a>`; 64 } else { 65 photographerLabel = photographer; 66 } 67 html += ` © ${photographerLabel}`; 68 } 69 if (wikitypeName) { 70 html += `, Lizenz: <a href="${wikitypeUrl}" target="_blank" rel="noopener">${wikitypeName}</a>`; 71 } 72 photographerEl.innerHTML = html; 73 } 74 }; 75 76 // Rechter Rand von image-photographer am rechten Bildrand ausrichten 77 const updatePhotographerAlignment = (swiper) => { 78 if (!photographerEl) return; 79 const activeSlide = swiper.slides[swiper.activeIndex]; 80 if (!activeSlide) return; 81 82 const slideRect = activeSlide.getBoundingClientRect(); 83 const cbRect = photographerEl.parentElement.getBoundingClientRect(); 84 const rightPad = Math.max(0, cbRect.right - slideRect.right); 85 photographerEl.style.paddingRight = rightPad + 'px'; 86 photographerEl.style.textAlign = 'right'; 87 }; 88 89 const swiperConfig = { 90 centeredSlides: true, 91 initialSlide: 0, 92 spaceBetween: 20, 93 slidesPerView: 'auto', 94 loop: false, 95 allowTouchMove: slideCount > 1, 96 observer: true, 97 observeParents: true, 98 keyboard: { 99 enabled: true, 100 onlyInViewport: true, 101 }, 102 103 navigation: { 104 nextEl: '.swiper-button-next', 105 prevEl: '.swiper-button-prev', 106 }, 107 thumbs: { 108 swiper: thumbnailSwiper, 109 }, 110 on: { 111 init: function () { 112 updateImageText(this); 113 }, 114 afterInit: function () { 115 updatePhotographerAlignment(this); 116 }, 117 slideChange: function () { 118 if (thumbnailSwiper) { 119 thumbnailSwiper.slideTo(this.activeIndex); 120 } 121 if (captionEl) captionEl.textContent = this.slides[this.activeIndex]?.getAttribute('data-caption') || ''; 122 if (photographerEl) photographerEl.style.opacity = '0'; 123 }, 124 slideChangeTransitionEnd: function () { 125 updateImageText(this); 126 updatePhotographerAlignment(this); 127 if (photographerEl) photographerEl.style.opacity = '1'; 128 }, 129 } 130 }; 131 132 this.swiper = new Swiper(this.container, swiperConfig); 133 134 const forceReset = () => { 135 if (thumbnailSwiper) { 136 thumbnailSwiper.update(); 137 thumbnailSwiper.slideTo(0, 0); 138 } 139 if (this.swiper) { 140 this.swiper.update(); 141 this.swiper.slideTo(0, 0); 142 updateImageText(this.swiper); 143 } 144 }; 145 146 forceReset(); 147 148 setTimeout(forceReset, 100); 149 setTimeout(() => { 150 forceReset(); 151 updatePhotographerAlignment(this.swiper); 152 this.container.classList.remove('not-initialized'); 153 154 if (infoContainer) infoContainer.classList.remove('not-initialized'); 155 const thumbWrapper = document.querySelector('.object-thumbnails-wrapper'); 156 if (thumbWrapper) thumbWrapper.classList.remove('not-initialized'); 157 }, 500); 158 159 window.addEventListener('resize', () => { 160 if (this.swiper) { 161 updateImageText(this.swiper); 162 updatePhotographerAlignment(this.swiper); 163 } 164 }); 165 } 166}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.