如果突出显示一个单词并且用户单击连接单词,则突出显示这两个单词

我最近发布了一个问题,要求通过以下方式来突出显示单词的方式:

  • 单击高亮显示整个单词(默认行为是双击)。

  • 点击拖拽只会使整个单词/词条变亮。

  • 阿尔曼贴出了漂亮的解决方案。

    jsFiddle进行测试。

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

    展示。 如果world,被光标选中:

    你好world, Lorem ipsum攻击泰坦。

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

    你好world, lorem 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(/^(?:rn|[rn])|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(/^(?:rn|[rn])|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]*((?:rn|[rn])|s{1,})[^s]*$/.test(rangeTryLeft.toString()))
                range.setStart(nAp,oAp);
              else if(/^[^s]*((?:rn|[rn])|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)条件:

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

    更新的JS小提琴在这里。

    链接地址: http://www.djcxy.com/p/32187.html

    上一篇: If a word is highlighted and user clicks the connecting word, highlight both

    下一篇: Is there a complementary way to get something like mouse events?