JSFiddle - React, Tailwind, and code Playground

by Derek Leung

HTML

<div id="input"></div>

CSS

#input {
    border: 2px inset #DDD;
    width: 500px;
    height: 14px;
    padding: 2px 0px;
    margin: 2px;
    font-size: 12px;
    font-family:'Arial';
    white-space: nowrap;
    overflow: hidden;
}

JavaScript

$("#input").prop("contentEditable", true).on("input", function (){
    var loc = getCaretLocation(this);
    $(this).contents().each(function(){
        var current = this;
        if(this.nodeType == 3 || this.nodeName == "FONT"){
            $.each($(this).text().split(""), function(){
                $("<span>").text(this).css({
                    color: "#"+(Math.random()*16777215|0).toString(16)
                }).insertBefore(current);
            });
            $(this).remove();
        }else{
            var crtTxt = $(current).text();
            if(crtTxt.length > 1){
                for(var i = crtTxt.length - 1; i > 0; i--){
                    $("<span>").text(crtTxt[i]).css({
                        color: "#"+(Math.random()*16777215|0).toString(16)
                    }).insertAfter(current);
                }
                $(current).text(crtTxt[0]);
            }
        }
    });
    setCaretLocation(this, loc);
});

function setCaretLocation(ele, pos){
    var range = document.createRange(),
        sel = window.getSelection();
    range.setStart(ele.childNodes[pos - 1], 1);
    range.collapse(true);
    sel.removeAllRanges();
    sel.addRange(range);
}

function getCaretLocation(element) {
    var range = window.getSelection().getRangeAt(0),
        preCaretRange = range.cloneRange();
        preCaretRange.selectNodeContents(element);
        preCaretRange.setEnd(range.startContainer, range.startOffset);
    return preCaretRange.toString().length;
}