skip to Main Content

I have a form in which there are input elements of type text that may contain text starting with @, now what I want to achieve is to highlight the partial text inside the input elements that starts with @. I have seen other solutions like using div with contenteditable and other solutions like using editor that does work but they changes the actual content with the HTML and this is not what I don’t want to use.

Now I have noticed chrome implements find and highlight the text as shown in below image and that seems I can modifify its logic with my requirements,

Highlight on search

other example

Is there any hints or solutions ? Please help me with this case.

2

Answers


  1. You could do something like the following:

    function updateSpanText() {
      const input = document.querySelector(".input");
      const highlightedText = document.querySelector(".highlighted-text");
      const highlightedTextContent = input.value.replace(/@(w+)/g, '<span class="highlight">@$1</span>');
      highlightedText.innerHTML = highlightedTextContent;
    }
    
    function onFocus() {
      const highlightedText = document.querySelector(".highlighted-text");
      highlightedText.style.display = 'none';
    }
    
    function onBlur() {
      const highlightedText = document.querySelector(".highlighted-text");
      highlightedText.style.display = 'inline-block';
    }
    * {
      box-sizing: border-box;
    }
    
    .highlight {
      background-color: yellow;
    }
    
    .input-container {
      position: relative;
      display: inline-block;
      width: 200px;
      line-height: 22px;
    }
    
    .highlighted-text {
      position: absolute;
      left: 4px;
      top: 3px;
      background-color: white;
      pointer-events: none;
      font-family: "Helvetica Neue";
      font-size: 14px;
      line-height: 22px;
    }
    
    .input {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      font-family: "Helvetica Neue";
      font-size: 14px;
      line-height: 22px;
    }
    <div class="input-container">
      <input class="input" type="text" onblur="onBlur()" onfocus="onFocus()" onchange="updateSpanText()" />
      <span class="highlighted-text"></span>
    </div>

    The idea is that you keep track of the input-value and once you are done editing, you layer the rendered text on top of the input. When you are actually editing, you just display the input itself.

    With pointer-events: none we can prevent the span from catching events so all events are received by the input.

    Login or Signup to reply.
  2. You cannot style substrings of a true text field. If you want to keep the input for functionality reasons but show the user a styled field, you can have a contenteditable on top with a hidden input on the bottom and bind them so that the raw text gets submitted in the input but your contenteditable is just for show.

    Example case:

    function setCaret(el, childNode, index) {
      var range = document.createRange();
      var sel = window.getSelection();
    
      range.setStart(childNode, index);
      range.collapse(true);
    
      sel.removeAllRanges();
      sel.addRange(range);
    }
    
    function getSelectedElement() {
      var sel = window.getSelection();
      var range = sel.getRangeAt(0);
      var tag = range.startContainer.parentNode;
      
      console.log(tag);
      
      return tag;
    }
    
    document.querySelector('#name-editor').addEventListener('keydown', function(event) {
      var $self = document.querySelector('#name-editor');
    
      if (event.key == '@') {
        event.preventDefault();
        $self.appendChild(document.createElement('span')).className = 'highlight';
        $self.lastChild.appendChild(document.createTextNode('@'));
        setCaret($self, $self.lastChild, 1);
      }
      
      if (event.keyCode == 32 && getSelectedElement().className.indexOf('highlight') > -1) {
        event.preventDefault();
        $self.appendChild(document.createElement('span'));
        $self.lastChild.innerHTML = '&nbsp;';
        setCaret($self, $self.lastChild, 1);
      }
    });
    
    document.querySelector('#name-editor').addEventListener('keyup', function(event) {
      document.querySelector('#name-field').value = this.textContent.trim();
      console.log(document.querySelector('#name-field').value);
    });
    .highlight {
      display: inline-block;
      padding: 5px;
      background-color: rgba(0, 0, 200, .3);
      border-radius: 3px;
    }
    
    [contenteditable] {
      border: 1px solid rgba(0,0,0.1);
      padding: 5px;
      display: inline-block;
      width: 10em;
    }
    <form>
      <input id="name-field" type="text" name="name" style="display: none;" />
      Name: <div id="name-editor" contenteditable="true"></div>
    </form>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search