The Range.commonAncestorContainer read-only property returns the deepest — or furthest down the document tree — Node that contains both boundary points of the Range. This means that if Range.startContainer and Range.endContainer both refer to the same node, this node is the common ancestor container. 
  Since a Range need not be continuous, and may also partially select nodes, this is a convenient way to find a Node which encloses a Range. 
  This property is read-only. To change the ancestor container of a Node, consider using the various methods available to set the start and end positions of the Range, such as Range.setStart() and Range.setEnd(). 
 
 In this example, we create an event listener to handle pointerup events on a list. The listener gets the common ancestors of each piece of selected text, and triggers an animation to highlight them. 
 
<ul>
  <li>
    Strings
    <ul>
      <li>Cello</li>
      <li>
        Violin
        <ul>
          <li>First Chair</li>
          <li>Second Chair</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>
    Woodwinds
    <ul>
      <li>Clarinet</li>
      <li>Oboe</li>
    </ul>
  </li>
</ul>
  
 The .highlight class created below uses a set of CSS @keyframes to animate a fading outline. 
 
.highlight {
  animation: highlight linear 1s;
}
@keyframes highlight {
  from {
    outline: 1px solid #f00f;
  }
  to {
    outline: 1px solid #f000;
  }
}
  
 
document.addEventListener("pointerup", (e) => {
  const selection = window.getSelection();
  if (selection.type === "Range") {
    for (let i = 0; i < selection.rangeCount; i++) {
      const range = selection.getRangeAt(i);
      playAnimation(range.commonAncestorContainer);
    }
  }
});
function playAnimation(el) {
  if (el.nodeType === Node.TEXT_NODE) {
    el = el.parentNode;
  }
  el.classList.remove("highlight");
  setTimeout(() => {
    el.classList.add("highlight");
  }, 0);
}