JSFiddle - React, Tailwind, and code Playground

HTML

<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Nulla doloribus exercitationem porro a doloremque adipisci ratione, cupiditate dolores facere praesentium quod velit illum expedita quaerat magnam, maxime laborum beatae est nihil, non hic ab, deserunt repellat quas. Est molestiae ipsum minus nesciunt tempore voluptate <span class="hl">laboriosam</span> aut cupiditate ex atque odit excepturi quae at facere, sunt culpa fugiat nostrum eveniet! Tempore nemo eligendi nulla, accusamus consectetur iure aliquid enim eaque rem temporibus. Rerum voluptatem, perferendis suscipit eligendi voluptas ea. Temporibus quod quibusdam et excepturi totam iste consectetur omnis tempore labore provident sed praesentium esse atque soluta, deserunt tenetur, ipsum. Molestiae, minus.</p>

CSS

p {
  -webkit-user-select: none;
  text-align: justify;
  font-size: 350%;
  contain: layout;
}

.hl {
  background: black;
  color: white;
  /*-webkit-font-smoothing: none;*/
}

JavaScript

const highlightClass = 'hl';
const p = document.querySelector('p');
const totalDataLength = p.firstChild.data.length;
let startWordOffsets = [];
p.addEventListener('mousedown', ev => {
  const clickPoint = {
    x: ev.clientX,
    y: ev.clientY
  };
  removeAllHighlights();
  startWordOffsets.length = 0;
  const clickWord = findWordByPoint(clickPoint);
  if (clickWord === null) {
    return;
  }
  highlightWord(clickWord);
  startWordOffsets = clickWord;
});

p.addEventListener('mousemove', ev => {
  if (startWordOffsets.length === 0) {
    return;
  }

	const movePoint = {
    x: ev.clientX,
    y: ev.clientY
  };
  const previousOffsets = removeAllHighlights();
  const movedWord = findWordByPoint(movePoint);
  if (movedWord === null) {
    highlightWord(previousOffsets);
    return;
  }
  // console.log('--moved', movedWord, startWordOffsets);
  if (movedWord[0] === startWordOffsets[0]) {
    highlightWord(movedWord);
  }
  else if (movedWord[0] > startWordOffsets[0]) {
    highlightWord([startWordOffsets[0], movedWord[1]]);
  }
  else {
    highlightWord([movedWord[0], startWordOffsets[1]]);
  }
});
p.addEventListener('mouseup', () => {
  startWordOffsets.length = 0;
})

function findWordByPoint(point) {
  const range = document.caretRangeFromPoint(point.x, point.y);
  return range && findWordByRange(range);
}

function isRangeValid(range) {
  return range.collapsed && 
    range.startContainer === range.endContainer;
}

function findWordByRange(range) {
  if (!isRangeValid(range)) {
  	return null;
  }
  const clickedNode = range.startContainer;
  const clickedText = clickedNode.data;
  const clickedChar = clickedText.charAt(range.startOffset);
  // console.log('--clicked: ' + clickedChar);
  
  if (clickedChar === ' ') {
    return null;
  }
  
  let startOffset = 0;
  if (range.startOffset !== startOffset) {
    startOffset = clickedText.lastIndexOf(' ', range.startOffset - 1);
  }
  startOffset = startOffset === -1 ? 0 : startOffset + 1;
  
  let endOffset...