Creating a span in dynamic CMS text element

So I just had to create a Span in some text fields that are populated by the CMS and the client needed to be able to manage the spans, anyhow, did that with some custom code and a marker the client can use in the CMS (just%%) to indicate where the Span should wrap text. Did a little post on it here in case someone needs to replicate it. Creating Text Spans in Dynamic CMS Text Elements in Webflow Hope it helps. We’ve also added a handy Webflow Cloneable here.

3 Likes

Hi, I really like it. I used it to span text with “no wrapping” class where needed. Have you thought about improving your custom code in terms of ability to choose breakpionts that the script funcion in?
Anyway, thanks for your help.

Hey @siara_acm it shouldn’t be too hard, if I take the example in the cloneable it’ll be something like:



<script>
document.addEventListener('DOMContentLoaded', function() {
  // Set your breakpoint in pixels. For example, 992px for desktop.
  const breakpoint = 992;

  // Only run the text span transformation on screens wider than the breakpoint.
  if (window.innerWidth < breakpoint) {
    return; // Exit if the viewport is smaller than the breakpoint.
  }

  // Select all elements with the class 'product_heading' or 'text-size-medium'
  // (Note: This selects elements with either class. If you meant both classes together, change the selector to '.product_heading.text-size-medium')
  const headings = document.querySelectorAll('.product_heading, .text-size-medium');

  // Iterate over each heading element and replace markers with a span
  headings.forEach(function(heading) {
    const headingContent = heading.innerHTML;
    const wrappedContent = headingContent.replace(/%%(.*?)%%/gi, function(_, match) {
      return `<span class="text-color-green">${match}</span>`;
    });
    heading.innerHTML = wrappedContent;
  });
});
</script>

Or for like a middle breakpoint that would require a min and max it could be:


<script>
document.addEventListener('DOMContentLoaded', function() {
  // Define your tablet breakpoint range (in pixels)
  const minBreakpoint = 768;  // Lower bound (e.g., portrait tablet)
  const maxBreakpoint = 1024; // Upper bound (e.g., landscape tablet)

  // Only run the text span transformation if the screen width is within the tablet range
  if (window.innerWidth < minBreakpoint || window.innerWidth > maxBreakpoint) {
    return; // Exit if not within the defined tablet range
  }

  // Select all elements with either the 'product_heading' or 'text-size-medium' classes.
  // (Update the selector if you need elements that have both classes.)
  const headings = document.querySelectorAll('.product_heading, .text-size-medium');

  // Iterate over each heading element and wrap marked text (%%...%%) with a span
  headings.forEach(function(heading) {
    const headingContent = heading.innerHTML;
    const wrappedContent = headingContent.replace(/%%(.*?)%%/gi, function(_, match) {
      return `<span class="text-color-green">${match}</span>`;
    });
    heading.innerHTML = wrappedContent;
  });
});
</script>

So that should give you a CMS span that only runs on specific breakpoints. But when you think about that it would simply not run the script, and that would leave the markers you added, so you’d need to run the script with some form of marker processing now matter what breakpoint you’re in if you want the marker gone. So to always strip markers you’d end up with something like this:

<script>
document.addEventListener('DOMContentLoaded', function() {
  // Select all elements with the class 'product_heading' or 'text-size-medium'
  // (Update the selector if you need elements that have both classes.)
  const headings = document.querySelectorAll('.product_heading, .text-size-medium');

  // Process each heading element.
  headings.forEach(function(heading) {
    // Store the original content.
    const originalContent = heading.innerHTML;
    let newContent;

    // Check the viewport and decide what to do:
    if (window.innerWidth >= 992) {
      // Desktop: Wrap the text between markers with a span.
      newContent = originalContent.replace(/%%(.*?)%%/gi, function(_, match) {
        return `<span class="text-color-green">${match}</span>`;
      });
    } else if (window.innerWidth >= 768 && window.innerWidth <= 1024) {
      // Tablet: Wrap the text between markers with a span.
      // (Adjust the span class or logic as needed.)
      newContent = originalContent.replace(/%%(.*?)%%/gi, function(_, match) {
        return `<span class="text-color-green">${match}</span>`;
      });
    } else {
      // For other breakpoints: Simply remove the markers.
      newContent = originalContent.replace(/%%(.*?)%%/gi, '$1');
    }

    // Update the element's content.
    heading.innerHTML = newContent;
  });
});
</script>

Of course I haven’t gone and tested any of this so I could just be spwing rubbish here, but that’s what I’d do off the top of my head. Ideally you’d also watch for the window resizing, but I’d need to think through that one carefully, because it could run multiple times so spans in spans, or the marker would be removed the first time and it wouldn’t find it on resize, mmm, yeah there would be some logic to think through for live resizing of the browser window and running the script as it changes breakpoints.

1 Like

Wow. I’m gonna leave some feedback when I have a chance to test it. You’re the man Jakes, I appreciate your effort.