簡體   English   中英

檢測文本中哪個單詞被點擊

[英]Detect which word has been clicked on within a text

我正在構建一個 JS 腳本,它在某個時候能夠在給定的頁面上允許用戶單擊任何單詞並將該單詞存儲在變量中。

我有一個非常難看的解決方案,它涉及使用 jQuery 進行類解析:我首先解析整個 html,將每個空間上的所有內容拆分為" " ,然后重新附加包裹在<span class="word">word</span> ,然后我用 jQ 添加一個事件來檢測對此類類的點擊,並使用 $(this).innerHTML 我得到點擊的詞。

這在很多方面都是緩慢而丑陋的,我希望有人知道實現這一目標的另一種方法。

PS:我可能會考慮將它作為瀏覽器擴展來運行,所以如果僅使用 JS 聽起來不可能,並且如果您知道允許這樣做的瀏覽器 API,請隨時提及它!

一個可能的 owrkaround 是讓用戶突出顯示這個詞而不是點擊它,但我真的很想只需點擊一下就可以實現同樣的事情!

這是一個無需向文檔添加大量跨度即可工作的解決方案(適用於 Webkit 和 Mozilla 以及 IE9+):

https://jsfiddle.net/Vap7C/15/

 $(".clickable").click(function(e){ s = window.getSelection(); var range = s.getRangeAt(0); var node = s.anchorNode; // Find starting point while(range.toString().indexOf(' ') != 0) { range.setStart(node,(range.startOffset -1)); } range.setStart(node, range.startOffset +1); // Find ending point do{ range.setEnd(node,range.endOffset + 1); }while(range.toString().indexOf(' ') == -1 && range.toString().trim() != ''); // Alert result var str = range.toString().trim(); alert(str); });
 <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <p class="clickable"> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris rutrum ante nunc. Proin sit amet sem purus. Aliquam malesuada egestas metus, vel ornare purus sollicitudin at. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer porta turpis ut mi pharetra rhoncus. Ut accumsan, leo quis hendrerit luctus, purus nunc suscipit libero, sit amet lacinia turpis neque gravida sapien. Nulla facilisis neque sit amet lacus ornare consectetur non ac massa. In purus quam, imperdiet eget tempor eu, consectetur eget turpis. Curabitur mauris neque, venenatis a sollicitudin consectetur, hendrerit in arcu. </p>

在 IE8 中,由於 getSelection,它有問題。 此鏈接( 是否有 getSelection() 的跨瀏覽器解決方案? )可能有助於解決這些問題。 我還沒有在 Opera 上測試過。

我從類似的問題中使用https://jsfiddle.net/Vap7C/1/作為起點。 它使用了Selection.modify函數:

s.modify('extend','forward','word');
s.modify('extend','backward','word');

不幸的是,他們並不總能得到完整的信息。 作為一種解決方法,我獲得了選擇范圍並添加了兩個循環來查找單詞邊界。 第一個不斷向單詞添加字符,直到它到達一個空格。 第二個循環到單詞的末尾,直到它到達一個空格。

這也將抓住單詞末尾的任何標點符號,因此請確保在需要時將其修剪掉。

據我所知,為每個單詞添加一個span是唯一的方法。

您可以考慮使用Lettering.js ,它會為您處理拆分 盡管這不會真正影響性能,除非您的“拆分代碼”效率低下。

然后,與其將.click()綁定到每個span.click()將單個.click()綁定到span的容器,並檢查event.target以查看單擊了哪個span會更有效。

以下是對已接受答案的改進:

$(".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();
});​

另一個對@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>

我所知道的唯一跨瀏覽器(IE < 8)方式是包裝在span元素中。 這很丑陋,但並不是那么慢。

這個例子直接來自 jQuery .css() 函數文檔,但有一大塊文本要預處理:

http://jsfiddle.net/kMvYy/

這是在不需要在span換行的同一文本塊上執行此操作的另一種方法(此處給出: jquery 捕獲單詞 value )。 http://jsfiddle.net/Vap7C/1

- 編輯 -這個怎么樣? 它使用綁定到mouseup getSelection()

<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>

我想不出除了拆分之外的其他方法,這就是我要做的,一個小插件將拆分為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>

這是我對stevendaniels 的回答(上圖)的評論的后續:

在上面的第一個代碼部分, range.setStart(node, (range.startOffset - 1)); 在“節點”中的第一個單詞上運行時崩潰,因為它試圖將范圍設置為負值。 我嘗試添加邏輯來防止這種情況發生,但是隨后的 range.setStart(node, range.startOffset + 1); 返回除第一個單詞的第一個字母之外的所有內容。 此外,當單詞由換行符分隔時,除了單擊的單詞外,還返回上一行的最后一個單詞。 所以,這需要一些工作。

這是我的代碼,使該答案中的范圍擴展代碼可靠地工作:

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;
    }
}

