JSFiddle - React, Tailwind, and code Playground
HTML
<p>This is a demo to show tracking the caret position when typing the @ symbol. When you type a red bar will appear at the start of your @ symbol and as you scroll it will reset to 0 0 (or fade out, w.e).</p>
<p>It seems to work ok in Chrome, Safari and Firefox - if you find any bugs please tweet me <strong>@tarnfeld</strong></p>
<div class="editor" contenteditable="true">Quisque eget odio ac lectus vestibulum faucibus eget in metus. In pellentesque faucibus vestibulum. Nulla at nulla justo, eget luctus tortor. Nulla facilisi. Duis aliquet egestas purus in blandit. Curabitur vulputate, ligula lacinia scelerisque tempor, lacus lacus ornare ante, ac egestas est urna sit. Quisque eget odio ac lectus vestibaulum faucibus eget in metus. In pellentesque faucibus vestibulum. Nulla at nulla justo, eget luctus tortor. Nulla facilisi. Duis aliquet egestas purus in blandit. Curabitur vulputate, ligula lacinia scelerisque tempor, lacus lacus ornare ante, ac egestas est urna sit. Quisque eget odio ac lectus vestibulum faucibus eget in metus. In pellentesque faucibus vestibulum. Nulla at nulla justo, eget luctus tortor. Nulla facilisi. Duis aliquet egestas purus in blandit. Curabitur vulputate, ligula lacinia scelerisque tempor, lacus lacus ornaare ante, ac egestas est urna sit. Quisque eget odio ac lectus vestaibulum faucibus eget in metus. In pellentesque faucibus vestibulum. Nulla at nulla justo, eget luctus tortor. Nulla facilisi. Duis aliquet egestas purus in blandit. Curabitur vulputate, ligula lacinia scelerisque tempor, lacus lacus ornare ante, ac egestas est urna sit. Quisque eget odio ac lectus vestibulum faucibus eget in metus. In pellentesque faucibus vestibulum. Nulla at nulla justo, eget luctus tortor. Nulla facilisi. Duis aliquet egestas purus in blandit. Curabitur vualputate, ligula lacinia scelerisque tempor, lacus lacus ornare ante, ac egestas est urna sit. Quisque eget odio ac lectus vestibulum faucibus eget in metus. In pellentesque faucibus vestibulum....
CSS
div.editor {
resize: none;
width: 400px;
height: 400px;
overflow: auto;
padding: 10px;
line-height: 1.5;
border: 1px solid blue;
}
span.cursor {
display: block;
width: 5px;
height: 18px;
background: red;
position: absolute;
top: 0;
left: 0;
}
span.ins_str {
/*position: relative;
display: inline-block;*/
color: red;
}
JavaScript
function getCaretPosition(editableDiv) {
var caretPos = 0, containerEl = null, sel, range;
if (window.getSelection) {
sel = window.getSelection();
if (sel.rangeCount) {
range = sel.getRangeAt(0);
if (range.commonAncestorContainer.parentNode == editableDiv) {
caretPos = range.endOffset;
}
}
} else if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
if (range.parentElement() == editableDiv) {
var tempEl = document.createElement("span");
editableDiv.insertBefore(tempEl, editableDiv.firstChild);
var tempRange = range.duplicate();
tempRange.moveToElementText(tempEl);
tempRange.setEndPoint("EndToEnd", range);
caretPos = tempRange.text.length;
}
}
return caretPos;
}
$('div.editor').bind('keyup click', function() {
var offset = getCaretPosition(this),
$org = $(this),
$el = $org.clone(),
char = $el.text().substr(offset-1, 1);
if (char == '@')
{
var html = '<span class="ins_str">' + char + '</span>';
$el.html($el.text().substr(0, offset - 1) + html + $el.text().substr(offset));
var x = $org.offset().left,
y = $org.offset().top,
width = $org.outerWidth() - 22,
height = $org.outerHeight() - 22;
$el.css({
'position': 'absolute',
'top': y,
'left': x,
'width': width,
'height': height,
'opacity': 0
});
$org.before($el);
var $span = $el.find('span.ins_str');
if ($span.length > 0)
{
var offset = $span.offset(),
char_x = offset.left - $(this).scrollLeft(),
char_y = offset.top - $(this).scrollTop();
$('span.cursor').css({
'left':...