0

What's the best way to have a <textarea> where you can write and it automatically colors the word red with the color red?

It should color it even if it's not surrounded by spaces.
So, for example, it should still work on the text I ate tha'red-meat and on the word reddit.

This is a perfect place to find words with red in it.

I have the following code:

body {
  margin: 0;
}
textarea {
  background-color: GhostWhite;
  border: 0;
  height: 100%;
  outline: none;
  resize: none;
  width: 100%;
}
<textarea spellcheck="false"></textarea>
user7393973
  • 2,270
  • 1
  • 20
  • 58

4 Answers4

2

You can't do it with textarea, instead use a contenteditable span(span is suggested to avoid problem with entering <) and wrap the color substring by using the span.

var div = document.getElementById('div');

div.addEventListener('input', function() {
    // get the current position
    var pos = getCaretCharacterOffsetWithin(this);
  // get all word in the content  
  this.innerHTML = this.innerText.replace(/\w+/g, function(m) {
    // create a temporary span element
    var temp = document.createElement('span');
    // set current word as color
    temp.style.color = m;
    // check color is valid by rechecking the proeprty
    if (temp.style.color) {
      // if valid color then replace with temp elements html after setting content
      temp.innerHTML = m;
      return temp.outerHTML;
    }
    // else return the word itself
    return m;
  })
  // set caret position
  setCaretPosition(this, pos);
})



// following code is copied from following question
// https://stackoverflow.com/questions/26139475/restore-cursor-position-after-changing-contenteditable

function getCaretCharacterOffsetWithin(element) {
  var caretOffset = 0;
  var doc = element.ownerDocument || element.document;
  var win = doc.defaultView || doc.parentWindow;
  var sel;
  if (typeof win.getSelection != "undefined") {
    sel = win.getSelection();
    if (sel.rangeCount > 0) {
      var range = win.getSelection().getRangeAt(0);
      var preCaretRange = range.cloneRange();
      preCaretRange.selectNodeContents(element);
      preCaretRange.setEnd(range.endContainer, range.endOffset);
      caretOffset = preCaretRange.toString().length;
    }
  } else if ((sel = doc.selection) && sel.type != "Control") {
    var textRange = sel.createRange();
    var preCaretTextRange = doc.body.createTextRange();
    preCaretTextRange.moveToElementText(element);
    preCaretTextRange.setEndPoint("EndToEnd", textRange);
    caretOffset = preCaretTextRange.text.length;
  }
  return caretOffset;
}

function setCaretPosition(element, offset) {
  var range = document.createRange();
  var sel = window.getSelection();

  //select appropriate node
  var currentNode = null;
  var previousNode = null;

  for (var i = 0; i < element.childNodes.length; i++) {
    //save previous node
    previousNode = currentNode;

    //get current node
    currentNode = element.childNodes[i];
    //if we get span or something else then we should get child node
    while (currentNode.childNodes.length > 0) {
      currentNode = currentNode.childNodes[0];
    }

    //calc offset in current node
    if (previousNode != null) {
      offset -= previousNode.length;
    }
    //check whether current node has enough length
    if (offset <= currentNode.length) {
      break;
    }
  }
  //move caret to specified offset
  if (currentNode != null) {
    range.setStart(currentNode, offset);
    range.collapse(true);
    sel.removeAllRanges();
    sel.addRange(range);
  }
}
<span contenteditable="true" id="div">sss</span>

NOTE : For position retaining code is copied from: Restore cursor position after changing contenteditable


UPDATE : In case you just want to replace red within any word then you can do :

var div = document.getElementById('div');

div.addEventListener('input', function() {
  var pos = getCaretCharacterOffsetWithin(this);
  // get all red subtring and wrap it with span
  this.innerHTML = this.innerText.replace(/red/g, '<span style="color:red">$&</span>')
  setCaretPosition(this, pos);
})



// following code is copied from following question
// https://stackoverflow.com/questions/26139475/restore-cursor-position-after-changing-contenteditable

function getCaretCharacterOffsetWithin(element) {
  var caretOffset = 0;
  var doc = element.ownerDocument || element.document;
  var win = doc.defaultView || doc.parentWindow;
  var sel;
  if (typeof win.getSelection != "undefined") {
    sel = win.getSelection();
    if (sel.rangeCount > 0) {
      var range = win.getSelection().getRangeAt(0);
      var preCaretRange = range.cloneRange();
      preCaretRange.selectNodeContents(element);
      preCaretRange.setEnd(range.endContainer, range.endOffset);
      caretOffset = preCaretRange.toString().length;
    }
  } else if ((sel = doc.selection) && sel.type != "Control") {
    var textRange = sel.createRange();
    var preCaretTextRange = doc.body.createTextRange();
    preCaretTextRange.moveToElementText(element);
    preCaretTextRange.setEndPoint("EndToEnd", textRange);
    caretOffset = preCaretTextRange.text.length;
  }
  return caretOffset;
}

function setCaretPosition(element, offset) {
  var range = document.createRange();
  var sel = window.getSelection();

  //select appropriate node
  var currentNode = null;
  var previousNode = null;

  for (var i = 0; i < element.childNodes.length; i++) {
    //save previous node
    previousNode = currentNode;

    //get current node
    currentNode = element.childNodes[i];
    //if we get span or something else then we should get child node
    while (currentNode.childNodes.length > 0) {
      currentNode = currentNode.childNodes[0];
    }

    //calc offset in current node
    if (previousNode != null) {
      offset -= previousNode.length;
    }
    //check whether current node has enough length
    if (offset <= currentNode.length) {
      break;
    }
  }
  //move caret to specified offset
  if (currentNode != null) {
    range.setStart(currentNode, offset);
    range.collapse(true);
    sel.removeAllRanges();
    sel.addRange(range);
  }
}
<span contenteditable="true" id="div" style="width:100%;display:block">sss</span>