這是一種完全不同的方法。 我不確定它的實用性,但它可能會給你一些不同的想法。 如果您有一個帶有相對位置的容器標簽,其中只有文本,這就是我的想法。 然后你可以在每個單詞周圍放一個跨度記錄它的偏移量 Height、Width、Left 和 Top,然后刪除 span。 將它們保存到一個數組中,然后當該區域中有點擊時,進行搜索以找出最接近點擊的單詞。 這顯然在開始時會很密集。 因此,在此人將花費一些時間閱讀文章的情況下,這將最有效。 好處是您不需要擔心可能有 100 多個額外元素,但這種好處充其量只是微不足道的。

注意我認為您可以從 DOM 中刪除容器元素以加快過程並仍然獲得偏移距離,但我並不積極。

所選解決方案有時不適用於俄語文本(顯示錯誤)。 對於俄文和英文文本,我建議采用以下解決方案:

function returnClickedWord(){
    let selection = window.getSelection(),
        text = selection.anchorNode.data,
        index = selection.anchorOffset,
        symbol = "a";
    while(/[a-zA-z0-9а-яА-Я]/.test(symbol)&&symbol!==undefined){
        symbol = text[index--];
    }
    index += 2;
    let word = "";
    symbol = "a";
    while(/[a-zA-z0-9а-яА-Я]/.test(symbol) && index<text.length){
        symbol = text[index++];
    word += symbol;
    }
    alert(word);
}
document.addEventListener("click", returnClickedWord);

為了其余答案的完整性,我將對所使用的主要方法進行解釋:

  • window.getSelection() :這是主要方法。 它用於獲取有關您在文本中所做選擇的信息(通過按下鼠標按鈕,拖動然后釋放,而不是通過簡單的單擊)。 它返回一個Selection對象,其主要屬性是anchorOffset和focusOffset,分別是選擇的第一個和最后一個字符的位置。 如果它沒有完全意義,這是我之前鏈接的 MDN 網站提供的錨點和焦點的描述:

    錨點是用戶開始選擇的地方,焦點是用戶結束選擇的地方

    • toString() :此方法返回選定的文本。

    • anchorOffset :您進行選擇的節點文本中選擇的起始索引。
      如果你有這個 html:

       <div>aaaa<span>bbbb cccc dddd</span>eeee/div>

      並且您選擇'cccc',然后選擇anchorOffset == 5,因為在節點內部,選擇從html 元素的第5 個字符開始。

    • focusOffset :您進行選擇的節點文本中選擇的最終索引。
      按照上一個示例,focusOffset == 9。

    • getRangeAt() :返回一個Range對象。 它接收一個索引作為參數,因為(我懷疑,我實際上需要對此進行確認)在某些瀏覽器(例如 Firefox)中,您可以一次選擇多個獨立文本

      • startOffset :這個 Range 的屬性類似於 anchorOffset。
      • endOffset :正如預期的那樣,這個類似於 focusOffset。
      • toString :類似於 Selection 對象的 toString() 方法。

除了其他解決方案,還有另一種似乎沒有人注意到的方法: Document.caretRangeFromPoint()

Document 接口的 caretRangeFromPoint() 方法為指定坐標下的文檔片段返回一個 Range 對象。

如果您點擊此鏈接,您將看到該文檔實際上如何提供與 OP 要求的內容非常相似的示例。 這個例子沒有得到用戶點擊的特定單詞,而是在用戶點擊的字符之后添加一個<br>

 function insertBreakAtPoint(e) { let range; let textNode; let offset; if (document.caretPositionFromPoint) { range = document.caretPositionFromPoint(e.clientX, e.clientY); textNode = range.offsetNode; offset = range.offset; } else if (document.caretRangeFromPoint) { range = document.caretRangeFromPoint(e.clientX, e.clientY); textNode = range.startContainer; offset = range.startOffset; } // Only split TEXT_NODEs if (textNode && textNode.nodeType == 3) { let replacement = textNode.splitText(offset); let br = document.createElement('br'); textNode.parentNode.insertBefore(br, replacement); } } let paragraphs = document.getElementsByTagName("p"); for (let i = 0; i < paragraphs.length; i++) { paragraphs[i].addEventListener('click', insertBreakAtPoint, false); }
 <p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>

通過獲取前一個空白字符之后和下一個空白字符之前的所有文本來獲取單詞只是一個問題。

這是適用於西里爾文的已接受答案的替代方案。 我不明白為什么需要檢查單詞邊界,但默認情況下,出於某種原因,選擇是折疊的。

let selection = window.getSelection();
if (!selection || selection.rangeCount < 1) return
let node = selection.anchorNode
let range = selection.getRangeAt(0)

let text = selection.anchorNode.textContent

let startIndex, endIndex
startIndex = endIndex = selection.anchorOffset
const expected = /[A-ZА-Я]*/i

function testSlice() {
  let slice = text.slice(startIndex, endIndex)
  return slice == slice.match(expected)[0]
}

while(startIndex > 0 && testSlice()) {
  startIndex -= 1
}
startIndex += 1

while(endIndex < text.length && testSlice()){
  endIndex += 1
}
endIndex -= 1

range.setStart(node, startIndex)
range.setEnd(node, endIndex)

let word = range.toString()
return word

看起來像一個稍微簡單的解決方案。

document.addEventListener('selectionchange', () => {
  const selection = window.getSelection();
  const matchingRE = new RegExp(`^.{0,${selection.focusOffset}}\\s+(\\w+)`);
  const clickedWord = (matchingRE.exec(selection.focusNode.textContent) || ['']).pop();
});

我在測試

接受的答案一樣,此解決方案使用window.getSelection來推斷文本中的光標位置。 它使用正則表達式來可靠地找到單詞邊界,並且不限制起始節點結束節點為同一個節點。

此代碼對接受的答案有以下改進:

  • 在文本的開頭工作。
  • 允許跨多個節點進行選擇。
  • 不修改選擇范圍。
  • 允許用戶使用自定義選擇覆蓋范圍。
  • 即使被非空格包圍也能檢測到單詞(例如"\\t\\n"
  • 僅使用普通 JavaScript。
  • 沒有警報!

 getBoundaryPoints = (range) => ({ start: range.startOffset, end: range.endOffset }) function expandTextRange(range) { // expand to include a whole word matchesStart = (r) => r.toString().match(/^\\s/) // Alternative: /^\\W/ matchesEnd = (r) => r.toString().match(/\\s$/) // Alternative: /\\W$/ // Find start of word while (!matchesStart(range) && range.startOffset > 0) { range.setStart(range.startContainer, range.startOffset - 1) } if (matchesStart(range)) range.setStart(range.startContainer, range.startOffset + 1) // Find end of word var length = range.endContainer.length || range.endContainer.childNodes.length while (!matchesEnd(range) && range.endOffset < length) { range.setEnd(range.endContainer, range.endOffset + 1) } if (matchesEnd(range) && range.endOffset > 0) range.setEnd(range.endContainer, range.endOffset - 1) //console.log(JSON.stringify(getBoundaryPoints(range))) //console.log('"' + range.toString() + '"') var str = range.toString() } function getTextSelectedOrUnderCursor() { var sel = window.getSelection() var range = sel.getRangeAt(0).cloneRange() if (range.startOffset == range.endOffset) expandTextRange(range) return range.toString() } function onClick() { console.info('"' + getTextSelectedOrUnderCursor() + '"') } var content = document.body content.addEventListener("click", onClick)
 <div id="text"> <p>Vel consequatur incidunt voluptatem. Sapiente quod qui rem libero ut sunt ratione. Id qui id sit id alias rerum officia non. A rerum sunt repudiandae. Aliquam ut enim libero praesentium quia eum.</p> <p>Occaecati aut consequuntur voluptatem quae reiciendis et esse. Quis ut sunt quod consequatur quis recusandae voluptas. Quas ut in provident. Provident aut vel ea qui ipsum et nesciunt eum.</p> </div>

因為它使用了箭頭函數,所以這段代碼在 IE 中不起作用; 但這很容易調整。 此外,因為它允許用戶選擇跨越節點,所以它可能返回用戶通常不可見的文本,例如存在於用戶選擇中的腳本標簽的內容。 (三擊最后一段來演示這個缺陷。)

您應該決定用戶應該看到哪些類型的節點,並過濾掉不需要的節點,我認為這超出了問題的范圍。

一位匿名用戶建議進行此編輯:一個改進的解決方案,它總是得到正確的詞,更簡單,並且適用於 IE 4+

http://jsfiddle.net/Vap7C/80/

document.body.addEventListener('click',(function() {
 // Gets clicked on word (or selected text if text is selected)
 var t = '';
 if (window.getSelection && (sel = window.getSelection()).modify) {
    // Webkit, Gecko
    var s = window.getSelection();
    if (s.isCollapsed) {
        s.modify('move', 'forward', 'character');
        s.modify('move', 'backward', 'word');
        s.modify('extend', 'forward', 'word');
        t = s.toString();
        s.modify('move', 'forward', 'character'); //clear selection
    }
    else {
        t = s.toString();
    }
  } else if ((sel = document.selection) && sel.type != "Control") {
    // IE 4+
    var textRange = sel.createRange();
    if (!textRange.text) {
        textRange.expand("word");
    }
    // Remove trailing spaces
    while (/\s$/.test(textRange.text)) {
        textRange.moveEnd("character", -1);
    }
    t = textRange.text;
 }
 alert(t);
});

這是一個替代方案,並不意味着要在視覺上修改范圍選擇。

/**
 * Find a string from a selection
 */
export function findStrFromSelection(s: Selection) {
  const range = s.getRangeAt(0);
  const node = s.anchorNode;
  const content = node.textContent;

  let startOffset = range.startOffset;
  let endOffset = range.endOffset;
  // Find starting point
  // We move the cursor back until we find a space a line break or the start of the node
  do {
    startOffset--;
  } while (startOffset > 0 && content[startOffset - 1] != " " && content[startOffset - 1] != '\n');

  // Find ending point
  // We move the cursor forward until we find a space a line break or the end of the node
  do {
    endOffset++;
  } while (content[endOffset] != " " && content[endOffset] != '\n' && endOffset < content.length);
  
  return content.substring(startOffset, endOffset);
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM