Определить, какое слово было нажато в тексте - PullRequest
38 голосов
/ 27 сентября 2011

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

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

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

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

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

Ответы [ 10 ]

49 голосов
/ 16 февраля 2012

Вот решение, которое будет работать без добавления тонн документов в документ (работает в 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 для выделения и добавил два цикла, чтобы найти границы слова. Первый продолжает добавлять символы в слово, пока оно не достигнет пробела. второй цикл идет до конца слова, пока не достигнет пробела.

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

13 голосов
/ 27 сентября 2011

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

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

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

5 голосов
/ 27 декабря 2016

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

$(".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 голосов
/ 27 сентября 2011

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

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

http://jsfiddle.net/kMvYy/

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

3 голосов
/ 27 сентября 2011

-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>
2 голосов
/ 27 сентября 2011

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

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

1 голос
/ 31 июля 2018

И еще один ответ на ответ @ 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>
1 голос
/ 11 января 2018

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

В первом разделе кода выше range.setStart (узел, (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;
    }
}
1 голос
0 голосов
/ 02 августа 2018

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

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

Я тестирую

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...