Textarea caret coordinates

Demo for https://github.com/component/textarea-caret-position

by thdoan

HTML

<p>This is a demo of <a href="https://github.com/component/textarea-caret-position">textarea-caret-position</a>, a <em>component</em> to determine the pixel coordinates of the cursor in a <code>textarea</code> or <code>input type="text"</code>.
    
<p>Click anywhere in the text to see a red vertical line &ndash; a 1-pixel div that should be positioned exactly at the location of the caret.</p>

<input type="text" size="15" maxlength="240" placeholder="Enter text here" style="width:50%">
    
<hr/>
    
<textarea rows="25" cols="40">
    I threw a wish in the well,
    Don't ask me, I'll never tell
    PlaceTheCursorUnderTheFirstLettersOfThisLineAndMakeSureItDoesntTrailOnThePreviousLine
    I looked to you as it fell,
    And now you're in my way
    And	tabs	are	handled	just	fine		
    Except in IE9.
    
    I'd trade my soul for a wish,
    Pennies and dimes for a kiss
    I wasn't looking for this,
    But now you're in my way
    
    Your stare was holdin',
    Ripped jeans, skin was showin'
    Hot night, wind was blowin'
    Where do you think you're going, baby?
    
    Hey, I just met you,
    And this is crazy,
    But here's my number,
    So call me, maybe!
</textarea>

<br/>

<label>
    <input type="checkbox" id="mirrorDivDisplay" onchange="toggleMirrorDivDisplay(this)">Show mirror div
</label>

<h3><a href="https://github.com/component/textarea-caret-position">textarea-caret-position</a> Features</h3>
<ul>
    <li>pixel precision
    <li>no dependencies whatsoever
        <li>browser compatibility: Chrome, Safari, Firefox (despite <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=753662">two</a> <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=984275">bugs</a> it has), Opera, IE9+
    <li>supports any font family and size, as well as text-transforms
    <li>the text area can have arbitrary padding or borders
    <li>not confused by horizontal or vertical scrollbars in the textarea
    <li>supports hard returns, tabs (except in IE) and...

CSS

input[type="text"], textarea {
  font-family: 'Times New Roman';  /* a proportional font makes it more difficult to calculate the position */
  font-size: 14px;
  line-height: 16px;
  padding: 24px 32px 16px 8px;    /* different paddings so position computations don't accidentally return a "correct" result */
  text-transform: uppercase;      /* this drastically changes character width on proportional fonts */
  text-indent: 20px;
  border: 16px lightblue dotted;  /* needs to be accounted for when returning the final position */
  border-right-width: 24px;       /* discourage naive border arithmetic */
  background: lightyellow;
}

JavaScript

// The properties that we copy into a mirrored div.
// Note that some browsers, such as Firefox,
// do not concatenate properties, i.e. padding-top, bottom etc. -> padding,
// so we have to do every single property specifically.
var properties = [
  'boxSizing',
  'width',  // on Chrome and IE, exclude the scrollbar, so the mirror div wraps exactly as the textarea does
  'height',
  'overflowX',
  'overflowY',  // copy the scrollbar for IE

  'borderTopWidth',
  'borderRightWidth',
  'borderBottomWidth',
  'borderLeftWidth',

  'paddingTop',
  'paddingRight',
  'paddingBottom',
  'paddingLeft',

  // https://developer.mozilla.org/en-US/docs/Web/CSS/font
  'fontStyle',
  'fontVariant',
  'fontWeight',
  'fontStretch',
  'fontSize',
  'lineHeight',
  'fontFamily',

  'textAlign',
  'textTransform',
  'textIndent',

  'letterSpacing',
  'wordSpacing'
];

var mirrorDivDisplayCheckbox = document.getElementById('mirrorDivDisplay');
var mirrorDiv, computed, style;

getCaretCoordinates = function (element, position) {
  // mirrored div
  mirrorDiv = document.getElementById(element.nodeName + '--mirror-div');
  if (!mirrorDiv) {
    mirrorDiv = document.createElement('div');
    mirrorDiv.id = element.nodeName + '--mirror-div';
    document.body.appendChild(mirrorDiv);
  }

  style = mirrorDiv.style;
  computed = getComputedStyle(element);
  console.log(computed);

  // default textarea styles
  style.whiteSpace = 'pre-wrap';
  if (element.nodeName !== 'INPUT')
    style.wordWrap = 'break-word';  // only for textarea-s

  // position off-screen
  style.position = 'absolute';  // required to return coordinates properly
  style.top = element.offsetTop + parseInt(computed.borderTopWidth) + 'px';
  //style.left = "400px";
  style.visibility = mirrorDivDisplayCheckbox.checked ? 'visible' : 'hidden';  // not 'display: none' because we want rendering

  // transfer the element's properties to the div
  properties.forEach(function(prop) {
    style[prop] = computed[prop];
  });

...