动态突出显示符合特定条件的单词

Dynamically Highlight Words That Fit a Specific Criteria

我已经发帖了,但是太静态了。现在我想进行更改,使代码是动态的。

OBJECTIVE

我希望以“t”开头的单词在用户键入时突出显示。如果单词不以“t”开头,则什么也不做。基本上,用户会有正常的打字体验,但“t”字将被突出显示。

版本详情

  1. 我有一个版本可以在 mousemove 上“运行”,但这对 用户。我不希望他们必须移动鼠标才能获取文本 突出显示。
  2. 我有一个按键“有效”的版本,但问题是 光标始终 returns 到初始位置(这会导致文本反向输入)并且一旦突出显示开始,它就不会停止。

版本 1:事件 = onmousemove

//highlight ANY word that starts with t

function highlighter(ev) {
        var content = ev.innerHTML;
        var tokens = content.split(" ");
        for (var i = 0; i < tokens.length; i++) {
                if (tokens[i][0] == 't') {
                        tokens[i] = "<mark style='background-color:red; color:white;'>" + tokens[i] + "</mark>";
                      } 
              }
        ev.innerHTML = tokens.join(" ");
}
/* NOT REQUIRED AT ALL, JUST TO MAKE INTERACTION MORE PLEASANT */
.container {
  outline: none;
  border: 3px solid black;
  height: 100px;
  width: 400px;
}
<div class="container" onmousemove=highlighter(this) contenteditable>
</div>

版本 2:事件 = onkeypress

//highlight ANY word that starts with t

function highlighter(ev) {
        var content = ev.innerHTML;
        var tokens = content.split(" ");
        for (var i = 0; i < tokens.length; i++) {
                if (tokens[i][0] == 't') {
                        tokens[i] = "<mark style='background-color:red; color:white;'>" + tokens[i] + "</mark>";
                      } 
              }
        ev.innerHTML = tokens.join(" ");
}
/* NOT REQUIRED AT ALL, JUST TO MAKE INTERACTION MORE PLEASANT */
.container {
  outline: none;
  border: 3px solid black;
  height: 100px;
  width: 400px;
}
<div class="container" onkeypress=highlighter(this) contenteditable>
</div>

这里是草稿示例。我使用了来自 this gist 的插入符号 get/set 片段。基本上想法很简单 - 获取插入位置,进行修改,将其设置回去。还将您的 innerHTML 方法交换为 innerText,因为您不需要在您的 t-finder 逻辑中解析 HTML 代码。

function highlighter(ev) {
  // Get current cursor position
  const currpos = getSelectionDirection(ev) !== 'forward' ? getSelectionStart(ev) : getSelectionEnd(ev);
  // Change innerHTML to innerText, you
  // dont need to parse HTML code here
  var content = ev.innerText;
  var tokens = content.split(" ");
  
  for (var i = 0; i < tokens.length; i++) {
    if (tokens[i][0] == 't') {
      tokens[i] = "<mark style='background-color:red; color:white;'>" + tokens[i] + "</mark>";
    } 
  }
  ev.innerHTML = tokens.join(" ");
  // Set cursor on it's proper position
  setSelectionRange(ev, currpos, currpos);
}
/* NOT REQUIRED AT ALL, JUST TO MAKE INTERACTION MORE PLEASANT */
.container {
  outline: none;
  border: 3px solid black;
  height: 100px;
  width: 400px;
}
<div class="container" onkeypress=highlighter(this) contenteditable>
</div>

<script>
// Usage:
// var x = document.querySelector('[contenteditable]');
// var caretPosition = getSelectionDirection(x) !== 'forward' ? getSelectionStart(x) : getSelectionEnd(x);
// setSelectionRange(x, caretPosition + 1, caretPosition + 1);
// var value = getValue(x);

