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

时间:2016-02-14 11:02:14

标签: javascript jquery

我最近发布了一个question,想要一种更聪明地突出显示单词的方法:

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

  • 点击并拖动只会突出显示完整的字词/字词。

Arman提出了美丽的解决方案posted

jsFiddle 进行测试。

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

示范。如果光标选择了world,

  

你好world, lorem ipsum攻击泰坦。

用户点击lorem,它应该选择这两个字:

  

你好world, lorem ipsum攻击泰坦。

如果用户点击Hello,行为相同。

因此,如果单词正在连接,它只会扩展突出显示。例如,如果选择worlds,,并且用户点击ipsum,则应选择{{1 }}。

延长亮点范围的方法是什么?

jsFiddle中的代码是:

ipsum

HTML:

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'));

});

赏金信息

奖励现有答案,因为它非常有用。无需发布更多解决方案,因为这个解决方案尽可能完整。

1 个答案:

答案 0 :(得分:3)

UPGRADE

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

请求是让上一个功能反向运行,即再次点击突出显示的单词时,它将从总选择中删除。

挑战是指<p></p>标签边缘或<b></b>标签边缘突出显示的字词单击段落内部,范围的startContainerendContainer必须进入 当前元素的位置和必须重置startOffsetendOffset。我不确定这是否是问题的明确表达,但简而言之,由于Range对象的工作方式,最接近HTML标签的单词被证明是一个相当大的挑战。

解决方案是介绍一些新的正则表达式测试,几个if检查,以及一个用于查找下一个/上一个兄弟的本地函数。在此过程中,我还修了一些以前没有引起我注意的事情。新功能位于the updated fiddle is here下方。

(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小提琴here

更新(在升级前完成):

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

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

更新的JS小提琴是here