Javascript 检测文本中哪个单词被点击
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/7563169/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Detect which word has been clicked on within a text
提问by Cystack
I am building a JS script which at some point is able to, on a given page, allow the user to click on any word and store this word in a variable.
我正在构建一个 JS 脚本,它在某个时候能够在给定的页面上允许用户单击任何单词并将这个单词存储在一个变量中。
I have one solution which is pretty ugly and involves class-parsing using jQuery:
I first parse the entire html, split everything on each space " "
, and re-append everything wrapped in a <span class="word">word</span>
, and then I add an event with jQ to detect clicks on such a class, and using $(this).innerHTML I get the clicked word.
我有一个非常难看的解决方案,它涉及使用 jQuery 进行类解析:我首先解析整个 html,拆分每个空间上的所有内容" "
,然后重新附加包裹在 a 中的所有内容<span class="word">word</span>
,然后我添加一个带有 jQ 的事件来检测点击这样一个类,并使用 $(this).innerHTML 我得到点击的词。
This is slow and ugly in so many ways and I was hoping that someone knows of another way to achieve this.
这在很多方面都是缓慢而丑陋的,我希望有人知道实现这一目标的另一种方法。
PS: I might consider running it as a browser extension, so if it doesn't sound possible with mere JS, and if you know a browser API that would allow that, feel free to mention it !
PS:我可能会考虑将它作为浏览器扩展来运行,所以如果仅使用 JS 听起来不可能,并且如果您知道允许这样做的浏览器 API,请随时提及它!
A possible owrkaround would be to get the user to highlight the word instead of clicking it, but I would really love to be able to achieve the same thing with only a click !
一个可能的 owrkaround 是让用户突出显示这个词而不是点击它,但我真的很想只需点击一下就可以实现同样的事情!
回答by stevendaniels
Here's a solution that will work without adding tons of spans to the document (works on Webkit and Mozilla and IE9+):
这是一个无需向文档添加大量跨度即可工作的解决方案(适用于 Webkit 和 Mozilla 以及 IE9+):
<p class="clickable">some words</p>
$(".clickable").click(function(e) {
s = window.getSelection();
var range = s.getRangeAt(0);
var node = s.anchorNode;
while (range.toString().indexOf(' ') != 0) {
range.setStart(node, (range.startOffset - 1));
}
range.setStart(node, range.startOffset + 1);
do {
range.setEnd(node, range.endOffset + 1);
} while (range.toString().indexOf(' ') == -1 && range.toString().trim() != '' && range.endOffset < node.length);
var str = range.toString().trim();
alert(str);
});?
in IE8, it has problems because of getSelection. This link ( Is there a cross-browser solution for getSelection()?) may help with those issues. I haven't tested on Opera.
在 IE8 中,由于 getSelection,它有问题。此链接(是否有 getSelection() 的跨浏览器解决方案?)可能有助于解决这些问题。我还没有在 Opera 上测试过。
I used http://jsfiddle.net/Vap7C/1/from a similar question as a starting point. It used the Selection.modifyfunction:
我从类似的问题中使用http://jsfiddle.net/Vap7C/1/作为起点。它使用了Selection.modify函数:
s.modify('extend','forward','word');
s.modify('extend','backward','word');
Unfortunately they don't always get the whole word. As a workaround, I got the Rangefor the selection and added two loops to find the word boundaries. The first one keeps adding characters to the word until it reaches a space. the second loop goes to the end of the word until it reaches a space.
不幸的是,他们并不总能得到完整的信息。作为一种解决方法,我获得了选择范围并添加了两个循环来查找单词边界。第一个不断向单词添加字符,直到它到达一个空格。第二个循环到单词的末尾,直到它到达一个空格。
This will also grab any punctuation at the end of the word, so make sure you trim that out if you need to.
这也将抓住单词末尾的任何标点符号,因此请确保在需要时将其修剪掉。
回答by thirtydot
As far as I know, adding a span
for each word is the only way to do this.
据我所知,span
为每个单词添加一个是唯一的方法。
You might consider using Lettering.js, which handles the splittingfor you. Though this won't really impact performance, unless your "splitting code" is inefficient.
您可以考虑使用Lettering.js,它会为您处理拆分。尽管这不会真正影响性能,除非您的“拆分代码”效率低下。
Then, instead of binding .click()
to every span
, it would be more efficient to bind a single .click()
to the container of the span
s, and check event.target
to see which span
has been clicked.
然后,与绑定.click()
到 every 相比span
,将 single 绑定.click()
到span
s的容器并检查event.target
哪个span
已被单击会更有效。
回答by link0ff
Here are improvements for the accepted answer:
以下是对已接受答案的改进:
$(".clickable").click(function (e) {
var selection = window.getSelection();
if (!selection || selection.rangeCount < 1) return true;
var range = selection.getRangeAt(0);
var node = selection.anchorNode;
var word_regexp = /^\w*$/;
// Extend the range backward until it matches word beginning
while ((range.startOffset > 0) && range.toString().match(word_regexp)) {
range.setStart(node, (range.startOffset - 1));
}
// Restore the valid word match after overshooting
if (!range.toString().match(word_regexp)) {
range.setStart(node, range.startOffset + 1);
}
// Extend the range forward until it matches word ending
while ((range.endOffset < node.length) && range.toString().match(word_regexp)) {
range.setEnd(node, range.endOffset + 1);
}
// Restore the valid word match after overshooting
if (!range.toString().match(word_regexp)) {
range.setEnd(node, range.endOffset - 1);
}
var word = range.toString();
});?
回答by spike
The only cross-browser (IE < 8) way that I know of is wrapping in span
elements. It's ugly but not really that slow.
我所知道的唯一跨浏览器(IE < 8)方式是包装在span
元素中。这很丑陋,但并不是那么慢。
This example is straight from the jQuery .css() function documentation, but with a huge block of text to pre-process:
这个例子直接来自 jQuery .css() 函数文档,但有一大块文本要预处理:
Here's another way of doing it (given here: jquery capture the word value) on the same block of text that doesn't require wrapping in span
.
http://jsfiddle.net/Vap7C/1
下面是这样做的另一种方式(这里给出:jQuery的捕获字值)文本的同一块不需要在包装上span
。
http://jsfiddle.net/Vap7C/1
回答by derp
-EDIT-What about this? it uses getSelection()
binded to mouseup
- 编辑 -这个怎么样?它使用getSelection()
绑定到mouseup
<script type="text/javascript" src="jquery-1.6.3.min.js"></script>
<script>
$(document).ready(function(){
words = [];
$("#myId").bind("mouseup",function(){
word = window.getSelection().toString();
if(word != ''){
if( confirm("Add *"+word+"* to array?") ){words.push(word);}
}
});
//just to see what we've got
$('button').click(function(){alert(words);});
});
</script>
<div id='myId'>
Some random text in here with many words huh
</div>
<button>See content</button>
I can't think of a way beside splitting, this is what I'd do, a small plugin that will split into spans
and when clicked it will add its content to an array
for further use:
我想不出除了拆分之外的其他方法,这就是我要做的,一个小插件,将拆分成spans
,单击时会将其内容添加到一个array
以供进一步使用:
<script type="text/javascript" src="jquery-1.6.3.min.js"></script>
<script>
//plugin, take it to another file
(function( $ ){
$.fn.splitWords = function(ary) {
this.html('<span>'+this.html().split(' ').join('</span> <span>')+'</span>');
this.children('span').click(function(){
$(this).css("background-color","#C0DEED");
ary.push($(this).html());
});
};
})( jQuery );
//plugin, take it to another file
$(document).ready(function(){
var clicked_words = [];
$('#myId').splitWords(clicked_words);
//just to see what we've stored
$('button').click(function(){alert(clicked_words);});
});
</script>
<div id='myId'>
Some random text in here with many words huh
</div>
<button>See content</button>
回答by cars10m
And another take on @stevendaniel's answer:
另一个对@stevendaniel 的回答的看法:
$('.clickable').click(function(){
var sel=window.getSelection();
var str=sel.anchorNode.nodeValue,len=str.length, a=b=sel.anchorOffset;
while(str[a]!=' '&&a--){}; if (str[a]==' ') a++; // start of word
while(str[b]!=' '&&b++<len){}; // end of word+1
console.log(str.substring(a,b));
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p class="clickable">The objective can also be achieved by simply analysing the
string you get from <code>sel=window.getSelection()</code>. Two simple searches for
the next blank before and after the word, pointed to by the current position
(<code>sel.anchorOffset</code>) and the work is done:</p>
<p>This second paragraph is <em>not</em> clickable. I tested this on Chrome and Internet explorer (IE11)</p>
回答by qw3n
Here is a completely different method. I am not sure about the practicality of it, but it may give you some different ideas. Here is what I am thinking if you have a container tag with position relative with just text in it. Then you could put a span around each word record its offset Height, Width, Left, and Top, then remove the span. Save those to an array then when there is a click in the area do a search to find out what word was closest to the click. This obviously would be intensive at the beginning. So this would work best in a situation where the person will be spending some time perusing the article. The benefit is you do not need to worry about possibly 100s of extra elements, but that benefit may be marginal at best.
这是一种完全不同的方法。我不确定它的实用性,但它可能会给你一些不同的想法。如果您有一个带有相对位置的容器标签,其中只有文本,这就是我的想法。然后你可以在每个单词周围放一个跨度记录它的偏移量 Height、Width、Left 和 Top,然后删除 span。将它们保存到一个数组中,然后当该区域中有点击时,进行搜索以找出最接近点击的单词。这显然在开始时会很密集。因此,在此人将花费一些时间阅读文章的情况下,这将最有效。好处是您不需要担心可能有 100 多个额外元素,但这种好处充其量只是微不足道的。
Note I think you could remove the container element from the DOM to speed up the process and still get the offset distances, but I am not positive.
注意我认为您可以从 DOM 中删除容器元素以加快过程并仍然获得偏移距离,但我并不积极。
回答by CODE-REaD
This is a followup on my comment to stevendaniels' answer(above):
这是我对stevendaniels 的回答(上图)的评论的后续:
In the first code section above, range.setStart(node, (range.startOffset - 1)); crashes when run on the first word in a "node," because it attempts to set range to a negative value. I tried adding logic to prevent that, but then the subsequent range.setStart(node, range.startOffset + 1); returns all but the first letter of the first word. Also, when words are separated by a newline, the last word on the previous line is returned in addition to the clicked-on word. So, this needs some work.
在上面的第一个代码部分, range.setStart(node, (range.startOffset - 1)); 在“节点”中的第一个单词上运行时崩溃,因为它试图将范围设置为负值。我尝试添加逻辑来防止这种情况发生,但是随后的 range.setStart(node, range.startOffset + 1); 返回除第一个单词的第一个字母之外的所有内容。此外,当单词由换行符分隔时,除了单击的单词外,还返回上一行的最后一个单词。所以,这需要一些工作。
Here is my code to make the range expansion code in that answer work reliably:
这是我的代码,使该答案中的范围扩展代码可靠地工作:
while (range.startOffset !== 0) { // start of node
range.setStart(node, range.startOffset - 1) // back up 1 char
if (range.toString().search(/\s/) === 0) { // space character
range.setStart(node, range.startOffset + 1);// move forward 1 char
break;
}
}
while (range.endOffset < node.length) { // end of node
range.setEnd(node, range.endOffset + 1) // forward 1 char
if (range.toString().search(/\s/) !== -1) { // space character
range.setEnd(node, range.endOffset - 1);// back 1 char
break;
}
}
回答by Alon Eitan
回答by stringparser
What looks like a slightly simpler solution.
看起来像一个稍微简单的解决方案。
document.addEventListener('selectionchange', () => {
const selection = window.getSelection();
const matchingRE = new RegExp(`^.{0,${selection.focusOffset}}\s+(\w+)`);
const clickedWord = (matchingRE.exec(selection.focusNode.textContent) || ['']).pop();
});
I'm testing
我在测试