JSFiddle - React, Tailwind, and code Playground
HTML
<p class="sentence">
<span class="word" index="0">There </span><span class="word" index="1">is </span><span class="word" index="2">a </span><span class="word" index="3">big, </span><span class="word" index="4">wonderful </span><span class="word" index="5">world.</span>
</p>
<p style="color: grey; margin-top: 10em">
Don't forget to expand the console (below) to see output.
</p>
CSS
.word {
border: 1px solid green;
font-size: 2.5em;
/* margin: 3px; */
}
JavaScript
$('.word').click(function() {
let word_index = parseInt($(this).attr('index'));
let selection = window.getSelection();
let char_index = selection.focusOffset;
// If we hit an overlap with another bit of selectable
// text, we zero out the cursor offset to avoid using
// that word's offset -- the left edge of a textnode
// is the only place where this happens, so we know
// the correct cursor offset is 0.
if ($(this).text() !== selection.focusNode.wholeText) {
char_index = 0;
}
// There's also a small clickable area on the right
// side of the final character in a word that would
// result in a cursor at the end of that word (often
// where the delimiting space is). We simply shift
// the cursor's reference from end-of-that-word to
// start-of-the-next word, which are the same to the
// user, but makes more sense for keeping proper
// word individuation.
if (char_index === $(this).text().length) {
word_index += 1;
char_index = 0;
}
console.log('Clicked to set a new cursor @');
console.log('Word index = ' + word_index.toString() +
' / char index = ' + char_index.toString() );
});