// it will not work with "<img /><img />" and, perhaps, in many other cases.

  function isAfter(container, offset, node) {
    var c = node;
    while (c.parentNode != container) {
      c = c.parentNode;
    }
    var i = offset;
    while (c != null && i > 0) {
      c = c.previousSibling;
      i -= 1;
    }
    return i > 0;
  }
  function compareCaretPositons(node1, offset1, node2, offset2) {
    if (node1 === node2) {
      return offset1 - offset2;
    }
    var c = node1.compareDocumentPosition(node2);
    if ((c & Node.DOCUMENT_POSITION_CONTAINED_BY) !== 0) {
      return isAfter(node1, offset1, node2) ? +1 : -1;
    } else if ((c & Node.DOCUMENT_POSITION_CONTAINS) !== 0) {
      return isAfter(node2, offset2, node1) ? -1 : +1;
    } else if ((c & Node.DOCUMENT_POSITION_FOLLOWING) !== 0) {
      return -1;
    } else if ((c & Node.DOCUMENT_POSITION_PRECEDING) !== 0) {
      return +1;
    }
  }

  function stringifyElementStart(node, isLineStart) {
    if (node.tagName.toLowerCase() === 'br') {
      if (true) {
        return '\n';
      }
    }
    if (node.tagName.toLowerCase() === 'div') { // Is a block-level element?
      if (!isLineStart) { //TODO: Is not at start of a line?
        return '\n';
      }
    }
    return '';
  }
  function* positions(node, isLineStart = true) {
    console.assert(node.nodeType === Node.ELEMENT_NODE);
    var child = node.firstChild;
    var offset = 0;
    yield {node: node, offset: offset, text: stringifyElementStart(node, isLineStart)};
    while (child != null) {
      if (child.nodeType === Node.TEXT_NODE) {
        yield {node: child, offset: 0/0, text: child.data};
        isLineStart = false;
      } else {
        isLineStart = yield* positions(child, isLineStart);
      }
      child = child.nextSibling;
      offset += 1;
      yield {node: node, offset: offset, text: ''};
    }
    return isLineStart;
  }
  function getCaretPosition(contenteditable, textPosition) {
    var textOffset = 0;
    var lastNode = null;
    var lastOffset = 0;
    for (var p of positions(contenteditable)) {
      if (p.text.length > textPosition - textOffset) {
        return {node: p.node, offset: p.node.nodeType === Node.TEXT_NODE ? textPosition - textOffset : p.offset};
      }
      textOffset += p.text.length;
      lastNode = p.node;
      lastOffset = p.node.nodeType === Node.TEXT_NODE ? p.text.length : p.offset;
    }
    return {node: lastNode, offset: lastOffset};
  }
  function getTextOffset(contenteditable, selectionNode, selectionOffset) {
    var textOffset = 0;
    for (var p of positions(contenteditable)) {
      if (selectionNode.nodeType !== Node.TEXT_NODE && selectionNode === p.node && selectionOffset === p.offset) {
        return textOffset;
      }
      if (selectionNode.nodeType === Node.TEXT_NODE && selectionNode === p.node) {
        return textOffset + selectionOffset;
      }
      textOffset += p.text.length;
    }
    return compareCaretPositons(selectionNode, selectionOffset, contenteditable, 0) < 0 ? 0 : textOffset;
  }
  function getValue(contenteditable) {
    var value = '';
    for (var p of positions(contenteditable)) {
      value += p.text;
    }
    return value;
  }
  function setSelectionRange(contenteditable, start, end) {
    var selection = window.getSelection();
    var s = getCaretPosition(contenteditable, start);
    var e = getCaretPosition(contenteditable, end);
    selection.setBaseAndExtent(s.node, s.offset, e.node, e.offset);
  }
  //TODO: Ctrl+A - rangeCount is 2
  function getSelectionDirection(contenteditable) {
    var selection = window.getSelection();
    var c = compareCaretPositons(selection.anchorNode, selection.anchorOffset, selection.focusNode, selection.focusOffset);
    return c < 0 ? 'forward' : 'none';
  }
  function getSelectionStart(contenteditable) {
    var selection = window.getSelection();
    var c = compareCaretPositons(selection.anchorNode, selection.anchorOffset, selection.focusNode, selection.focusOffset);
    return c < 0 ? getTextOffset(contenteditable, selection.anchorNode, selection.anchorOffset) : getTextOffset(contenteditable, selection.focusNode, selection.focusOffset);
  }
  function getSelectionEnd(contenteditable) {
    var selection = window.getSelection();
    var c = compareCaretPositons(selection.anchorNode, selection.anchorOffset, selection.focusNode, selection.focusOffset);
    return c < 0 ? getTextOffset(contenteditable, selection.focusNode, selection.focusOffset) : getTextOffset(contenteditable, selection.anchorNode, selection.anchorOffset);
  }
</script>