如果突出显示某个单词,并且用户单击了连接单词,请突出显示这两个单词

If a word is highlighted and user clicks the connecting word, highlight both

本文关键字:单词 显示 连接 两个 如果 用户 单击      更新时间:2023-09-26

我最近发布了一个问题,询问一种通过以下方式突出显示更聪明的单词的方法:

  • 单击突出显示整个单词(默认行为为双击(。

  • 单击拖动将仅突出显示完整的单词/术语。

美丽的解决方案由阿尔曼发布。

用于测试的 jsFiddle

我提出这个问题的目的是允许用户单击两个或多个连接词并突出显示它们(扩展突出显示的范围(。

来演示。如果光标选择了world,

你好world,洛伦伊普苏姆攻击泰坦。

并且用户点击lorem,它应该像这样选择两个词:

你好world, lorem ipsum攻击泰坦。

如果用户单击Hello

因此,它仅在单词连接时扩展突出显示。例如,如果选择了worlds,,并且用户单击ipsum,则只需选择ipsum

扩大高光覆盖面的方法是什么?

jsFiddle 中的代码是:

jQuery(document).ready(function(e){
    (function(els){
        for(var i=0;i<els.length;i++){
            var el = els[i];
            el.addEventListener('mouseup',function(evt){
                if (document.createRange) { // Works on all browsers, including IE 9+
                    var selected = window.getSelection();
                    /* if(selected.toString().length){ */
                    var d = document,
                        nA = selected.anchorNode,
                        oA = selected.anchorOffset,
                        nF = selected.focusNode,
                        oF = selected.focusOffset,
                        range = d.createRange();
                    range.setStart(nA,oA);
                    range.setEnd(nF,oF);
                    // Check if direction of selection is right to left
                    if(range.startContainer !== nA || (nA === nF && oF < oA)){
                        range.setStart(nF,oF);
                        range.setEnd(nA,oA);
                    }
                    // Extend range to the next space or end of node
                    while(range.endOffset < range.endContainer.textContent.length && !/'s$/.test(range.toString())){
                        range.setEnd(range.endContainer, range.endOffset + 1);
                    }
                    // Extend range to the previous space or start of node
                    while(range.startOffset > 0 && !/^'s/.test(range.toString())){
                        range.setStart(range.startContainer, range.startOffset - 1);
                    }
                    // Remove spaces
                    if(/'s$/.test(range.toString()) && range.endOffset > 0)
                        range.setEnd(range.endContainer, range.endOffset - 1);
                    if(/^'s/.test(range.toString()))
                        range.setStart(range.startContainer, range.startOffset + 1);
                    // Assign range to selection
                    selected.addRange(range);
                    el.style.MozUserSelect = '-moz-none';
                    /* } */
                } else {
                    // Fallback for Internet Explorer 8 and earlier
                    // (if you think it still is worth the effort of course)
                }
            });
            /* This part is necessary to eliminate a FF specific dragging behavior */
            el.addEventListener('mousedown',function(){
                if (window.getSelection) {  // Works on all browsers, including IE 9+
                    var selection = window.getSelection ();
                    selection.collapse (selection.anchorNode, selection.anchorOffset);
                } else {
                    // Fallback for Internet Explorer 8 and earlier
                    // (if you think it still is worth the effort of course)
                }
                el.style.MozUserSelect = 'text';
            });
        }
    })(document.getElementsByClassName('taggable'));
});

.HTML:

<p class="taggable">
   Hello world, lorem ipsum attack on titan.
</p>
<p>
   JS doesn't affect this text. 
</p>

赏金信息

奖励现有的答案,因为它非常有用。无需发布更多解决方案,因为此解决方案已尽最大。

 

升级

好的,我把它放在顶部,因为它是一个重大更新,我相信甚至可以被认为是对先前功能的升级。

请求是使前一个函数反向工作,即当再次单击突出显示的单词时,它将从总选择中删除。

挑战在于,当单击段落内<p></p>标签边缘或<b></b>标签边缘突出显示的单词时,必须将范围的startContainerendContainer带入或移出它们所在的当前元素,并且还必须重置startOffsetendOffset我不确定这是否清楚地表达了这个问题,但简而言之,由于Range对象的工作方式,最接近 HTML 标签的单词被证明是一个相当大的挑战。

解决方案是引入一些新的正则表达式测试、几个if检查和一个用于查找下一个/上一个兄弟姐妹的本地函数。在这个过程中,我还修复了一些以前没有引起我注意的事情。新功能在下面,更新的小提琴在这里。

 

(function(el){
  // variable declaration for previous range info
  // and function for finding the sibling
    var prevRangeInfo = {},
    findSibling = function(thisNode, direction){
      // get the child node list of the parent node
      var childNodeList = thisNode.parentNode.childNodes,
        children = [];
        // convert the child node list to an array
        for(var i=0, l=childNodeList.length; i<l; i++) children.push(childNodeList[i]);
        return children[children.indexOf(thisNode) + direction];
    };
    el.addEventListener('mouseup',function(evt){
        if (document.createRange) { // Works on all browsers, including IE 9+
            var selected = window.getSelection();
      // Removing the following line from comments will make the function drag-only
            /* if(selected.toString().length){ */
                var d = document,
                    nA = selected.anchorNode,
                    oA = selected.anchorOffset,
                    nF = selected.focusNode,
                    oF = selected.focusOffset,
                    range = d.createRange(),
          rangeLength = 0;
                range.setStart(nA,oA);
                range.setEnd(nF,oF);
                // Check if direction of selection is right to left
                if(range.startContainer !== nA || (nA === nF && oF < oA)){
                    range.setStart(nF,oF);
                    range.setEnd(nA,oA);
                }
                // Extend range to the next space or end of node
                while(range.endOffset < range.endContainer.textContent.length && !/'s$/.test(range.toString())){
                    range.setEnd(range.endContainer, range.endOffset + 1);
                }
                // Extend range to the previous space or start of node
                while(range.startOffset > 0 && !/^'s/.test(range.toString())){
                    range.setStart(range.startContainer, range.startOffset - 1);
                }
                // Remove spaces
                if(/'s$/.test(range.toString()) && range.endOffset > 0)
                    range.setEnd(range.endContainer, range.endOffset - 1);
                if(/^'s/.test(range.toString()))
                    range.setStart(range.startContainer, range.startOffset + 1);
        // Store the length of the range
        rangeLength = range.toString().length;
        // Check if another range was previously selected
        if(prevRangeInfo.startContainer && nA === nF && oA === oF){
            var rangeTryContain = d.createRange(),
            rangeTryLeft = d.createRange(),
            rangeTryRight = d.createRange(),
            nAp = prevRangeInfo.startContainer;
            oAp = prevRangeInfo.startOffset;
            nFp = prevRangeInfo.endContainer;
            oFp = prevRangeInfo.endOffset;
          rangeTryContain.setStart(nAp, oAp);
          rangeTryContain.setEnd(nFp, oFp);
          rangeTryLeft.setStart(nFp, oFp-1);
          rangeTryLeft.setEnd(range.endContainer, range.endOffset);
          rangeTryRight.setStart(range.startContainer, range.startOffset);
          rangeTryRight.setEnd(nAp, oAp+1);
          // Store range boundary comparisons
          // & inner nodes close to the range boundary --> stores null if none
          var compareStartPoints = range.compareBoundaryPoints(0, rangeTryContain) === 0,
            compareEndPoints = range.compareBoundaryPoints(2, rangeTryContain) === 0,
            leftInnerNode = range.endContainer.previousSibling,
            rightInnerNode = range.startContainer.nextSibling;
          // Do nothing if clicked on the right end of a word
          if(range.toString().length < 1){
            range.setStart(nAp,oAp);
            range.setEnd(nFp,oFp);
          }
          // Collapse the range if clicked on last highlighted word
          else if(compareStartPoints && compareEndPoints)
            range.collapse();
          // Remove a highlighted word from left side if clicked on
          // This part is quite tricky!
          else if(compareStartPoints){
            range.setEnd(nFp,oFp);
            if(range.startOffset + rangeLength + 1 >= range.startContainer.length){
              if(rightInnerNode)
                // there is a right inner node, set its start point as range start
                range.setStart(rightInnerNode.firstChild, 0);
              else {
                // there is no right inner node
                // there must be a text node on the right side of the clicked word
                // set start of the next text node as start point of the range
                var rightTextNode = findSibling(range.startContainer.parentNode, 1),
                    rightTextContent = rightTextNode.textContent,
                    level=1;
                // if beginning of paragraph, find the first child of the paragraph
                if(/^(?:'r'n|['r'n])|'s{2,}$/.test(rightTextContent)){
                    rightTextNode = findSibling(rightTextNode, 1).firstChild;
                  level--;
                }
                range.setStart(rightTextNode, level);
              }
            }
            else
              range.setStart(range.startContainer, range.startOffset + rangeLength + 1);
          }
          // Remove a hightlighted word from right side if clicked on
          // This part is also tricky!
          else if (compareEndPoints){
            range.setStart(nAp,oAp);
            if(range.endOffset - rangeLength - 1 <= 0){
              if(leftInnerNode)
                // there is a right inner node, set its start point as range start
                range.setEnd(leftInnerNode.lastChild, leftInnerNode.lastChild.textContent.length);
              else {
                // there is no left inner node
                // there must be a text node on the left side of the clicked word
                // set start of the previous text node as start point of the range
                var leftTextNode = findSibling(range.endContainer.parentNode, -1),
                    leftTextContent = leftTextNode.textContent,
                    level = 1;
                // if end of paragraph, find the last child of the paragraph
                if(/^(?:'r'n|['r'n])|'s{2,}$/.test(leftTextContent)){
                    leftTextNode = findSibling(leftTextNode, -1).lastChild;
                  level--;
                }
                range.setEnd(leftTextNode, leftTextNode.length - level);
              }
            }
            else
              range.setEnd(range.endContainer, range.endOffset - rangeLength - 1);
          }
          // Add previously selected range if adjacent
          // Upgraded to include previous/next word even in a different paragraph
          else if(/^[^'s]*((?:'r'n|['r'n])|'s{1,})[^'s]*$/.test(rangeTryLeft.toString()))
            range.setStart(nAp,oAp);
          else if(/^[^'s]*((?:'r'n|['r'n])|'s{1,})[^'s]*$/.test(rangeTryRight.toString()))
            range.setEnd(nFp,oFp);
          // Detach the range objects we are done with, clear memory
          rangeTryContain.detach();
          rangeTryRight.detach();
          rangeTryLeft.detach();
        }
        // Save the current range --> not the whole Range object but what is neccessary
        prevRangeInfo = {
            startContainer: range.startContainer,
          startOffset: range.startOffset,
          endContainer: range.endContainer,
          endOffset: range.endOffset
        };
        // Clear the saved range info if clicked on last highlighted word
        if(compareStartPoints && compareEndPoints)
          prevRangeInfo = {};
        // Remove all ranges from selection --> necessary due to potential removals
        selected.removeAllRanges();
                // Assign the current range as selection
                selected.addRange(range);
        // Detach the range object we are done with, clear memory
        range.detach();
        el.style.MozUserSelect = '-moz-none';
      // Removing the following line from comments will make the function drag-only
            /* } */
        } else { 
           // Fallback for Internet Explorer 8 and earlier
           // (if you think it still is worth the effort of course)
        }
    });
  /* This part is necessary to eliminate a FF specific dragging behavior */
  el.addEventListener('mousedown',function(e){
    if (window.getSelection) {  // Works on all browsers, including IE 9+
         var selection = window.getSelection ();
       selection.collapse (selection.anchorNode, selection.anchorOffset);
    } else {
       // Fallback for Internet Explorer 8 and earlier
           // (if you think it still is worth the effort of course)
    }
    el.style.MozUserSelect = 'text';
  });
})(document.getElementById('selectable'));

 


 

升级前

将最后一个range存储在object中,并在每次进行新选择时检查先前选择的range是否与新range相邻,可以完成以下工作:

(function(el){
    var prevRangeInfo = {};
    el.addEventListener('mouseup',function(evt){
        if (document.createRange) { // Works on all browsers, including IE 9+
            var selected = window.getSelection();
            /* if(selected.toString().length){ */
                var d = document,
                    nA = selected.anchorNode,
                    oA = selected.anchorOffset,
                    nF = selected.focusNode,
                    oF = selected.focusOffset,
                    range = d.createRange();
                range.setStart(nA,oA);
                range.setEnd(nF,oF);
                // Check if direction of selection is right to left
                if(range.startContainer !== nA || (nA === nF && oF < oA)){
                    range.setStart(nF,oF);
                    range.setEnd(nA,oA);
                }
                // Extend range to the next space or end of node
                while(range.endOffset < range.endContainer.textContent.length && !/'s$/.test(range.toString())){
                    range.setEnd(range.endContainer, range.endOffset + 1);
                }
                // Extend range to the previous space or start of node
                while(range.startOffset > 0 && !/^'s/.test(range.toString())){
                    range.setStart(range.startContainer, range.startOffset - 1);
                }
                // Remove spaces
                if(/'s$/.test(range.toString()) && range.endOffset > 0)
                    range.setEnd(range.endContainer, range.endOffset - 1);
                if(/^'s/.test(range.toString()))
                    range.setStart(range.startContainer, range.startOffset + 1);
        // Check if another range was previously selected
        if(prevRangeInfo.startContainer){
            var rangeTryLeft = d.createRange(),
            rangeTryRight = d.createRange(),
            nAp = prevRangeInfo.startContainer;
            oAp = prevRangeInfo.startOffset;
            nFp = prevRangeInfo.endContainer;
            oFp = prevRangeInfo.endOffset;
          rangeTryLeft.setStart(nFp,oFp-1);
          rangeTryLeft.setEnd(range.endContainer,range.endOffset);
          rangeTryRight.setStart(range.startContainer,range.startOffset);
          rangeTryRight.setEnd(nAp,oAp+1);
          // Add previously selected range if adjacent
          if(/^[^'s]*'s{1}[^'s]*$/.test(rangeTryLeft.toString())) range.setStart(nAp,oAp);
          else if(/^[^'s]*'s{1}[^'s]*$/.test(rangeTryRight.toString())) range.setEnd(nFp,oFp);
        }
        // Save the current range
        prevRangeInfo = {
            startContainer: range.startContainer,
          startOffset: range.startOffset,
          endContainer: range.endContainer,
          endOffset: range.endOffset
        };
                // Assign range to selection
                selected.addRange(range);
        el.style.MozUserSelect = '-moz-none';
            /* } */
        } else { 
           // Fallback for Internet Explorer 8 and earlier
           // (if you think it still is worth the effort of course)
        }
    });
  /* This part is necessary to eliminate a FF specific dragging behavior */
  el.addEventListener('mousedown',function(e){
    if (window.getSelection) {  // Works on all browsers, including IE 9+
         var selection = window.getSelection ();
       selection.collapse (selection.anchorNode, selection.anchorOffset);
    } else {
       // Fallback for Internet Explorer 8 and earlier
           // (if you think it still is worth the effort of course)
    }
    el.style.MozUserSelect = 'text';
  });
})(document.getElementById('selectable'));

JS在这里小提琴。

更新(在升级前完成(:

如果您希望此功能在单击但不拖动时有效,您所要做的就是更改if(prevRangeInfo.startContainer)条件,如下所示:

if(prevRangeInfo.startContainer && nA === nF && oA === oF){
    // rest of the code is the same...

更新后的JS小提琴在这里。