NOTE : This solution is holds some problem related to newline since the content is HTML and where newline doesn't have any meaning.

Community
  • 1
  • 1
Pranav C Balan
  • 113,687
  • 23
  • 165
  • 188
1

So you can use a contenteditable div, match any red that doesn't have a > right before it, and replace that red with : "$1<span class='specialColor'>red</span>&zwnj;" The zero-width non-joiner character at the end is used as a delimiter for the span.

document.getElementById('foo').addEventListener('input', function(e) {
  var re = /([^\>])red/;
  var split = this.innerHTML.replace(re, "$1<span class='specialColor'>red</span>&zwnj;");
  this.innerHTML = split;
  placeCaretAtEnd(this);
})

function placeCaretAtEnd(el) {
  el.focus();
  if (typeof window.getSelection != "undefined" && typeof document.createRange != "undefined") {
    var range = document.createRange();
    range.selectNodeContents(el);
    range.collapse(false);
    var sel = window.getSelection();
    sel.removeAllRanges();
    sel.addRange(range);
  } else if (typeof document.body.createTextRange != "undefined") {
    var textRange = document.body.createTextRange();
    textRange.moveToElementText(el);
    textRange.collapse(false);
    textRange.select();
  }
}
body {
  margin: 0;
}
textarea {
  background-color: GhostWhite;
  border: 0;
  height: 100%;
  outline: none;
  resize: none;
  width: 100%;
}

.specialColor {
  color:red;
}
<div id="foo" contenteditable spellcheck="false"></div>

DISCLAIMER:

The placeCaretAtEnd() method is taken from this SO answer:contenteditable, set caret at the end of the text (cross-browser)

Community
  • 1
  • 1
baao
  • 71,625
  • 17
  • 143
  • 203
0

You cannot add partial styles to text inside an editable input area like input or textarea. It is considered as a single block of text and can only be styled as a whole.

But, the desired effect can be simulated by clever positioning of styled span elements.

There is one such project that provides on the go text highlighting. Check them out: Code Mirror Project

I've used codemirror on one of my personal projects. You should take a look at it too. See the editable pane on the left

  • I currently cannot check that external link because my internet connection doesn't allow as it has security limitations. Could you please make an effort to provide me a working example? I would really appreciate it! – user7393973 Feb 03 '17 at 14:41
  • @user7393973 There is a working example here http://coderpad-clone.herokuapp.com/ad/vish – Vishwas Singh Chouhan Feb 04 '17 at 17:18
  • I know there's multiple working code editors out there that do the trick. What I was asking was the simplest code to do that and nothing more. – user7393973 Feb 05 '17 at 02:21
0

The only thing I'm not happy about is how it handles line breaks and tabs. It's a tiny bit more complex than only replacing red, but this way you can highlight any word in any color and probably even mix several of these on the same element, so the functions used are reusable.

<html>
<head>
    <style>
    #myTextarea {
        border: 1px solid black;
        border-radius: 5px;
        min-height: 50px;
        width: 50%;
    }
    .hl-red {
        color: red;
    }
    </style>
</head>
<body>
<div id="myTextarea" contenteditable>Test string</div>
<script>
var createHighlighter = function createHighlighter( word ) {
    // Let's create a reusable function that can highlight any word.
    // You call this function providing the word you want highlighted.
    // Then this returns a function that will insert some html spans around each instance of the word.
    // You can the call the returned function with any string to get the string saturated with the span tags.
    return function highlight( str ) {
        // We split on the word we want to highlight, so our array contains all the non-highlighted chunks.
        // Since we can call the function multiple times in a row, we will usually want to remove the previous highlights, so we don't double-highlight it.
        // This can be easily done with a simple global replace regex.
        var chunks;
        if (str === '<br>') return '';
        else {
            chunks = str.replace(/\<span\sclass="hl-red"\>/g, '').replace(/\<\/span\>/g, '').split(word);
            // Then we add the non-highlighted text + the <span> + the highlighted word.
            return chunks.reduce(function( html, chunk, index ) {
                html += chunk;
                // Add the highlighted word, except for the last chunk, since that's just the end of the string.
                if ((index + 1) !== chunks.length) html += '<span class="hl-red">' + word + '</span>';
                return html;
            }, '');
        }
    };
};

var wordToHighlight = 'red';

var highlightRed = createHighlighter( wordToHighlight );

var highlightArea = function highlightArea( event ) {
    event.target.innerHTML = highlightRed( event.target.innerHTML );
};

document.querySelector('#myTextarea').addEventListener('keyup', highlightArea);

</script>
</body>
</html>
Shilly
  • 8,511
  • 1
  • 18
  • 24
  • Is it possible to make it not change the caret's position? – user7393973 Feb 03 '17 at 15:05
  • That's less trivial than the highlighting, so I would refer to baao's answer concerning the caret. – Shilly Feb 03 '17 at 15:26
  • baao's answer isn't currently working. If you write `red` and then delete the middle `e`, the `rd` stays with the color red. – user7393973 Feb 03 '17 at 15:29
  • If you combine the best parts from all the answers you got, you'll have it fully working. I prefer my version of the highlighting, since it allows any sequence of characters that isnt `` or `` and it doesn't suffer from backspace issues with words aprtially highlighted. But I can't get my version of the caret position to work, so I'm going to give up. Was a nice distraction. :) – Shilly Feb 03 '17 at 16:10
  • You gave it a good try I must admit that. This got much attention and competition, and I will accept the answer of who gets it fully working, but right now, I think most gave up as it's to complicated. I will see what I can do... – user7393973 Feb 03 '17 at 16:16