Interactive footnotes in CMS blog posts

Hello everyone,

I was wondering if anyone is able to help me regarding footnote implementation in Webflow’s CMS pages. I’ve seen it mentioned in a few different forum posts on here but haven’t been able to find a satisfactory solution. (Here and here you can see similar questions to my own).

I want to include footnotes in my long-form content, either as tooltips, a side bar or in a div at the bottom of the page, however there is no clear way to create anchor links from the in-line footnote to the relevant footnote itself and back again in Webflow’s CMS natively. I currently use Refokus’ rich-text enhancer to wrap in-line footnote numbers in an anchor link to the relevant footnote, but lots of our essays are long and have upwards of 80 footnotes, and doing this manually is extremely bulky and time-consuming.

Basically I want to know if anybody has created any custom code that would tell Webflow to generate two-way anchor links each time you create an in-line footnote link - e.g. for every time you use superscript text for the footnote number. Or if there is a way to extend something like Bigfoot.js to work inside CMS items, so that you don’t need to create a new collection for each new set of footnotes (we have nearly 150 publications and counting so this is not practical).

I’m amazed that something like this is not part of Webflow’s functionality seeing as its CMS is otherwise very compatible with long-form written content. I’ve requested that Webflow create their own feature to automate this process, so if anyone feels similarly please give the request an upvote here!


Read-only link

Hi Sophie, I’ve written custom code for this but nothing in a library form that you can simply plug in. The problem is that each client’s implementation requirements are different. Some want the footnotes as a scroll-to section. Some want a sidebar. Some want a tooltip or a popup. The indicators can be styled differently too.

But the basic feature is straightforward to build if you are familiar with basic JS.

The best approach I’ve found is to have two separate rich text fields in the CMS- one for the article content, one for the footnotes.

In both, I use the notation [n] to indicate footnotes, with n as any positive integer.

The script parses the blog content [n]'s into footnote indicators that you like, I often keep the brackets and the number, and link and superscript them. These are linked to #footnote-n.

It also parses the footnote content [n]'s similarly, usually presenting them as a bolded numeric indicator prefacing the footnote, which has the id footnote-n.

That’s the whole setup.

This lets you add as many footnotes as you want, and to repeat references like [36]. Using numbers also has the advantage that if JS is disabled in the browser, the user can still read and locate the correct footnote.

Note that it won’t warn you if you’ve created a footnote references that doesn’t exist, or about orphaned footnotes. Also the editing experience isn’t ideal since they’re completely separate.
But this is the simplest implementation.

There are a LOT of variations to this, but I generally recommend keeping it simple if you can.

Hi Michael thanks for taking the time to reply. This sounds like a good solution and similar to the method that I am already using manually. Is there a script that you then include in the body code of the collection template? If so are you able to share?

Hi Sophie,

Yes you’ll need to write a script for your specific setup. In my case each client project is different so these get custom built. Feel free to DM me if you need help building this.

If you follow the outline I gave you above, that’s the most straightforward design I’ve found and you should be able to describe it a tool like chatgpt and get a basic functional script.

Ok cool, thanks Michael!

I finally landed on a solution that works for this, using a script I wrote with a bit of help from chatgpt. This works for my set up specifically but thought I would share it in case it can be of any use to others.

My set-up is as follows:

On my CMS template page I have two divs, one containing the article body text and a second containing the footnotes, containing rich text elements bound to fields in my CMS. I applied the id #article-content to the former and #footnote-content to the latter. I then added the following script into the Before Body custom code section on the page. It detects positive integers wrapped in square brackets, e.g. [1] in the article-content div, and the corresponding indicator in the footnote div, also e.g. [1]. It creates a two-way anchor link between the two.

