PluginProbe ʕ •ᴥ•ʔ
FrontBlocks for Gutenberg/GeneratePress / trunk
FrontBlocks for Gutenberg/GeneratePress vtrunk
trunk 0.2.0 0.2.1 0.2.2 0.2.3 0.2.4 0.2.5 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.1.0 1.2.0 1.2.1 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 ci-artifacts
frontblocks / assets / gallery / frontblocks-gallery.js
frontblocks / assets / gallery Last commit date
frontblocks-gallery-option.js 8 months ago frontblocks-gallery-option.jsx 1 month ago frontblocks-gallery.css 9 months ago frontblocks-gallery.js 8 months ago masonry.min.js 8 months ago
frontblocks-gallery.js
337 lines
1 // Initialize on DOM ready for faster loading.
2 if (document.readyState === 'loading') {
3 document.addEventListener('DOMContentLoaded', initFrontBlocksGallery);
4 } else {
5 // DOM is already ready.
6 initFrontBlocksGallery();
7 }
8
9 function initFrontBlocksGallery() {
10 // Wait for images to load before initializing layouts.
11 if (document.readyState === 'complete') {
12 initMasonryGalleries();
13 initGridGalleries();
14 initLightbox();
15 } else {
16 window.addEventListener('load', function() {
17 initMasonryGalleries();
18 initGridGalleries();
19 initLightbox();
20 });
21 }
22 }
23
24 function initMasonryGalleries() {
25 const masonryGalleries = document.querySelectorAll('.frontblocks-gallery-masonry');
26
27 masonryGalleries.forEach(function(gallery) {
28 const columns = parseInt(gallery.getAttribute('data-columns')) || 3;
29 const gutter = parseInt(gallery.getAttribute('data-gutter')) || 20;
30 setupMasonryLayout(gallery, columns, gutter);
31 });
32 }
33
34 function initGridGalleries() {
35 const gridGalleries = document.querySelectorAll('.frontblocks-gallery-grid');
36
37 gridGalleries.forEach(function(gallery) {
38 const columns = parseInt(gallery.getAttribute('data-columns')) || 3;
39 const gutter = parseInt(gallery.getAttribute('data-gutter')) || 20;
40 // Set up grid layout
41 setupGridLayout(gallery, columns, gutter);
42 });
43 }
44
45 function setupGridLayout(gallery, columns, gutter) {
46 gallery.style.setProperty('--frontblocks-gutter', gutter + 'px');
47 }
48
49 function setupMasonryLayout(gallery, columns, gutter) {
50 // Set container styles for masonry
51 gallery.style.position = 'relative';
52 gallery.style.display = 'block';
53 gallery.style.width = '100%';
54 gallery.style.setProperty('--frontblocks-gutter', gutter + 'px');
55
56 // Get all figure elements
57 const figures = gallery.querySelectorAll('figure.wp-block-image');
58 if (figures.length === 0) return;
59
60 // Calculate optimal column count based on available width and breakpoints
61 const containerWidth = gallery.offsetWidth;
62 const minColumnWidth = 200;
63 const totalGutterWidth = gutter * (columns - 1);
64 const availableWidthForColumns = containerWidth - totalGutterWidth;
65 const actualColumnWidth = availableWidthForColumns / columns;
66
67 let optimalColumns = getResponsiveColumns(columns, containerWidth);
68
69 // If columns are too narrow, reduce the number of columns
70 if (actualColumnWidth < minColumnWidth) {
71 optimalColumns = Math.max(1, Math.floor((containerWidth + gutter) / (minColumnWidth + gutter)));
72 }
73
74 // Only set margin-bottom for gutter spacing
75 figures.forEach(function(figure) {
76 figure.style.margin = '0';
77 figure.style.marginBottom = gutter + 'px';
78 // Do NOT set width, float, or clear - let Masonry handle this
79 });
80
81 // Initialize Masonry with calculated column width
82 const masonry = new Masonry(gallery, {
83 itemSelector: 'figure.wp-block-image',
84 columnWidth: '.wp-block-image', // Let Masonry calculate column width
85 gutter: gutter,
86 percentPosition: true,
87 transitionDuration: '0.3s'
88 });
89
90 // Layout Masonry after images load
91 const images = gallery.querySelectorAll('img');
92 let loadedImages = 0;
93
94 if (images.length === 0) {
95 masonry.layout();
96 return;
97 }
98
99 images.forEach(function(img) {
100 if (img.complete) {
101 loadedImages++;
102 if (loadedImages === images.length) {
103 masonry.layout();
104 }
105 } else {
106 img.addEventListener('load', function() {
107 loadedImages++;
108 if (loadedImages === images.length) {
109 masonry.layout();
110 }
111 });
112 }
113 });
114
115 // Layout immediately if all images are already loaded
116 if (loadedImages === images.length) {
117 masonry.layout();
118 }
119
120 // Store masonry instance for resize handling
121 gallery.masonryInstance = masonry;
122
123 // Handle window resize
124 window.addEventListener('resize', function() {
125 if (gallery.masonryInstance) {
126 gallery.masonryInstance.layout();
127 }
128 });
129 }
130
131 function getResponsiveColumns(originalColumns, containerWidth) {
132 // Responsive breakpoints
133 if (containerWidth <= 480) {
134 return Math.min(originalColumns, 2);
135 } else if (containerWidth <= 768) {
136 return Math.min(originalColumns, 3);
137 } else if (containerWidth <= 1024) {
138 return Math.min(originalColumns, 4);
139 } else {
140 return originalColumns;
141 }
142 }
143
144 function initLightbox() {
145 // Create lightbox container if it doesn't exist
146 let lightbox = document.getElementById('frontblocks-lightbox');
147 if (!lightbox) {
148 lightbox = createLightbox();
149 document.body.appendChild(lightbox);
150 }
151
152 // Add click event listeners to gallery images
153 const galleries = document.querySelectorAll('.frontblocks-gallery-masonry, .frontblocks-gallery-grid');
154
155 galleries.forEach(function(gallery) {
156 const enableLightbox = gallery.getAttribute('data-lightbox') === 'true';
157
158 if (enableLightbox) {
159 const images = gallery.querySelectorAll('img');
160 images.forEach(function(img, index) {
161 img.style.cursor = 'pointer';
162 img.addEventListener('click', function(e) {
163 e.preventDefault();
164 openLightbox(images, index);
165 });
166 });
167 }
168 });
169 }
170
171 function createLightbox() {
172 const lightbox = document.createElement('div');
173 lightbox.id = 'frontblocks-lightbox';
174 lightbox.className = 'frontblocks-lightbox';
175
176 lightbox.innerHTML = `
177 <div class="frontblocks-lightbox-content">
178 <button class="frontblocks-lightbox-close">&times;</button>
179 <img src="" alt="">
180 <div class="frontblocks-lightbox-caption"></div>
181 <div class="frontblocks-lightbox-counter"></div>
182 <button class="frontblocks-lightbox-nav frontblocks-lightbox-prev">&lt;</button>
183 <button class="frontblocks-lightbox-nav frontblocks-lightbox-next">&gt;</button>
184 </div>
185 `;
186
187 // Add event listeners
188 const closeBtn = lightbox.querySelector('.frontblocks-lightbox-close');
189 const prevBtn = lightbox.querySelector('.frontblocks-lightbox-prev');
190 const nextBtn = lightbox.querySelector('.frontblocks-lightbox-next');
191
192 closeBtn.addEventListener('click', closeLightbox);
193 prevBtn.addEventListener('click', showPreviousImage);
194 nextBtn.addEventListener('click', showNextImage);
195
196 // Close on background click
197 lightbox.addEventListener('click', function(e) {
198 if (e.target === lightbox) {
199 closeLightbox();
200 }
201 });
202
203 // Close on escape key
204 document.addEventListener('keydown', function(e) {
205 if (e.key === 'Escape' && lightbox.classList.contains('active')) {
206 closeLightbox();
207 } else if (e.key === 'ArrowLeft' && lightbox.classList.contains('active')) {
208 showPreviousImage();
209 } else if (e.key === 'ArrowRight' && lightbox.classList.contains('active')) {
210 showNextImage();
211 }
212 });
213
214 return lightbox;
215 }
216
217 let currentImages = [];
218 let currentImageIndex = 0;
219
220 function openLightbox(images, index) {
221 currentImages = Array.from(images);
222 currentImageIndex = index;
223
224 const lightbox = document.getElementById('frontblocks-lightbox');
225 const lightboxImg = lightbox.querySelector('img');
226
227 // Set image source
228 lightboxImg.src = currentImages[currentImageIndex].src;
229 lightboxImg.alt = currentImages[currentImageIndex].alt || '';
230
231 // Show lightbox
232 lightbox.classList.add('active');
233 document.body.style.overflow = 'hidden';
234
235 // Update caption and counter
236 updateLightboxCaption();
237 updateLightboxCounter();
238 updateLightboxNavigation();
239 }
240
241 function closeLightbox() {
242 const lightbox = document.getElementById('frontblocks-lightbox');
243 lightbox.classList.remove('active');
244 document.body.style.overflow = '';
245
246 currentImages = [];
247 currentImageIndex = 0;
248 }
249
250 function showPreviousImage() {
251 if (currentImages.length === 0) return;
252
253 currentImageIndex = (currentImageIndex - 1 + currentImages.length) % currentImages.length;
254 updateLightboxImage();
255 }
256
257 function showNextImage() {
258 if (currentImages.length === 0) return;
259
260 currentImageIndex = (currentImageIndex + 1) % currentImages.length;
261 updateLightboxImage();
262 }
263
264 function updateLightboxImage() {
265 const lightbox = document.getElementById('frontblocks-lightbox');
266 const lightboxImg = lightbox.querySelector('img');
267
268 lightboxImg.src = currentImages[currentImageIndex].src;
269 lightboxImg.alt = currentImages[currentImageIndex].alt || '';
270
271 updateLightboxCaption();
272 updateLightboxCounter();
273 updateLightboxNavigation();
274 }
275
276 function updateLightboxCaption() {
277 const lightbox = document.getElementById('frontblocks-lightbox');
278 const captionElement = lightbox.querySelector('.frontblocks-lightbox-caption');
279
280 if (currentImages.length === 0) {
281 captionElement.textContent = '';
282 return;
283 }
284
285 const currentImage = currentImages[currentImageIndex];
286 let caption = '';
287
288 // Try to get caption from different sources
289 if (currentImage.alt && currentImage.alt.trim() !== '') {
290 caption = currentImage.alt;
291 } else if (currentImage.title && currentImage.title.trim() !== '') {
292 caption = currentImage.title;
293 } else {
294 // Try to get caption from parent figure element
295 const figure = currentImage.closest('figure');
296 if (figure) {
297 const figcaption = figure.querySelector('figcaption');
298 if (figcaption) {
299 caption = figcaption.textContent.trim();
300 }
301 }
302 }
303
304 captionElement.textContent = caption;
305 captionElement.style.display = caption ? 'block' : 'none';
306 }
307
308 function updateLightboxCounter() {
309 const lightbox = document.getElementById('frontblocks-lightbox');
310 const counterElement = lightbox.querySelector('.frontblocks-lightbox-counter');
311
312 if (currentImages.length <= 1) {
313 counterElement.style.display = 'none';
314 return;
315 }
316
317 counterElement.style.display = 'block';
318 counterElement.textContent = `${currentImageIndex + 1} / ${currentImages.length}`;
319 }
320
321 function updateLightboxNavigation() {
322 const lightbox = document.getElementById('frontblocks-lightbox');
323 const prevBtn = lightbox.querySelector('.frontblocks-lightbox-prev');
324 const nextBtn = lightbox.querySelector('.frontblocks-lightbox-next');
325
326 const showNav = currentImages.length > 1;
327 prevBtn.style.display = showNav ? 'block' : 'none';
328 nextBtn.style.display = showNav ? 'block' : 'none';
329 }
330
331 // Handle dynamic content loading (for AJAX-loaded galleries)
332 function reinitFrontBlocksGallery() {
333 initFrontBlocksGallery();
334 }
335
336 // Expose function globally for external use
337 window.reinitFrontBlocksGallery = reinitFrontBlocksGallery;