如果突出显示了一个单词,并且用户单击了所连接的单词,请同时突出显示两个单词 [英] If a word is highlighted and user clicks the connecting word, highlight both

查看:83
本文介绍了如果突出显示了一个单词,并且用户单击了所连接的单词,请同时突出显示两个单词的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我最近发布了一个问题寻求一种方法来突出显示更聪明的单词:

I recently posted a question asking for a way to highlight words smarter by:

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

  • Single-click highlights the whole word (default behavior is double-click).

单击并拖动只会突出显示完整的单词/词条.

Click-drag will hightlight full words/terms only.

精美的解决方案是Arman 发布.

Beautiful solution was posted by Arman.

jsFiddle 进行测试.

jsFiddle for testing.

我对这个问题的目的是允许用户单击两个或多个连接词并将其突出显示(扩展突出显示的范围).

My aim with this question is to allow the user to single-click two or more connecting words and highlight them (extend the range of the highlight).

进行演示.如果光标选择了world,:

To demonstrate. If world, is selected by the cursor:

你好world, lorem ipsum对titan的攻击.<​​/p>

Hello world, lorem ipsum attack on titan.

用户单击lorem时,应选择两个单词,如下所示:

And user clicks on lorem, it should select both words like this:

你好world, lorem ipsum对titan的攻击.<​​/p>

Hello world, lorem ipsum attack on titan.

如果用户单击Hello,则行为相同.

Same behavior if user clicks Hello.

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

So it only extends the highlight if the word is connecting. Example, if worlds, is selected, and user clicks on ipsum, it should just select ipsum.

采用什么方法来扩大亮点范围?

What's the approach to extend the highlight reach?

jsFiddle中的代码是:

Code in jsFiddle is:

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>

赏金信息

奖励现有答案,因为它非常有用.无需发布更多解决方案,因为这个解决方案已经足够完善.

Bounty info

Rewarding the existing answer because it's profoundly useful. No need to post more solutions as this one is as complete as it gets.

推荐答案

 

好吧,我把它放在首位,因为它是一个重大更新,并且我认为甚至可以认为是对先前功能的升级.

Ok, I am putting this at top, because it is a major update and, I believe, can even be considered as an upgrade on the previous function.

请求是使上一个功能反向工作,即,再次单击突出显示的单词时,该单词将从总选择中删除.

The request was to make the previous function work in reverse, i.e. when a highlighted word is clicked again, it would be removed from the total selection.

挑战是,当单击段落内<p></p>标记边缘或<b></b>标记边缘的突出显示单词时,<必须将范围的c12>或endContainer携带到它们所在的当前元素的 中,并且必须重置startOffsetendOffset也一样我不确定这是否是问题的明确表达,但是总之,由于Range对象的工作方式,最接近HTML标签的单词被证明是一个很大的挑战.

The challenge was that when a highlighted word at the edge of <p> and </p> tags or the edge of <b> and </b> tags inside the paragraphs was clicked, the startContainer or endContainer of the range had to be carried into or out of the current element they were positioned and the startOffset or endOffset had to be reset as well. I am not sure if this has been a clear expression of the problem, but, in brief, due to the way Range objects work, the words closest to HTML tags proved to be quite a challenge.

解决方案是要引入一些新的正则表达式测试,几个if检查以及一个用于查找下一个/上一个同级的局部函数.在此过程中,我还修复了一些以前没有引起我注意的问题.新功能在下面,更新的提琴在这里.

The Solution was to introduce a few new regex tests, several if checks, and a local function for finding the next/previous sibling. During the process, I have also fixed a few things which had escaped my attention before. The new function is below and 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相邻,该工作:

Storing the last range in an object and checking if the previously selected range is adjacent to the new range every time a new selection is made, does the job:

(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 Fiddle 此处.

JS Fiddle here.

更新(在升级之前完成):

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

If you want to this feature to be effective when clicking but not dragging, all you have to do is to change the if(prevRangeInfo.startContainer) condition as follows:

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

更新的JS小提琴位于此处.

The updated JS Fiddle is here.

这篇关于如果突出显示了一个单词,并且用户单击了所连接的单词,请同时突出显示两个单词的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