我最近发布了一个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'));
});
奖励现有答案,因为它非常有用。无需发布更多解决方案,因为这个解决方案尽可能完整。
答案 0 :(得分:3)
好的,我把它放在最前面,因为它是一个重大更新,我相信,甚至可以被认为是对以前功能的升级。
请求是让上一个功能反向运行,即再次点击突出显示的单词时,它将从总选择中删除。
挑战是指<p>
和</p>
标签边缘或<b>
和</b>
标签边缘突出显示的字词单击段落内部,范围的startContainer
或endContainer
必须进入或 当前元素的位置和必须重置startOffset
或endOffset
。我不确定这是否是问题的明确表达,但简而言之,由于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。