JSFiddle - React, Tailwind, and code Playground

HTML

<!-- Version 2: Pure JavaScript, created at 31 oct 2011 -->
<a href="http://stackoverflow.com/questions/6930578/get-cursor-or-text-position-in-pixels-for-input-element/7948715#7948715" target="_blank">Documentation at Stack Overflow</a>. Created by <a href="http://stackoverflow.com/users/938089/rob-w" target="_blank">Rob W</a><br />

<input value="getBoundingClientRect() for text ranges at non-IE browsers" id="test" /><br />
<input value="6" id="start" /> &lt; "selectionStart" (Edit CSS at the upper box<br />
<input value="6" id="end" /> &lt; "selectionEnd" &nbsp; &nbsp; to test the code)<br />
<input type="button" onclick="bounding_blabla()" value="Get info" />

<label for="debug"><input type="checkbox" id="debug" /> Debugging mode</label>
<input type="button" value="Remove ranges" title="Click here to remove all non-removed elements. Normally, these temporary elements are immediately removed, but this debugging screen allows you to check the results." id="remove" onclick="allDivs('remove')" />
<input type="button" value="Move original element +/- 20px" title="Move the original element 20px to up/down" id="move" />
<br />
<span id="out">
  Hit button "Get info" to activate the function.
    <span style="color:blue">Note: When debugging mode is on,
    appended "ranges" are not removed.
    If the script is working correctly, you
    cannot distinguish the range text from
    the copied source, since the copy should
    be an exact copy, including positioning.</span></span>

CSS

#test { position: relative; outline: 2px solid green; width: 80%; /* Previous styles = debugging */
    top: 250px;
    /* Examples: font-size, font-family, top, left, right, bottom, etc.*/
}
/*div { background: rgba(200, 0, 0, 0.4);*/

input[type=button]{padding:2px 4px;}
#out { white-space: pre; display: block; }

JavaScript

// @author Rob W       http://stackoverflow.com/users/938089/rob-w
// @name               getTextBoundingRect
// @param input          Required HTMLElement with `value` attribute
// @param selectionStart Optional number: Start offset. Default 0
// @param selectionEnd   Optional number: End offset. Default selectionStart
// @param debug          Optional boolean. If true, the created test layer
//                         will not be removed.
function getTextBoundingRect(input, selectionStart, selectionEnd, debug) {
    // Basic parameter validation
    if(!input || !('value' in input)) return input;
    if(typeof selectionStart == "string") selectionStart = parseFloat(selectionStart);
    if(typeof selectionStart != "number" || isNaN(selectionStart)) {
        selectionStart = 0;
    }
    if(selectionStart < 0) selectionStart = 0;
    else selectionStart = Math.min(input.value.length, selectionStart);
    if(typeof selectionEnd == "string") selectionEnd = parseFloat(selectionEnd);
    if(typeof selectionEnd != "number" || isNaN(selectionEnd) || selectionEnd < selectionStart) {
        selectionEnd = selectionStart;
    }
    if (selectionEnd < 0) selectionEnd = 0;
    else selectionEnd = Math.min(input.value.length, selectionEnd);
    
    // If available (thus IE), use the createTextRange method
    if (typeof input.createTextRange == "function") {
        var range = input.createTextRange();
        range.collapse(true);
        range.moveStart('character', selectionStart);
        range.moveEnd('character', selectionEnd - selectionStart);
        return range.getBoundingClientRect();
    }
    // createTextRange is not supported, create a fake text range
    var offset = getInputOffset(),
        topPos = offset.top,
        leftPos = offset.left,
        width = getInputCSS('width', true),
        height = getInputCSS('height', true);

        // Styles to simulate a node in an input field
    var cssDefaultStyles = "white-space:pre;padding:0;margin:0;",
    ...