Подтвердить что ты не робот

Определить, какое слово было нажато в тексте

Я создаю JS script, который в какой-то момент способен на данной странице разрешить пользователю щелкнуть любое слово и сохранить это слово в переменной.

У меня есть одно решение, которое довольно уродливо и включает в себя разбор классов с помощью jQuery: Сначала я разбираю весь html, разбиваю все на каждое пространство " " и повторно добавляю все, заключенное в <span class="word">word</span>, а затем добавляю событие с jQ для обнаружения кликов на таком классе и используя $(this).innerHTML Я получаю нажатое слово.

Это медленное и уродливое во многих отношениях, и я надеялся, что кто-то знает о другом способе достижения этого.

PS: Я мог бы использовать его как расширение браузера, поэтому, если это не кажется возможным с помощью простого JS, и если вы знаете API-интерфейс браузера, который позволит это, не стесняйтесь упоминать его!

Возможное owrkaround будет заключаться в том, чтобы заставить пользователя выделить это слово, а не щелкнуть его, но мне очень хотелось бы добиться того же самого результата одним щелчком!

4b9b3361

Ответ 1

Здесь будет решение, которое будет работать без добавления в документ количества пробелов (работает с Webkit и Mozilla и IE9 +):

http://jsfiddle.net/Vap7C/15/

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

в IE8, у него есть проблемы из-за getSelection. Эта ссылка (Есть ли кросс-браузерное решение для getSelection()?) может помочь с этими проблемами. Я не тестировал в Opera.

Я использовал http://jsfiddle.net/Vap7C/1/ из аналогичного вопроса в качестве отправной точки. Он использовал функцию Selection.modify:

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

К сожалению, они не всегда получают целое слово. В качестве обходного решения я получил Range для выбора и добавил две петли, чтобы найти границы слов. Первая добавляет символы в слово до тех пор, пока оно не достигнет пробела. второй цикл идет до конца слова, пока не достигнет пробела.

Это также будет захватывать любые знаки препинания в конце слова, поэтому убедитесь, что вы обрезаете это, если вам нужно.

Ответ 2

Насколько я знаю, добавление span для каждого слова - единственный способ сделать это.

Вы можете использовать Lettering.js, который обрабатывает расщепление для вас. Хотя это не повлияет на производительность, если ваш "код разделения" неэффективен.

Затем вместо привязки .click() к каждому span было бы более эффективно связывать один .click() с контейнером span s и проверять event.target, чтобы увидеть, какой span был нажат.

Ответ 3

Единственный способ перекрестного браузера (IE < 8), который я знаю, обертывается в элементы span. Это уродливо, но не так медленно.

Этот пример прямо из документации функции jQuery.css(), но с огромным блоком текста для предварительной обработки:

http://jsfiddle.net/kMvYy/

Вот еще один способ сделать это (здесь: jquery зафиксировать значение слова) в том же блоке текста, который не требует обертывания в span. http://jsfiddle.net/Vap7C/1

Ответ 4

Ниже приведены улучшения для принятого ответа:

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

Ответ 5

-Edit - Как насчет этого? он использует 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>

Я не могу придумать способ разделения, это то, что я сделал бы, небольшой плагин, который будет разбит на 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>

Ответ 6

Вот совершенно другой метод. Я не уверен в ее практичности, но это может дать вам несколько разных идей. Вот что я думаю, если у вас есть тег контейнера с положением относительно простого текста. Затем вы можете поместить диапазон вокруг каждого слова, записать его смещение Высота, Ширина, Влево и Верх, а затем удалить диапазон. Сохраните их в массиве, а затем, щелкнув по области, выполните поиск, чтобы узнать, какое слово ближе всего к клику. Вначале это было бы интенсивно. Таким образом, это будет работать лучше всего в ситуации, когда человек будет тратить некоторое время на просмотр статьи. Преимущество в том, что вам не нужно беспокоиться о возможностях 100 дополнительных элементов, но это преимущество может быть незначительным в лучшем случае.

Примечание. Я думаю, вы могли бы удалить элемент контейнера из DOM, чтобы ускорить процесс и все равно получить смещение расстояний, но я не уверен.

Ответ 7

И еще один ответ на @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>

Ответ 9

Это продолжение моего комментария к ответу stevendaniels (вверху):

В первом разделе кода выше range.setStart(node, (range.startOffset - 1)); сбой при запуске первого слова в "node", потому что он пытается установить диапазон на отрицательное значение. Я пытался добавление логики для предотвращения этого, но затем последующее 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;
    }
}

Ответ 10

Что выглядит как немного более простое решение.

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

я тестирую

Ответ 11

Выбранное решение иногда не работает на русских текстах (показывает ошибку). Я бы предложил следующее решение для русских и английских текстов:

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