JSFiddle - React, Tailwind, and code Playground

by Scott Kaye

HTML

<textarea>Test</textarea>
<canvas id="c"></canvas>

CSS

textarea {
  width: 300px;
  height: 100px;
}

canvas {
  position: absolute;
  top: 8px;
  left: 8px;
  pointer-events: none;
}

div {
  background: red;
  animation: fade 2s ease 1 forwards;
}

@keyframes fade {
  to { opacity: 0 }
}

JavaScript

// Element prototype extension pattern from
// http://lea.verou.me/2015/04/idea-extending-native-dom-prototypes-without-collisions/
// Thank you!

console.clear();

const CaretTools = function(element) {
  this.element = element;
};

CaretTools.prototype = {
  getTextDimensions: function(str) {
    let clone = document.createElement("div");
    let style = window.getComputedStyle(this.element);
    clone.style.maxWidth = style.width;
    clone.style.display = "inline-block";
    clone.style.position = "absolute";
    clone.style.top = 0;
    clone.style.left = 0;
    clone.style.pointerEvents = "none";
    clone.style.font = style.font;
    clone.style.lineHeight = style.fontSize;
    clone.innerText = str;
    console.log(clone);
    document.body.appendChild(clone);
    let dims = {
      width: clone.clientWidth,
      height: clone.clientHeight
    };
    //document.body.removeChild(clone);
    return dims;
  },
  getPoint: function() {
    let pos = this.element.selectionStart;
    let before = this.element.value.substring(0, pos);
		let totalSize = this.getTextDimensions(before);
    let fontSize = parseFloat(window.getComputedStyle(this.element).fontSize.match(/[0-9.]+/));
    let line = Math.round(totalSize.height / fontSize);
    
    let x = totalSize.width;
    let y = totalSize.height - (fontSize / 2);
    
    //get linesize for x value, not totalsize
    
    console.log(x, y);
    return {x, y};
  }
};

Object.defineProperty(HTMLTextAreaElement.prototype, "caretTools", {
  get: function() {
    Object.defineProperty(this, "caretTools", {
      value: new CaretTools(this)
    });
    
    return this.caretTools;
  },
  configurable: true,
  writeable: false
});

var canvas = document.getElementById("c");
canvas.width = 305;
canvas.height = 105;
var context = canvas.getContext("2d");

var area = document.querySelector("textarea");
area.addEventListener("input", () => {
  var point = area.caretTools.getPoint();
  console.log(point);
 ...