Textarea with Mentions

by sstur

HTML

<div class="x">
    <pre></pre>
    <textarea></textarea>
</div>

CSS

body {
    margin: 0;
    padding: 5px;
}
.x {
    max-width: 200px;
    position: relative;
    font-family: "Helvetica", "Arial", "FreeSans", "Verdana", "Tahoma", "Lucida Sans", "Lucida Sans Unicode", "Luxi Sans", sans-serif;
    font-size: 13px;
    line-height: 18px;
    border: 1px solid #ddd;
}

.x pre, .x textarea {
    padding: 0;
    margin: 0;
    border: 5px solid transparent;
    font-family: inherit;
    font-size: inherit;
    line-height: inherit;
    min-height: 36px;
    white-space: pre-wrap;
    word-wrap: break-word;
}

.x pre {
    color: #900;
}

.x pre .mention {
    color: #009;
}

.x textarea {
    color: #000;
    -webkit-text-fill-color: transparent;
    background: transparent none;
    display: block;
    position: absolute;
    top: 0;
    left: 0;
    resize: none;
    box-sizing: border-box;
    width: 100%;
    height: 100%;
}

.x textarea:focus {
    outline: 0;
}

JavaScript

var x = document.querySelector('.x');
var pre = x.querySelector('pre');
var textArea = x.querySelector('textarea');

textArea.addEventListener('input', function(event) {
    var text = event.target.value;
    console.log('value', text);
    while (pre.firstChild) pre.removeChild(pre.firstChild);
    var lastIndex = 0;
    var frag = document.createDocumentFragment();
    text.replace(/@\w+/g, function(match, index) {
        var previous = text.slice(lastIndex, index);
        if (previous) {
            frag.appendChild(document.createTextNode(previous));
        }
        var span = document.createElement('span');
        span.classList.add('mention');
        span.appendChild(document.createTextNode(match));
        frag.appendChild(span);
        lastIndex = index + match.length;
    });
    frag.appendChild(document.createTextNode(text.slice(lastIndex)));
    pre.appendChild(frag);
});