<script>
// Function to replace footnotes notation with links
function generateFootnotes() {
  var articleContent = document.getElementById('article-content');
  var footnoteContent = document.getElementById('footnote-content');
  var offset = 200; // Offset value in pixels

  // Regular expression to match [n] notation
  var regex = /\[(\d+)\]/g;

  // Find all footnote indicators in the article content and replace with links
  articleContent.innerHTML = articleContent.innerHTML.replace(regex, function(match, number) {
    var footnoteId = 'footnote-' + number;
    var articleId = 'article-footnote-' + number;
    var footnoteLink = '<a id="' + articleId + '" href="#' + footnoteId + '" class="footnote">' + match + '</a>';
    return footnoteLink;
  });

  // Find all footnotes in the footnote content and replace with links back to the article
  footnoteContent.innerHTML = footnoteContent.innerHTML.replace(regex, function(match, number) {
    var articleId = 'article-footnote-' + number;
    var footnoteId = 'footnote-' + number;
    var articleLink = '<a href="#' + articleId + '" class="footnote">' + match + '</a>';
    return '<span id="' + footnoteId + '">' + articleLink + '</span>';
  });

  // Move the footnotes to be visible at the end of the article content
  footnoteContent.style.display = 'block';
  articleContent.appendChild(footnoteContent);

  // Add an event listener to adjust scroll position when a footnote link is clicked
  document.querySelectorAll('.footnote').forEach(function(link) {
    link.addEventListener('click', function(event) {
      event.preventDefault();
      var targetId = this.getAttribute('href').substring(1);
      var targetElement = document.getElementById(targetId);

      // Get the current scroll position
      var currentScrollPosition = window.pageYOffset || document.documentElement.scrollTop;

      // Calculate the target scroll position
      var targetScrollPosition = targetElement.getBoundingClientRect().top + window.pageYOffset - offset;

      // Scroll to the target element with adjusted position
      window.scrollTo({
        top: targetScrollPosition,
        behavior: 'smooth'
      });
    });
  });
}

// Call the function to generate footnotes when the page loads
window.onload = generateFootnotes;
</script>

And because Webflow has built-in scroll CSS that can mess up the positioning of the scroll to and from the anchor links, I overrode this by adding the following code to the Before Head custom code section of the template page:

<script>
var Webflow = Webflow || [];
Webflow.push(function() {
  //DISABLE WEBFLOW SMOOTH SCROLL
  $(function() {
      $(document).off('click.wf-scroll');
  });
});
</script>

This works perfectly, for me now. Footnote indicators in the text, e.g. [1] take you straight down to their corresponding footnote.

Hope this can be helpful for others as it’s saved me a load of time!

@tfka @memetican this script is great - just what I needed!

One issue I did notice is that re-assigning innerHTML for the entire article at once will blow away event listeners that may have been set by earlier scripts. This can cause bad interactions with other plugins; for example, I was having trouble using this plugin for my own blog.

To get around this, I updated the script so that it only changed inner html for the direct parents of text nodes. I put this function (lifted & modified from here) at the top:

function getTextNodesIn(node) {
    var textNodes = [], whitespace = /^\s*$/;

    function getTextNodes(parentNode, node) {
        if (node.nodeType == 3) {
            if (!whitespace.test(node.nodeValue)) {
                textNodes.push([parentNode, node]);
            }
        } else {
            for (var i = 0, len = node.childNodes.length; i < len; ++i) {
                getTextNodes(node, node.childNodes[i]);
            }
        }
    }

    getTextNodes(undefined, node);
    return textNodes;
}

and then used it to insert links for footnotes like so:

  for ([parentNode, textNode] of getTextNodesIn(articleContent)) {
      if (regex.test(textNode.nodeValue)) {
        // Find all footnote indicators in the article content and replace with links
      	parentNode.innerHTML = parentNode.innerHTML.replace(regex, 
          function(match, number) {
        	var footnoteId = 'footnote-' + number;
        	var articleId = 'article-footnote-' + number;
        	var footnoteLink = '<a id="' + articleId + '" href="#' + footnoteId + '" class="footnote">' + match + '</a>';
        	return footnoteLink;
      	  });
      }
   }

This has been working for me so far - it adds in the links while generally leaving event listeners untouched. Hope this is helpful for you & any future readers!