CMS Nested Multi-image with custom Lightbox

Hello to this wonderful community,

here is my first post after weeks of searching without finding a solution, you are my last chance!

To make it simple, I have a large image gallery, when I click on one of the images, I’d like each image to hide a (custom) slider with other images, like a photo album.
For example, the first image is a rabbit, and when I click on it, I want another rabbit image. Of course, everything has to be added from the CMS. I currently have a script (not very clean) but it works only halfway, there are bugs, in short I’d like another solution, simpler and more functional. (if possible)

here’s a site that does what I’d like to reproduce: https://www.tylermitchell.co

Do you think this is possible?
I plan to have over 700 photos in the gallery, so the collections will be duplicated and then combined.

https://preview.webflow.com/preview/test-36651c-3b340435776503d28ec3ae27849?utm_medium=preview_link&utm_source=designer&utm_content=test-36651c-3b340435776503d28ec3ae27849&preview=bf6f249eb72f3168d04037fb67a73db6&locale=fr&workflow=preview

Thanks a lot! Help!

Hey @Shavinz! This is certainly possible but will have to include some clever CMS Structure along with some custom Code.

I see you have the CMS Combine script already on the page (if all lists are to be combined into the same list, there is no need to add the -x suffix to the lists), one thing we can start doing is adding the callback for the solution so any code we need to run will run after the lists are combined.

<script>
  window.fsAttributes = window.fsAttributes || [];
  window.fsAttributes.push([
    'cmscombine',
    (listInstances) => {
      console.log('cmscombine Successfully loaded!');
      // add code here
    },
  ]);
</script>

If you are using custom code to trigger the lightboxes please share it here and I can try refactoring it and working any bugs out.

thanks for your feedback !

yes of course here are the two custom codes I use in embeds.
The first is on the static page, here with this function:

<div class="nested-coll-wrapper" id="lightbox-{{wf {&quot;path&quot;:&quot;slug&quot;,&quot;type&quot;:&quot;PlainText&quot;\} }}"></div>
<script>
window.addEventListener('DOMContentLoaded', function() {
   jQuery(function() {
       jQuery('#lightbox-{{wf {&quot;path&quot;:&quot;slug&quot;,&quot;type&quot;:&quot;PlainText&quot;\} }}').load("/gallery-nabil/{{wf {&quot;path&quot;:&quot;slug&quot;,&quot;type&quot;:&quot;PlainText&quot;\} }} .nested-coll-wrapper")
   });
});
</script>

the second is in the CMS template page


Then there’s this big script in the static page settings:

<script src="https://cdn.jsdelivr.net/npm/swiper@9/swiper-bundle.min.js"></script>
<script>
    document.addEventListener('DOMContentLoaded', () => {
      const items = document.querySelectorAll('.img-46');
      const totalItems = items.length;
      let itemsProcessed = 0;

      const shuffledItems = Array.from(items).sort(() => Math.random() - 0.5);

      shuffledItems.forEach((item, index) => {
        setTimeout(() => {
          item.style.opacity = 1;
          item.style.transform = 'translateY(0) scale(1) skewY(0deg)';
          itemsProcessed++;
          if (itemsProcessed === totalItems) {
            // Appeler la fonction finale une fois que toutes les itérations sont terminées
            finalFunction();
          }
        }, index * 20);
      });

      function finalFunction() {
        // Code à exécuter une fois que toutes les itérations et les setTimeout sont terminés
        window.Webflow ||= [];
        window.Webflow.push(() => {
          const lightboxComponents = document.querySelectorAll(
            '[lightbox="component"]',
          );

          lightboxComponents.forEach((component, index1) => {
            let imgUrl = [];
            const imagesSource = component
              .querySelectorAll('.custom-pics img');

            imagesSource.forEach(imgElement => {
              imgUrl.push(imgElement.src); // Ajouter la source de l'image au tableau
            });

            //const separator = ","; //moved to top
            //const lightboxImagesSrc = imagesSource.split(separator); //moved to top

            // New code
            const thumbnailsOtherContainer = component.querySelector('[lightbox="thumbnails-other"]');
            const noOfThumbs = 3;
            const thumbsClass = 'img-thumbnails-other'; //class for each thumbnail
            const thumbWidth = '300'; //thumbnail width
            const thumbHeight = '300'; //thumbnail height

            for (let i = 0; i < noOfThumbs; i++) {
              const thumb = document.createElement('img');
              thumb.setAttribute('src', imgUrl[i]);
              thumb.setAttribute('width', thumbWidth);
              thumb.setAttribute('height', thumbHeight);
              thumb.classList.add(thumbsClass);

              const triggerAttr = document.createAttribute('lightbox');
              triggerAttr.value = 'trigger';

              thumb.setAttributeNode(triggerAttr);
              thumbnailsOtherContainer.appendChild(thumb);
            }
            // End of new code

            const swiperModal = component.querySelector('[lightbox="swiper"]');
            swiperModal.classList.add(`swiper-${index1}`);
            const lightboxTriggers = component.querySelectorAll('[lightbox="trigger"]');


            const swiperPrev = component.querySelector('[swiper="prev-button"]');
            const swiperNext = component.querySelector('[swiper="next-button"]');
            const swiperPagination = component.querySelector('[swiper="pagination"]');

            swiperPrev.classList.add(`swiper-button-prev-${index1}`);
            swiperNext.classList.add(`swiper-button-next-${index1}`);
            swiperPagination.classList.add(`swiper-pagination-${index1}`);

            new Swiper(`.swiper-${index1}`, {
              slidesPerView: 1,
              spaceBetween: 30,
              centeredSlides: true,
              navigation: {
                nextEl: `.swiper-button-next-${index1}`,
                prevEl: `.swiper-button-prev-${index1}`,
              },
              pagination: {
                el: `.swiper-pagination-${index1}`,
                type: 'bullets',
                clickable: true,
                renderBullet: function(index, className) {
                  return `
                  <span class="${className}" style="background-image: url('${imgUrl[index]}'); background-repeat: ; background-size: cover !important; background-position: center; height: 3rem; width: 3rem;')"></span>
              `;
                },
              },
            });

            lightboxTriggers.forEach((trigger, index) => {
              let clickCounter = 0;
              trigger.addEventListener('click', () => {
                if (index > 0) {
                  lightboxTriggers[0].click();
                }
                if (index > 0 || clickCounter > 0) return;

                const swiperWrapper = component.querySelector(
                  '[lightbox="swiper-wrapper"]',
                );

                imgUrl.forEach((imgSrc) => {
                  const slide = document.createElement('div');
                  slide.classList.add('swiper-slide');

                  const slideImg = document.createElement('img');
                  slideImg.setAttribute('src', imgSrc);
                  slideImg.setAttribute('width', 'auto');
                  slideImg.setAttribute('height', 'auto');


                  slide.appendChild(slideImg);
                  swiperWrapper.appendChild(slide);
                });

                clickCounter++;
              });
            });
          });

          const swiperNextBtns = document.querySelectorAll('[swiper="next-button"]');
          const swiperPrevBtns = document.querySelectorAll('[swiper="prev-button"]');

          document.onkeydown = (event) => {
            event = event || window.event;

            if (event.code == 'ArrowLeft') {
              swiperPrevBtns.forEach((btn) => {
                btn.click();
              });
            }

            if (event.code == 'ArrowRight') {
              swiperNextBtns.forEach((btn) => {
                btn.click();
              });
            }
          };
        });
      }
    });


</script>

Hi! I’m still stuck with this problem :frowning:

Hey @Shavinz I am taking a look into the issue, I will update tomorrow :slight_smile:

1 Like

thank you so much @Support-Luis ! can’t wait to see how we can fix it all!

1 Like

hello @Support-Luis, do you have any little update ? :slight_smile:

Hey @Shavinz I just realized your site is not published and can’t test the code :man_facepalming:

I was trying it on the inspo site you share haha.

Anyway, here is the code I’d like you to test. Didn’t do too much to it but should be enough to test the theory


<script>
      window.fsAttributes = window.fsAttributes || [];
      window.fsAttributes.push([
        'cmscombine',
        (listInstances) => {
          console.log('cmscombine Successfully loaded!');

          const items = document.querySelectorAll('.img-46');
          const totalItems = items.length;
          let itemsProcessed = 0;

          const shuffledItems = Array.from(items).sort(() => Math.random() - 0.5);

          shuffledItems.forEach((item, index) => {
            setTimeout(() => {
              item.style.opacity = 1;
              item.style.transform = 'translateY(0) scale(1) skewY(0deg)';
              itemsProcessed++;
              if (itemsProcessed === totalItems) {
                finalFunction();
              }
            }, index * 20);
          });

          function finalFunction() {
            window.Webflow ||= [];
            window.Webflow.push(() => {
              const lightboxComponents = document.querySelectorAll('[lightbox="component"]');

              lightboxComponents.forEach((component, index1) => {
                let imgUrl = [];
                const imagesSource = component.querySelectorAll('.custom-pics img');

                imagesSource.forEach((imgElement) => {
                  imgUrl.push(imgElement.src);
                });

                const thumbnailsOtherContainer = component.querySelector(
                  '[lightbox="thumbnails-other"]'
                );
                const noOfThumbs = 3;
                const thumbsClass = 'img-thumbnails-other';
                const thumbWidth = '300';
                const thumbHeight = '300';

                for (let i = 0; i < noOfThumbs; i++) {
                  const thumb = document.createElement('img');
                  thumb.setAttribute('src', imgUrl[i]);
                  thumb.setAttribute('width', thumbWidth);
                  thumb.setAttribute('height', thumbHeight);
                  thumb.classList.add(thumbsClass);

                  const triggerAttr = document.createAttribute('lightbox');
                  triggerAttr.value = 'trigger';
                  thumb.setAttributeNode(triggerAttr);

                  thumbnailsOtherContainer.appendChild(thumb);
                }

                const swiperModal = component.querySelector('[lightbox="swiper"]');
                swiperModal.classList.add(`swiper-${index1}`);
                const lightboxTriggers = component.querySelectorAll('[lightbox="trigger"]');

                const swiperPrev = component.querySelector('[swiper="prev-button"]');
                const swiperNext = component.querySelector('[swiper="next-button"]');
                const swiperPagination = component.querySelector('[swiper="pagination"]');

                swiperPrev.classList.add(`swiper-button-prev-${index1}`);
                swiperNext.classList.add(`swiper-button-next-${index1}`);
                swiperPagination.classList.add(`swiper-pagination-${index1}`);

                new Swiper(`.swiper-${index1}`, {
                  slidesPerView: 1,
                  spaceBetween: 30,
                  centeredSlides: true,
                  navigation: {
                    nextEl: `.swiper-button-next-${index1}`,
                    prevEl: `.swiper-button-prev-${index1}`,
                  },
                  pagination: {
                    el: `.swiper-pagination-${index1}`,
                    type: 'bullets',
                    clickable: true,
                    renderBullet: function (index, className) {
                      return `
                          <span class="${className}" style="background-image: url('${imgUrl[index]}'); background-repeat: ; background-size: cover !important; background-position: center; height: 3rem; width: 3rem;')"></span>
                      `;
                    },
                  },
                });

                lightboxTriggers.forEach((trigger, index) => {
                  let clickCounter = 0;
                  trigger.addEventListener('click', () => {
                    if (index > 0) {
                      lightboxTriggers[0].click();
                    }
                    if (index > 0 || clickCounter > 0) return;

                    const swiperWrapper = component.querySelector('[lightbox="swiper-wrapper"]');

                    imgUrl.forEach((imgSrc) => {
                      const slide = document.createElement('div');
                      slide.classList.add('swiper-slide');

                      const slideImg = document.createElement('img');
                      slideImg.setAttribute('src', imgSrc);
                      slideImg.setAttribute('width', 'auto');
                      slideImg.setAttribute('height', 'auto');

                      slide.appendChild(slideImg);
                      swiperWrapper.appendChild(slide);
                    });

                    clickCounter++;
                  });
                });
              });

              const swiperNextBtns = document.querySelectorAll('[swiper="next-button"]');
              const swiperPrevBtns = document.querySelectorAll('[swiper="prev-button"]');

              document.onkeydown = (event) => {
                event = event || window.event;

                if (event.code == 'ArrowLeft') {
                  swiperPrevBtns.forEach((btn) => {
                    btn.click();
                  });
                }

                if (event.code == 'ArrowRight') {
                  swiperNextBtns.forEach((btn) => {
                    btn.click();
                  });
                }
              };
            });
          }
        },
      ]);
    </script>

Hi @Support-Luis ! I’ve made good progress with my problem, however I have a problem with CMS LOAD render all, I only have 100 items displayed, but there should be 130, do you have any idea where this is coming from please?

thanks you :slight_smile:

here’s the link: Webflow - Arvin Studio - Portfolio

@Shavinz I just replied to the other post :sweat_smile:

It is basically the same answer, however I used the CMS Load callback instead of the CMS Combine one.

Once you have the compete combined lists we can go over the setup so everything works as you need it to.

I’ve done what I wanted to do with this topic, if you want you can close it. :slight_smile: Thanks again for your help! :slight_smile: