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

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

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

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

Arman 发布了美丽的解决方案。

jsFiddle进行测试。

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

展示。 如果是world,则由光标选择:

你好world, lorem ipsum攻击泰坦。

用户点击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  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:

 

Hello world, lorem ipsum attack on titan.

JS doesn't affect this text.

赏金信息

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

升级

好的,我把它放在首位,因为它是一个重大更新,我相信,甚至可以被视为对以前function的升级。

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

挑战是当点击

标签边缘的突出显示的单词或段落内的标签的endContainer ,该范围的startContainerendContainer必须是进入离开它们所处的当前元素,并且还必须重置startOffsetendOffset 。 我不确定这是否是问题的明确表达,但简而言之,由于Range对象的工作方式,最接近HTML标签的单词被certificate是一个相当大的挑战。

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

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


升级前

在每次进行新选择时,在object存储最后一个range并检查先前选择的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小提琴在这里 。

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

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

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

更新的JS Fiddle就在这里 。