UPGRADE
好吧,我把它放在最上面,因为这是一个重大更新,我相信甚至可以认为是对之前功能的升级。
请求的目的是使之前的功能反向工作,即当再次单击突出显示的单词时,它将从总选择中删除。
挑战是当边缘突出显示的单词时<p>
and </p>
标签或边缘<b>
and </b>
单击段落内的标签后,startContainer
or endContainer
必须携带该范围的into or out of它们当前所在的元素以及startOffset
or endOffset
也必须重置。我不确定这是否清楚地表达了问题,但是,简而言之,由于方式Range
对象工作时,最接近 HTML 标签的单词被证明是一个相当大的挑战。
解决方案是引入一些新的正则表达式测试,几个if
检查,以及用于查找下一个/上一个同级的本地函数。在这个过程中,我还修复了一些之前没有注意到的事情。新功能如下更新的小提琴在这里 https://jsfiddle.net/1tvoa0um/13/.
(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
in an 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'));
JSFiddlehere https://jsfiddle.net/1tvoa0um/4/.
更新(升级前完成):
如果你想让这个功能在点击而不是拖动时有效,你所要做的就是改变if(prevRangeInfo.startContainer)
条件如下:
if(prevRangeInfo.startContainer && nA === nF && oA === oF){
// rest of the code is the same...
更新后的 JS Fiddle 是here https://jsfiddle.net/1tvoa0um/5/.