Adding lightbox to image in CMS rich text field

Hi, I wondered if it was possible to add a lightbox feature to an image that has been added in a rich text field through the CMS? I have seen workarounds for using a lightbox for an image that is in a specific image field in the CMS but can’t seem to find anything that would work if it has been added in a rich text field.

Thanks

Trying to do the same :frowning:

Hmm that’s definitely a tricky problem to solve. I honestly can’t think of anyway to do this exactly how you want. The only solutions I can think of aren’t super practical, but may help:

​1. Turn the entire rich text element into a click trigger interaction. You can’t target just the image so you’d have to use the entire thing, then use interactions to set up a pop up modal / custom lightbox with the image inside
​2. Create two separate rich text fields. One field for above the image content, the other for after image content. Then add an image field to the collection and set your image as needed. Then you can structure the page like this:

From here you can create a custom lightbox using interactions and set the image as the click trigger to open the lightbox.

Beyond those two methods I can’t think of any other way to do this natively. Best of luck and let us know if you find a better solution! :slight_smile:

1 Like

Thanks for your help Brandon.

1 Like

Here’s code that enables that functionality.

<script>
  (() => {
    function replaceImagesWithLightboxes(element) {
      const figures = element.querySelectorAll(".w-richtext-figure-type-image");

      figures.forEach((figure) => {
        const image = figure.querySelector("img");
        const imageUrl = image.getAttribute("src");
        const imageAlt = image.getAttribute("alt");
        const imageWidth = image.getAttribute("width"); // Added to retrieve the image width

        // Create lightbox HTML
        const lightbox = document.createElement("a");
        lightbox.setAttribute("href", "#");
        lightbox.classList.add("w-inline-block", "w-lightbox");

        const lightboxImage = document.createElement("img");
        lightboxImage.setAttribute("src", imageUrl);
        lightboxImage.setAttribute("loading", "lazy");
        lightboxImage.setAttribute("sizes", "100vw");
        lightboxImage.setAttribute("srcset", generateSrcset(imageUrl, imageWidth));
        lightboxImage.setAttribute("alt", imageAlt);

        const script = document.createElement("script");
        script.setAttribute("type", "application/json");
        script.classList.add("w-json");
        script.textContent = JSON.stringify({
          items: [
            {
              _id: Math.random().toString(),
              origFileName: imageAlt,
              fileName: imageAlt,
              url: imageUrl,
              width: imageWidth ? parseInt(imageWidth) : undefined, // Updated to use the retrieved image width
              type: "image",
            },
          ],
          group: "",
        });

        lightbox.appendChild(lightboxImage);
        lightbox.appendChild(script);

        figure.innerHTML = "";
        figure.appendChild(lightbox);
      });
    }

    function generateSrcset(imageUrl, imageWidth) {
      const sizes = [500, 800, 1080, 1600, 2000];
      const srcset = sizes
      .filter((size) => size <= imageWidth) // Filter sizes smaller than or equal to the original image width
      .map((size) => {
        return `${imageUrl.replace(".png", `-p-${size}.png`)} ${size}w`;
      });

      srcset.push(`${imageUrl} ${imageWidth}w`);

      return srcset.join(", ");
    }


    const containerElement = document.querySelector('.pillar-main__rte');
    replaceImagesWithLightboxes(containerElement);
  })();
</script>

Let’s break down the code and understand its functionality:

  1. The code is wrapped in an immediately invoked function expression (IIFE) to create a local scope and avoid polluting the global namespace.
  2. The replaceImagesWithLightboxes function is defined. It accepts an element parameter, which should be an HTML element containing the images that need to be replaced with lightboxes.
  3. The function starts by selecting all elements with the class “w-richtext-figure-type-image” within the provided element. These elements are typically used to represent images within a Webflow rich text element.
  4. The function iterates over each of the selected figures using the forEach method.
  5. For each figure, the function retrieves the img element within it and extracts the URL (src attribute), alternate text (alt attribute), and width (width attribute) of the image.
  6. The code then proceeds to create the necessary HTML elements for the lightbox functionality. It creates an a element (lightbox) that serves as a container for the image and attaches the necessary classes (w-inline-block and w-lightbox).
  7. Inside the lightbox container, an img element (lightboxImage) is created and populated with attributes such as the image URL, lazy loading, sizes for responsiveness, and a srcset attribute generated by the generateSrcset function.
  8. Additionally, a script element is created to hold JSON data representing the image properties. It includes an array of objects representing the image data, such as ID, original and file names, URL, width, and type.
  9. The lightboxImage and script elements are appended as children to the lightbox container.
  10. After constructing the lightbox HTML, the figure’s contents are cleared (innerHTML = "") to remove the original image.
  11. Finally, the lightbox container is appended as a child to the figure, effectively replacing the original image with the lightbox.
  12. The generateSrcset function is defined to generate the srcset attribute for the lightbox image. It takes the image URL and width as input parameters.
  13. Inside the function, an array of sizes is defined (e.g., [500, 800, 1080, 1600, 2000]). It filters out sizes that are smaller than or equal to the original image width.
  14. For each remaining size, it generates a URL with the size appended (e.g., replacing “.png” with “-p-500.png”) and appends it to the srcset array with the corresponding width (size + "w").
  15. Finally, the srcset array is joined into a comma-separated string and returned.
  16. Outside the function definitions, the code selects the container element (specified by the class “RICH-TEXT-ELEMENT-CLASS”) where the images should be replaced with lightboxes.
  17. The replaceImagesWithLightboxes function is called with the container element as an argument, triggering the replacement of images within that element.

Overall, this code enhances the images within a Webflow rich text element by replacing them with lightboxes, which allow users to view the images in a larger size or in a modal popup. It also provides responsive behavior by generating a srcset attribute with different image sizes based on the

Explanation offered by ChatGPT, but seems right

2 Likes

Thank you for sharing this code. Could you demo this in a webflow site where this is functioning? Thank you. Andras

This looks amazing, thanks. How would you implement this site-wide? I can’t code for sh*t :slight_smile:

I tried the code provided above, just need to change element to document and it will work
So here how to make it for anyone who don’t know how to code @schaffan @dozzi

First, open your page settings and go to the custom code section, put the code in the Before </body> tag, and put it like this:

<script>
  (() => {
    function replaceImagesWithLightboxes(element) {
      const figures = document.querySelectorAll(".w-richtext-figure-type-image");

      figures.forEach((figure) => {
        const image = figure.querySelector("img");
        const imageUrl = image.getAttribute("src");
        const imageAlt = image.getAttribute("alt");
        const imageWidth = image.getAttribute("width"); // Added to retrieve the image width

        // Create lightbox HTML
        const lightbox = document.createElement("a");
        lightbox.setAttribute("href", "#");
        lightbox.classList.add("w-inline-block", "w-lightbox");

        const lightboxImage = document.createElement("img");
        lightboxImage.setAttribute("src", imageUrl);
        lightboxImage.setAttribute("loading", "lazy");
        lightboxImage.setAttribute("sizes", "100vw");
        lightboxImage.setAttribute("srcset", generateSrcset(imageUrl, imageWidth));
        lightboxImage.setAttribute("alt", imageAlt);

        const script = document.createElement("script");
        script.setAttribute("type", "application/json");
        script.classList.add("w-json");
        script.textContent = JSON.stringify({
          items: [
            {
              _id: Math.random().toString(),
              origFileName: imageAlt,
              fileName: imageAlt,
              url: imageUrl,
              width: imageWidth ? parseInt(imageWidth) : undefined, // Updated to use the retrieved image width
              type: "image",
            },
          ],
          group: "",
        });

        lightbox.appendChild(lightboxImage);
        lightbox.appendChild(script);

        figure.innerHTML = "";
        figure.appendChild(lightbox);
      });
    }

    function generateSrcset(imageUrl, imageWidth) {
      const sizes = [500, 800, 1080, 1600, 2000];
      const srcset = sizes
      .filter((size) => size <= imageWidth) // Filter sizes smaller than or equal to the original image width
      .map((size) => {
        return `${imageUrl.replace(".png", `-p-${size}.png`)} ${size}w`;
      });

      srcset.push(`${imageUrl} ${imageWidth}w`);

      return srcset.join(", ");
    }


    const containerElement = document.querySelector('.pillar-main__rte');
    replaceImagesWithLightboxes(containerElement);
  })();
</script>

If still not, you may want to add this to the Inside <head> tag

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>

Thanks for this. It seems close to working, but I get a bunch of JS errors which prevent the click action doing anything.

(I’m using Lam Thai’s code)

Any ideas what might need to change to fix it?

This is the page I have it on: Outline

@Lam_Thai1 sorry to bug you, curious if those JS errors make any sense to you?

omg, it worked! thank you so much! just a note that, you can only see it after publishing the website, you can’t see it in the preview, it works like a champ!

only thing is that, it gets rid of the caption (unable to display the caption for the image), are you able to keep the caption from the original image?

thanks to @Lam_Thai1 I had motified the code with GPT so now it shows the caption as well, and here’s the code:

<script>
  (() => {
    function replaceImagesWithLightboxes(element) {
      const figures = document.querySelectorAll(".w-richtext-figure-type-image");

      figures.forEach((figure) => {
        const image = figure.querySelector("img");
        const caption = figure.querySelector("figcaption");
        const imageUrl = image.getAttribute("src");
        const imageAlt = image.getAttribute("alt");
        const imageWidth = image.getAttribute("width"); 

        // Create lightbox HTML
        const lightbox = document.createElement("a");
        lightbox.setAttribute("href", "#");
        lightbox.classList.add("w-inline-block", "w-lightbox");

        const lightboxImage = document.createElement("img");
        lightboxImage.setAttribute("src", imageUrl);
        lightboxImage.setAttribute("loading", "lazy");
        lightboxImage.setAttribute("sizes", "100vw");
        lightboxImage.setAttribute("srcset", generateSrcset(imageUrl, imageWidth));
        lightboxImage.setAttribute("alt", imageAlt);

        const script = document.createElement("script");
        script.setAttribute("type", "application/json");
        script.classList.add("w-json");
        script.textContent = JSON.stringify({
          items: [
            {
              _id: Math.random().toString(),
              origFileName: imageAlt,
              fileName: imageAlt,
              url: imageUrl,
              width: imageWidth ? parseInt(imageWidth) : undefined,
              type: "image",
              caption: caption ? caption.innerText : undefined,
            },
          ],
          group: "",
        });

        lightbox.appendChild(lightboxImage);
        lightbox.appendChild(script);

        // Clear figure and append new elements
        figure.innerHTML = "";
        figure.appendChild(lightbox);
        if (caption) {
          figure.appendChild(caption.cloneNode(true)); // Add a clone of the caption
        }
      });
    }

    function generateSrcset(imageUrl, imageWidth) {
      const sizes = [500, 800, 1080, 1600, 2000];
      const srcset = sizes
        .filter((size) => size <= imageWidth)
        .map((size) => `${imageUrl.replace(".png", `-p-${size}.png`)} ${size}w`);

      srcset.push(`${imageUrl} ${imageWidth}w`);

      return srcset.join(", ");
    }

    const containerElement = document.querySelector('.pillar-main__rte');
    replaceImagesWithLightboxes(containerElement);
  })();
</script>

Oli, I’m getting the same errors as you. I’ve tried it on an existing site and a fresh build so I don’t think anything else is interfering with it.

Are we supposed to set the width of the figure image somewhere other than choosing a default width when clicking on it in the designer? It seems like it’s looking for a pixel width for the image that isn’t there.

If anyone else is having issues with this and getting Javascript errors, I’ve made a simple alternative solution you can use that doesn’t use the native lightbox functionality.

Here is the read-only link where you can grab it. Webflow - Modal lightbox for figure images in a rich text field

And here is the published site: https://modal-lightbox-for-figure-images-in-a-r.webflow.io/

All you have to do is include an embed anywhere on your page that has the HTML for the modal, some CSS in the page settings inside the head tag, and the script inside the page settings before the closing body tag.

Just look at the home page and it should be self explanatory. This will work on static pages as well as CMS pages as long as you are using the figure element in a rich text field.

Here is how to implement it:

Add an embed anywhere on the page with this (you will only see it in the designer, not the published site):

<!-- Modal HTML markup -->
<div id="modal">
  <span id="close">&times;</span>
  <img id="modal-img" src="" alt="Modal Image">
</div>

Open your page settings and add this CSS inside the tag:

<style>
  /* Style for the modal */
  #modal {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.8);
    z-index: 9999;
    justify-content: center;
    align-items: center;
  }

  #modal img {
    max-width: 90%;
    max-height: 90%;
  }

  /* Style for the close button */
  #close {
    position: absolute;
    top: 20px;
    right: 20px;
    color: #fff;
    cursor: pointer;
  }

  /* Change cursor to pointer when hovering over initial figure image */
  .w-richtext-align-center img {
    cursor: pointer;
  }

  /* Change cursor to pointer for entire body when modal is open */
  body.modal-open {
    cursor: pointer;
  }
</style>

Then add this script before the tag:

<script>
  document.addEventListener("DOMContentLoaded", function() {
    var modal = document.getElementById("modal");
    var modalImg = document.getElementById("modal-img");
    var closeBtn = document.getElementById("close");

    // Function to open modal with clicked image
    function openModal(imgSrc) {
      modalImg.src = imgSrc;
      modal.style.display = "flex";
      document.body.classList.add('modal-open'); // Add modal-open class to body
    }

    // Function to close modal
    function closeModal() {
      modal.style.display = "none";
      document.body.classList.remove('modal-open'); // Remove modal-open class from body
    }

    // Event listener to open modal when image is clicked
    document.querySelectorAll('.w-richtext-align-center img').forEach(function(img) {
      img.addEventListener('click', function() {
        openModal(img.src);
      });
    });

    // Event listener to close modal when clicked outside of image or on the modal image
    modal.addEventListener('click', function(event) {
      if (event.target === modal || event.target === closeBtn || event.target === modalImg) {
        closeModal();
      }
    });

    // Optional: Close modal when pressing Esc key
    document.addEventListener('keydown', function(event) {
      if (event.key === 'Escape') {
        closeModal();
      }
    });
  });
</script>

That’s it! No Webflow lightboxes or external lightbox scripts needed! Just don’t ask me how to make it fade in and out - you’re on your own on that one! :wink: