如何更快速,更智能地突出显示单词/术语?
我有一些文字:
Hello world, Attack on Titan season two!
目前,如果用户想要用光标突出显示单词/术语,他们将逐字逐句地单击并拖动。
我希望这个过程更快。 例如,如果用户开始突出显示At
,则应自动突出显示单词的其余部分Attack
。 所以空的空间就是分隔线。
我知道这可以通过将单词划分为div来实现,但我希望在一个
标记内提供纯文本的解决方案。
你可以使用Range
和selectionRange
对象使用纯JS。
HTML:
Hello world, Attack on Titan season two!
Another paragraph with sample text.
The selection will behave normally on this div.
JS:
(function(el){ 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); /* } */ } else { // Fallback for Internet Explorer 8 and earlier // (if you think it still is worth the effort of course) } // Stop Moz user select el.style.MozUserSelect = '-moz-none'; }); /* This part is added 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) } // Add Moz user select back el.style.MozUserSelect = 'text'; }); })(document.getElementById('selectable'));
请在这里查看工作示例。
更新:
- 点击添加全字选择
- 修复了添加的Firefox特定拖动行为
在这里更新了JSFiddle。
使用node.textContent
我们可以找到空格并将我们的选择跳转到整个单词,而无需创建新元素。
主要是为了我未来的潜在用途,我写了这个相当模块化的,它也不需要在观察元素上使用鼠标,可以处理元素集合,并且如果用户使用键盘进行选择也会更改选择。
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; } })();
一个例子jsFiddle
我还没有测试它在移动设备上是如何工作的,还没有让它正常工作 – 但它可能是一个好的开始。
更新:现在只需单击一下即可选择单词
概念
要选择每个单词,首先必须记住一些事项:
-
textNode
是包含所有单词的单个sting,您将无法选择每个“单词”,因为它不是DOM节点。 -
“拖动并选择”单词时,浏览器中没有触发特定事件。 但是,当您拖动并选择时,会触发2个事件:移动鼠标时会触发
mouseover
,释放鼠标按钮时会触发click
。 (即使在Mac的触控板上也是如此)。 -
选择单词时,“突出显示”有不同的实现。
脚步
根据这些概念,您必须按顺序执行以下步骤才能实现目标:
- 获取段落中的单词并用标签(例如
)包装它们以进行DOM选择
- 触发
click
事件(表示您的选择已结束)时,突出显示您刚刚选择的单词。
实现将是这样的(使用jQuery
)。 你可能会在这里看到现场演示:
$(function() { // 1. When mouseover the paragraph, wrapped each word with $('p').one('mouseover', function(event) { $('p').html(function(index, text) { var wordsArray = text.split(' '); var wrappedArray = wordsArray.map(function(val, index) { val = '' + val + ''; return val; }); var wrappedString = wrappedArray.join(' '); // 2. Replace the paragraph with wrapped text $(this).html(wrappedString); // 3. When the word is select, highlight the word $(this).children('span').on('click', function() { var selector = '.' + $(this).attr('class'); SelectText(selector); }); }); }); }); function SelectText(element) { var doc = document, text = doc.querySelector(element), range, selection; if (doc.body.createTextRange) { range = document.body.createTextRange(); range.moveToElementText(text); range.select(); } else if (window.getSelection) { selection = window.getSelection(); range = document.createRange(); range.selectNodeContents(text); selection.removeAllRanges(); selection.addRange(range); } }
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Autem amet suscipit incidunt placeat dicta iure, perspiciatis libero nobis dolore, temporibus et! Quae fugiat necessitatibus ut, molestias aut. Sequi rerum earum facilis voluptates ratione architecto officia quod aut unde voluptas? Dignissimos ducimus exercitationem perspiciatis nam numquam minima accusamus quod necessitatibus amet illo vel vero placeat voluptate eos iste ratione veniam quisquam atque non voluptatum sint hic sed, suscipit. Doloremque officiis rerum sunt delectus unde odit eos quod earum aspernatur, tempora neque modi tempore minima maiores fuga eaque dolore quos minus veritatis aliquid, vel suscipit dolores. Voluptatem eius obcaecati, laborum ipsa a!
此文本是文本节点,文本节点根本不会触发大多数事件。 但是它们可以触发DOM突变事件 ,例如DOMCharacterDataModified
,它用于检测文本节点文本的更改:
var textNode = document.getElementsByClassName("drag")[0].firstChild; textNode.addEventListener("DOMCharacterDataModified", function(e) { console.log("Text changed from '" + e.prevValue + "' to '" + evt.newValue +"'"); }, false);
但是,
Hello world, Attack on Titan season two!
中的文字
Hello world, Attack on Titan season two!
是一个单一的文本节点,你需要每个单词都是一个单独的节点。
我看到的唯一解决方案是将每个单词放在span
标记中。 你不能用纯文本做到这一点。
编辑
下面是一个如何使用span
标签执行此操作的示例(我在这里使用jQuery只是为了减少代码量,这不是必需的):
$(function() { $('.drag').on('click', 'span', function() { var range; if (document.selection) { range = document.body.createTextRange(); range.moveToElementText($(this)[0]); range.select(); } else if (window.getSelection) { range = document.createRange(); range.selectNode($(this)[0]); window.getSelection().addRange(range); } }); });
这是JS Bin的一个例子
更新
我编辑了代码片段,因此选择的行为与您的要求相同(目前它仅适用于从左到右的选择,而不是反向选择):
$(function(){ var range = document.createRange(); var selectionMode = false; $(document).on('mouseup', function() { selectionMode = false; }) .on('mousedown', '.drag', function(e) { selectionMode = true; }) .on('dragstart', '.drag span', function(e) { return false; }); $('.drag').on('mousedown', 'span', function() { range.setStartBefore($(this)[0]); range.setEndAfter($(this)[0]); window.getSelection().addRange(range); }) .on('mousemove', 'span', function() { if (!selectionMode) { return; } range.setEndAfter($(this)[0]); window.getSelection().addRange(range); }) .on('mouseup', 'span', function() { setTimeout(function(){ window.getSelection().addRange(range); }, 1); }); });
您可以在此处阅读有关HTML Range API的更多信息: https : //developer.mozilla.org/en-US/docs/Web/API/Range
所以你将不得不处理文本范围等。 我已经解决了这个问题,这非常痛苦,特别是如果你有DOM内容,如:
New season of Attack on Titan!
即与其他DOM元素混合的文本节点,例如在这种情况下的跨度。 考虑到这一点,我想强烈推荐图书馆rangy.js: https : //github.com/timdown/rangy
当我制作一个标签突出显示系统时,它为我节省了几天的头痛。
您不能将事件设置为文本,但可以将事件设置为Html元素。 将每个单词放在div元素中,并添加一个onmouseover事件,使用css将div更改为新的突出显示状态。
脚步:
- 使用split将单词转换为数组。
- 迭代单词并将其放入div中。
- 迭代div并将其设置为将div更改为css类.highlight的事件。
而已。