Home > Mobile >  Change word color in input or textarea
Change word color in input or textarea

Time:10-17

For a chat app, I need to change the color of words starting with a special character, @word inside an input tag, i.e:

Hello @John how are you?

the part @John should change color inside the input, i.e:

Hello @John how are you?

It works after it's posted, but that's obviously another code. I want it to highlight words (@mention) by changing its color right in the input or textarea Element.

CodePudding user response:

Color text portion inside an input-alike element or textarea

Two examples ahead:

1. Input-alike field:

One 5y old idea would be to:

  • Make a Textarea with CSS color: transparent text, but with visible caret: caret-color: black
  • Overlay the Textarea over an underlying Pre element
  • On "input" event do some String.prototype.replace() with a regular expression that matches @mentions on the textarea value and replace i.e: @John with <span >@John</span>
  • Write the result as HTML to the underlying PRE element.

const colorMention = (elText, elPre) => {
  elPre.innerHTML = elText.value.replace(/(?<=^| )@\p{L} /gu, "<span class='mention'>$&</span>");
};

const scrollMirror = (elText, elPre) => {
  elPre.scrollTo(elText.scrollLeft, elText.scrollTop);
};

const handleKey = (ev, elText, elPre) => {
  if (ev.key === "Enter") ev.preventDefault();
  scrollMirror(elText, elPre);
};

const handlePaste = (ev, elText, elPre) => {
  ev.preventDefault();
  const value = ev.clipboardData.getData('Text');
  elText.value = value.replace(/(\r\n|\n|\r)/gm, "");
  elPre.innerHTML = elText.value;
};

document.querySelectorAll(".message").forEach(el => {

  const elText = el.querySelector("textarea");
  const elPre = el.querySelector("pre");
  
  elText.addEventListener("keyup", () => scrollMirror(elText, elPre));
  elText.addEventListener("input", () => colorMention(elText, elPre));
  elText.addEventListener("keydown", (ev) => handleKey(ev, elText, elPre));
  elText.addEventListener("paste", (ev) => handlePaste(ev, elText, elPre));

  // Init:
  colorMention(elText, elPre);
  scrollMirror(elText, elPre);
});
* {
  margin: 0;
  box-sizing: border-box;
}

.message {
  font: 16px/1 sans-serif;
  display: inline-block;
  border: 1px solid #888;
  position: relative;
}

.message>* {
  position: relative;
  display: block;
  overflow: hidden;
  font: inherit;
  padding: 0.5rem;
  width: 13rem;
  resize: none;
}


/* The overlay TEXTAREA with transparent text but visible caret */

.message>textarea {
  border: 0;
  white-space: nowrap;
  background: transparent;
  /* Make text invisible */
  color: transparent;
  /* But keep caret visible */
  caret-color: black;
}


/* The underlaying PRE with colors */

.message>pre {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  /* Prevent text selections */
  user-select: none;
}

.mention {
  color: fuchsia;
}
<div >
  <pre></pre>
  <textarea cols="50" rows="1" spellcheck="false">Hi @test, how are you?</textarea>
</div>

2. Textarea field:

Till now the above code just prevents the "Enter" key to create new lines and the <textarea> tries to emulate the standard behavior of <input>. If instead you do need a textarea like i.e: for a chat app messaging area:

const colorMention = (elText, elPre) => {
  elPre.innerHTML = elText.value.replace(/(?<=^| )@\p{L} /gu, "<span class='mention'>$&</span>");
};

const scrollMirror = (elText, elPre) => {
  elPre.scrollTo(elText.scrollLeft, elText.scrollTop);
};

const handlePaste = (ev, elText, elPre) => {
  ev.preventDefault();
  const value = ev.clipboardData.getData('Text');
  elText.value = value.replace(/(\r\n|\n|\r)/gm, "");
  elText.previousElementSibling.innerHTML = elText.value;
};

const handleKey = (ev, elText, elPre) => {
  if (ev.key === "Enter" && !ev.shiftKey) {
    // prevent Enter key behavior (new-line)
    ev.preventDefault();

    // Check message to send...
    const message = elText.value.trim(); // (trim whitespaces)
    if (!message) {
      return; // Nothing to submit. Message is empty
    }
    
    // YOUR SUBMIT LOGIC GOES HERE:
    console.log(message);
    
    // Clear fields:
    elText.value = "";
    elPre.innerHTML = "";
  } else {
    // Any other key: (scroll areas is necessary)
    scrollMirror(elText, elPre);
  }
};

document.querySelectorAll(".message").forEach(el => {

  const elText = el.querySelector("textarea");
  const elPre = el.querySelector("pre");

  elText.addEventListener("scroll", () => scrollMirror(elText, elPre));
  elText.addEventListener("keyup", () => scrollMirror(elText, elPre));
  elText.addEventListener("input", () => colorMention(elText, elPre));
  elText.addEventListener("keydown", (ev) => handleKey(ev, elText, elPre));
  elText.addEventListener("paste", (ev) => handlePaste(ev, elText, elPre));

  // Init:
  colorMention(elText, elPre);
  scrollMirror(elText, elPre);
});
* {
  margin: 0;
  box-sizing: border-box;
}

.message {
  font: 16px/1.3 sans-serif;
  display: block;
  border: 1px solid #888;
  position: relative;
}

.message>* {
  position: relative;
  display: block;
  overflow-y: scroll;
  font: inherit;
  padding: 0.5rem;
  height: 80px;
  resize: none;
  width: 100%;
}


/* The overlay TEXTAREA with transparent text but visible caret */

.message>textarea {
  border: 0;
  background: transparent;
  /* Make text invisible */
  color: transparent;
  /* But keep caret visible */
  caret-color: black;
}


/* The underlaying PRE with colors */

.message>pre {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  /* Prevent text selections */
  user-select: none;
}

.mention {
  color: fuchsia;
}
<div >
  <pre></pre>
  <textarea spellcheck="false">Hi @Anna, how are you?
Hope @JohnDoe called you today!
      </textarea>
</div>

Chat tips:<br> Use <code>@username</code> to mention a user.<br> Use <kbd>Enter</kbd> to send.<br> Use <kbd>Shift</kbd> <kbd>Enter</kbd> to go to a new line.<br>

Regarding the regular expression for matching mentions:

/(?<=^| )@\p{L} /gu

here's a Regex101.com example with description, and a related answer.


  • Related