I have some text:
Hello world, Attack on Titan season two!
Currently, if a user wants to highlight a word/ter
Using node.textContent
we can find spaces and jump our selection to whole words without the need for creating new elements.
Mainly for my potential future use I've written this fairly modular, it also doesn't require a mouseup on the watched element, can deal with an element collection and also makes the selection changes if the user makes their selection using their keyboard.
var WordJumpSelection = (function() {
var watchList = [];
var WordJumpSelection = {
stopWatching: function(elem) {
var wlIdx = watchList.indexOf(elem);
if(wlIdx > -1) watchList.splice(wlIdx,1);
},
watch: function(elem) {
var elems = Array.prototype.slice.call(typeof elem.length === "number" ? elem : arguments);
if(watchList.length === 0)
{
WordJumpSelection.init();
}
elems.forEach(function(elem) {
if(watchList.indexOf(elem) === -1)
{
watchList.push(elem);
}
});
},
init: function() {
function handleSelectionChange() {
if(watchList.length === 0) return;
var selection = window.getSelection();
var selDir = getSelectionDir(selection);
var startNode,endNode,startPos,endPos;
if(selDir === 1)
{
startNode = selection.anchorNode;
endNode = selection.focusNode;
startPos = selection.anchorOffset;
endPos = selection.focusOffset;
}
else
{
startNode = selection.focusNode;
endNode = selection.anchorNode;
startPos = selection.focusOffset;
endPos = selection.anchorOffset;
}
var rangeStart = textNodeIsWatched(startNode) ? roundSelectionIndex(startNode,0,startPos) : startPos-1;
var rangeEnd = textNodeIsWatched(endNode) ? roundSelectionIndex(endNode,1,endPos) : endPos;
var r = document.createRange();
r.setStart(startNode,rangeStart+1)
r.setEnd(endNode,rangeEnd)
selection.removeAllRanges();
selection.addRange(r);
}
document.documentElement.addEventListener('mouseup', handleSelectionChange);
document.documentElement.addEventListener('keyup', function(e) {
if(e.keyCode === 16)
{
handleSelectionChange();
}
});
WordJumpSelection.init = function(){};
}
};
return WordJumpSelection;
function getSelectionDir(sel) {
var range = document.createRange();
range.setStart(sel.anchorNode,sel.anchorOffset);
range.setEnd(sel.focusNode,sel.focusOffset);
if(range.startContainer !== sel.anchorNode || (sel.anchorNode === sel.focusNode && sel.focusOffset < sel.anchorOffset)) return -1;
else return 1;
}
function roundSelectionIndex(textNode,nodeId,idx) {
var isStart = nodeId === 0;
var contents = textNode.textContent;
var nearestSpaceIdx = -1;
if(isStart)
{
nearestSpaceIdx = contents.lastIndexOf(' ',idx);
if(nearestSpaceIdx === -1) nearestSpaceIdx = -1;
}
else
{
nearestSpaceIdx = contents.indexOf(' ',idx);
if(nearestSpaceIdx === -1) nearestSpaceIdx = contents.length;
}
return nearestSpaceIdx;
}
function textNodeIsWatched(textNode) {
return watchList.indexOf(textNode.parentElement) > -1;
}
})();
An example jsFiddle
I am yet to test how this works on mobile, and haven't got it working live yet - but it might be a good start.
Update: Now selects word with a single click