Як отримати слово під курсором за допомогою JavaScript?


77

Якщо я, наприклад, маю

<p> some long text </p>

на моїй HTML-сторінці, як я можу знати, що курсор миші знаходиться, наприклад, над словом "текст"?


1
Це реальна демонстрація того, як отримати слово під курсором за допомогою JavaScript на основі вихідного коду, наданого Damovisa : jsfiddle.net/5gyRx .
Рубенс Маріуццо,

2
@Ivan На це запитання є нова відповідь із щедрості. Ви можете подумати про його вибір (заради нових користувачів, які приходять).
user1122069

Відповіді:


43

На додаток до двох інших відповідей ви можете розділити свої абзаци на проміжки, використовуючи jQuery (або javascript загалом).

Таким чином, вам не потрібно було б думати про виведення тексту з інтервалами навколо слів. Нехай ваш javascript зробить це за вас.

напр

<p>Each word will be wrapped in a span.</p>
<p>A second paragraph here.</p>
Word: <span id="word"></span>

<script type="text/javascript">
    $(function() {
        // wrap words in spans
        $('p').each(function() {
            var $this = $(this);
            $this.html($this.text().replace(/\b(\w+)\b/g, "<span>$1</span>"));
        });

        // bind to each span
        $('p span').hover(
            function() { $('#word').text($(this).css('background-color','#ffff66').text()); },
            function() { $('#word').text(''); $(this).css('background-color',''); }
        );
    });
</script>

Зверніть увагу, що вищевказаний код, хоча і працює, буде видаляти будь-який html всередині тегів абзаців.

Приклад jsFiddle


5
Або ви можете просто зробити $(this).text().replace(/\b(\w+)\b/g, "<span>$1</span>")замість циклу. Це буде правильно обробляти всі пробіли.
Chetan S

@Chetan - дякую за це, я не дуже добре працюю з регулярним виразом, тому я зробив це простим способом :) Я оновив його.
Damovisa

Я думав про це, але це незручне рішення (я новачок у JavaScript, тому мій шлях був набагато гіршим, ніж ваш). Дякую за роз'яснення. @Chetan - це чудове рішення.
Іван

Як би ми його редагували, щоб він ідентифікував теги h1, h2, h3 тощо, а не лише теги p?
продовжувати роботу

@idude Ви просто мали змогу замінити перший $('p')селектор $('p,h1,h2,h3')і так далі. Подібним чином, щоб навести курсор на роботу, потрібно змінити другий селектор на $('p span,h1 span,h2 span,h3 span').
Damovisa

40

Інша моя відповідь працює лише у Firefox. Ця відповідь працює в Chrome. (Можливо, це теж працює у Firefox, я не знаю.)

function getWordAtPoint(elem, x, y) {
  if(elem.nodeType == elem.TEXT_NODE) {
    var range = elem.ownerDocument.createRange();
    range.selectNodeContents(elem);
    var currentPos = 0;
    var endPos = range.endOffset;
    while(currentPos+1 < endPos) {
      range.setStart(elem, currentPos);
      range.setEnd(elem, currentPos+1);
      if(range.getBoundingClientRect().left <= x && range.getBoundingClientRect().right  >= x &&
         range.getBoundingClientRect().top  <= y && range.getBoundingClientRect().bottom >= y) {
        range.expand("word");
        var ret = range.toString();
        range.detach();
        return(ret);
      }
      currentPos += 1;
    }
  } else {
    for(var i = 0; i < elem.childNodes.length; i++) {
      var range = elem.childNodes[i].ownerDocument.createRange();
      range.selectNodeContents(elem.childNodes[i]);
      if(range.getBoundingClientRect().left <= x && range.getBoundingClientRect().right  >= x &&
         range.getBoundingClientRect().top  <= y && range.getBoundingClientRect().bottom >= y) {
        range.detach();
        return(getWordAtPoint(elem.childNodes[i], x, y));
      } else {
        range.detach();
      }
    }
  }
  return(null);
}    

Зателефонуйте у своєму обробнику миші getWordAtPoint(e.target, e.x, e.y);


Код чудово працює на iOS (6/7), але в Android 4.0.3 getBoundingClientRect може привести до нуля. Тож додайте: range.getBoundingClientRect ()! = Null як умову в першому циклі (до отримання властивості left).
Hugo Logmans

У документах зазначено, що межею для "слова" є пробіл. Але розширення, здається, не працює для URL-адрес. Будь-які ідеї?
Адам Готтерер,

@Eyal Я знайшов, що ваш код працює нормально в chrome, а не у firefox. Але коли range.expand коментується, він може надати символ під курсором для firefox. Будь-яка ідея змусити його працювати у firefox?
Nagaraju

2
Це хороший шматок коду, але він зламається при роботі з поєднанням textNodes та інших вбудованих елементів. Є два випадки, коли це з’являється. 1. Текстовий вузол із розривом рядка матиме обмежувальне поле безглуздя. 2. Вбудовані елементи, висота яких перевищує рядок textNode, можуть скинути вертикальне положення діапазону. Я думаю, це повинно бути можливим, щоб подолати їх, перевіряючи textNodes символ за символом з самого початку та компенсуючи випадкові скидання вертикального положення, припускаючи, що texNodes ніколи не може бути вищим за будь-якого з їхніх попередніх братів і сестер (але це не завжди може бути правдою).
джаху

Також +1 в умові в циклі while непотрібний. Останній символ textNode починається з range.endOffset(і закінчується на range.endOffset + 1). Отже, якщо умовою не є насправді while(currentPos < endPos)останній символ, ніколи не буде перевірятися.
джаху

39

Преамбула:

Якщо у вас є кілька інтервалів та вкладений HTML, що розділяє слова (або навіть символи у словах), тоді всі наведені вище рішення матимуть проблеми з поверненням повного та правильного слова.

Ось приклад з щедрот питання: Х</span>rт0съ. Як правильно повернути Хrт0съ? Ці проблеми не розглядались ще у 2010 році, тому я зараз буду представляти два рішення (2015).


Рішення 1 - Зніміть внутрішні мітки, оберніть прольоти навколо кожного повного слова:

Одним із рішень є видалення тегів span у абзацах, але збереження їх тексту. Таким чином, розділені слова та фрази поєднуються як звичайний текст. Кожне слово знаходить пробіл (а не просто пробіл), і ці слова загортаються в проміжки, до яких можна отримати індивідуальний доступ.

У демонстраційній частині ви можете виділити ціле слово і таким чином отримати текст цілого слова.


рис 0

Код:

$(function() {
  // Get the HTML in #hoverText - just a wrapper for convenience
  var $hoverText = $("#hoverText");

  // Replace all spans inside paragraphs with their text
  $("p span", $hoverText).each(function() {
    var $this = $(this);
    var text = $this.text(); // get span content
    $this.replaceWith(text); // replace all span with just content
  });

  // Wrap words in spans AND preserve the whitespace
  $("p", $hoverText).each(function() {
    var $this = $(this);
    var newText = $this.text().replace(/([\s])([^\s]+)/g, "$1<span>$2</span>");
    newText = newText.replace(/^([^\s]+)/g, "<span>$1</span>");
    $this.empty().append(newText);
  });

  // Demo - bind hover to each span
  $('#hoverText span').hover(
    function() { $(this).css('background-color', '#ffff66'); },
    function() { $(this).css('background-color', ''); }
  );
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="hoverText">
  <p><span class="kinovar"><span id="selection_index3337" class="selection_index"></span>По f7-мъ часЁ твори1тъ сщ7eнникъ начaло съ кади1ломъ и3 со свэщeю, цrкимъ двeремъ tвeрзєннымъ, и3 поeтъ: Х</span>rт0съ воскRсе: <span class="kinovar">со 
стіхи2. И# по стісёхъ pал0мъ: Б</span>лгcви2 душE моS гDа: <span class="kinovar">И# є3ктеніA. Тaже каfjсма nбhчнаz.</span>
  </p>
</div>

Рішення 1 повнотекстова демонстрація


Рішення 2 - Інспекція Каре і обхід DOM:

Ось більш складне рішення. Це алгоритмічне рішення з використанням обходу вузла, яке точно фіксує повне і правильне слово під курсором у текстовому вузлі.

Тимчасове слово можна знайти, перевіривши позицію каретки (використовуючи caretPositionFromPointабо caretRangeFromPoint, кредити за ідею @chrisv). Це все ще може бути повним словом, а може і не бути.

Потім його аналізують, щоб побачити, чи знаходиться він на кожному краю текстового вузла (початку чи кінця). Якщо це так, попередній текстовий вузол або наступний текстовий вузол перевіряється, щоб перевірити, чи слід приєднувати його, щоб зробити цей фрагмент слова довшим.

Приклад:

Х</span>rт0съповинен повернутися Хrт0съ, а не Хні rт0съ.

Дерево DOM обходить, щоб отримати наступний неперешкодний текстовий вузол. Якщо два фрагменти слів відокремлені <p>або іншим бар'єрним тегом, то вони не суміжні і, отже, не є частиною одного слова.

Приклад:

њб.)</p><p>Во не повинен повертатися њб.)Во


У демо-версії лівий плаваючий div - це слово під курсором. Правий плаваючий div, якщо видно, показує, як утворилося слово на межі. Інші теги можуть бути безпечно вбудовані в текст із цим рішенням.

рис 1

Код:

$(function() {
  // Get the HTML in #hoverText - just a wrapper for convenience
  var $hoverText = $("#hoverText");

  // Get the full word the cursor is over regardless of span breaks
  function getFullWord(event) {
     var i, begin, end, range, textNode, offset;
    
    // Internet Explorer
    if (document.body.createTextRange) {
       try {
         range = document.body.createTextRange();
         range.moveToPoint(event.clientX, event.clientY);
         range.select();
         range = getTextRangeBoundaryPosition(range, true);
      
         textNode = range.node;
         offset = range.offset;
       } catch(e) {
         return ""; // Sigh, IE
       }
    }
    
    // Firefox, Safari
    // REF: https://developer.mozilla.org/en-US/docs/Web/API/Document/caretPositionFromPoint
    else if (document.caretPositionFromPoint) {
      range = document.caretPositionFromPoint(event.clientX, event.clientY);
      textNode = range.offsetNode;
      offset = range.offset;

      // Chrome
      // REF: https://developer.mozilla.org/en-US/docs/Web/API/document/caretRangeFromPoint
    } else if (document.caretRangeFromPoint) {
      range = document.caretRangeFromPoint(event.clientX, event.clientY);
      textNode = range.startContainer;
      offset = range.startOffset;
    }

    // Only act on text nodes
    if (!textNode || textNode.nodeType !== Node.TEXT_NODE) {
      return "";
    }

    var data = textNode.textContent;

    // Sometimes the offset can be at the 'length' of the data.
    // It might be a bug with this 'experimental' feature
    // Compensate for this below
    if (offset >= data.length) {
      offset = data.length - 1;
    }

    // Ignore the cursor on spaces - these aren't words
    if (isW(data[offset])) {
      return "";
    }

    // Scan behind the current character until whitespace is found, or beginning
    i = begin = end = offset;
    while (i > 0 && !isW(data[i - 1])) {
      i--;
    }
    begin = i;

    // Scan ahead of the current character until whitespace is found, or end
    i = offset;
    while (i < data.length - 1 && !isW(data[i + 1])) {
      i++;
    }
    end = i;

    // This is our temporary word
    var word = data.substring(begin, end + 1);

    // Demo only
    showBridge(null, null, null);

    // If at a node boundary, cross over and see what 
    // the next word is and check if this should be added to our temp word
    if (end === data.length - 1 || begin === 0) {

      var nextNode = getNextNode(textNode);
      var prevNode = getPrevNode(textNode);

      // Get the next node text
      if (end == data.length - 1 && nextNode) {
        var nextText = nextNode.textContent;

        // Demo only
        showBridge(word, nextText, null);

        // Add the letters from the next text block until a whitespace, or end
        i = 0;
        while (i < nextText.length && !isW(nextText[i])) {
          word += nextText[i++];
        }

      } else if (begin === 0 && prevNode) {
        // Get the previous node text
        var prevText = prevNode.textContent;

        // Demo only
        showBridge(word, null, prevText);

        // Add the letters from the next text block until a whitespace, or end
        i = prevText.length - 1;
        while (i >= 0 && !isW(prevText[i])) {
          word = prevText[i--] + word;
        }
      }
    }
    return word;
  }

  // Return the word the cursor is over
  $hoverText.mousemove(function(e) {
    var word = getFullWord(e);
    if (word !== "") {
      $("#result").text(word);
    }
  });
});

// Helper functions

// Whitespace checker
function isW(s) {
  return /[ \f\n\r\t\v\u00A0\u2028\u2029]/.test(s);
}

// Barrier nodes are BR, DIV, P, PRE, TD, TR, ... 
function isBarrierNode(node) {
  return node ? /^(BR|DIV|P|PRE|TD|TR|TABLE)$/i.test(node.nodeName) : true;
}

// Try to find the next adjacent node
function getNextNode(node) {
  var n = null;
  // Does this node have a sibling?
  if (node.nextSibling) {
    n = node.nextSibling;

    // Doe this node's container have a sibling?
  } else if (node.parentNode && node.parentNode.nextSibling) {
    n = node.parentNode.nextSibling;
  }
  return isBarrierNode(n) ? null : n;
}

// Try to find the prev adjacent node
function getPrevNode(node) {
  var n = null;

  // Does this node have a sibling?
  if (node.previousSibling) {
    n = node.previousSibling;

    // Doe this node's container have a sibling?
  } else if (node.parentNode && node.parentNode.previousSibling) {
    n = node.parentNode.previousSibling;
  }
  return isBarrierNode(n) ? null : n;
}

// REF: http://stackoverflow.com/questions/3127369/how-to-get-selected-textnode-in-contenteditable-div-in-ie
function getChildIndex(node) {
  var i = 0;
  while( (node = node.previousSibling) ) {
    i++;
  }
  return i;
}

// All this code just to make this work with IE, OTL
// REF: http://stackoverflow.com/questions/3127369/how-to-get-selected-textnode-in-contenteditable-div-in-ie
function getTextRangeBoundaryPosition(textRange, isStart) {
  var workingRange = textRange.duplicate();
  workingRange.collapse(isStart);
  var containerElement = workingRange.parentElement();
  var workingNode = document.createElement("span");
  var comparison, workingComparisonType = isStart ?
    "StartToStart" : "StartToEnd";

  var boundaryPosition, boundaryNode;

  // Move the working range through the container's children, starting at
  // the end and working backwards, until the working range reaches or goes
  // past the boundary we're interested in
  do {
    containerElement.insertBefore(workingNode, workingNode.previousSibling);
    workingRange.moveToElementText(workingNode);
  } while ( (comparison = workingRange.compareEndPoints(
    workingComparisonType, textRange)) > 0 && workingNode.previousSibling);

  // We've now reached or gone past the boundary of the text range we're
  // interested in so have identified the node we want
  boundaryNode = workingNode.nextSibling;
  if (comparison == -1 && boundaryNode) {
    // This must be a data node (text, comment, cdata) since we've overshot.
    // The working range is collapsed at the start of the node containing
    // the text range's boundary, so we move the end of the working range
    // to the boundary point and measure the length of its text to get
    // the boundary's offset within the node
    workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange);

    boundaryPosition = {
      node: boundaryNode,
      offset: workingRange.text.length
    };
  } else {
    // We've hit the boundary exactly, so this must be an element
    boundaryPosition = {
      node: containerElement,
      offset: getChildIndex(workingNode)
    };
  }

  // Clean up
  workingNode.parentNode.removeChild(workingNode);

  return boundaryPosition;
}

// DEMO-ONLY code - this shows how the word is recombined across boundaries
function showBridge(word, nextText, prevText) {
  if (nextText) {
    $("#bridge").html("<span class=\"word\">" + word + "</span>  |  " + nextText.substring(0, 20) + "...").show();
  } else if (prevText) {
    $("#bridge").html("..." + prevText.substring(prevText.length - 20, prevText.length) + "  |  <span class=\"word\">" + word + "</span>").show();
  } else {
    $("#bridge").hide();
  }
}
.kinovar { color:red; font-size:20px;}.slavic { color: blue;}#result {top:10px;left:10px;}#bridge { top:10px; right:80px;}.floater { position: fixed; background-color:white; border:2px solid black; padding:4px;}.word { color:blue;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> <div id="bridge" class="floater"></div> <div id="result" class="floater"></div> <div id="hoverText"><p><span class="kinovar"><span id="selection_index3337" class="selection_index"></span>По f7-мъ часЁ твори1тъ сщ7eнникъ начaло съ кади1ломъ и3 со свэщeю, цrкимъ двeремъ tвeрзєннымъ, и3 поeтъ: Х</span>rт0съ воскRсе: <span class="kinovar">со стіхи2. И# по стісёхъ pал0мъ: Б</span>лгcви2 душE моS гDа: <span class="kinovar">И# є3ктеніA. Тaже каfjсма nбhчнаz.</span></p><div class="slavic"> <input value="Works around other tags!"><p><span id="selection_index3737" class="selection_index"></span>(л. рo7з њб.)</p><p><span class="kinovar"><span id="selection_index3738" class="selection_index"></span>Во вт0рникъ вeчера</span> </p><p><span class="kinovar"><span id="selection_index3739" class="selection_index"></span>tдaніе прaздника пaсхи.</span></p><p><span class="kinovar"><span id="selection_index3740" class="selection_index"></span>По f7-мъ часЁ твори1тъ сщ7eнникъ начaло съ кади1ломъ и3 со свэщeю, цrкимъ двeремъ tвeрзєннымъ, и3 поeтъ: Х</span>rт0съ воскRсе: <span class="kinovar">со стіхи2. И# по стісёхъ pал0мъ: Б</span>лгcви2 душE моS гDа: <span class="kinovar">И# є3ктеніA. Тaже каfjсма nбhчнаz.<input value="Works around inline tags too"></span></p><p><span class="kinovar"><span id="selection_index3741" class="selection_index"></span>На ГDи воззвaхъ: поeмъ стіхи6ры самоглaсны, слэпaгw, на ѕ7. Глaсъ в7:</span></p></div>

( Примітка: Я дозволив собі застосовувати стилі до тегів span, які були у вашому зразку HTML, щоб висвітлити, де знаходяться межі текстового вузла.)

Рішення 2 повнотекстова демонстрація

(Поки що працював у Chrome та IE. Для IE метод IERange повинен був використовуватися як прохідник для сумісності між браузерами)


У цьому слов’янському кодуванні {позначає наголос, тому я хотів би просто порахувати слово як усе, що знаходиться в пробілі, навіть справжні розділові знаки (оскільки я їх видалю сам). Відповідь технічно не відповідає щедрості, але якщо це найкраще вирішить проблему, я оберу.
user1122069 02

@ user1122069 Я опублікував друге рішення, набагато краще, яке використовує обхід DOM і теж працює в IE. Це швидко і розроблено, щоб бути надійним для майбутнього HTML. Мені подобаються обидва рішення, але тут не використовується перенесення тегів span, як ви просили.
Дрейкс

Дякую. Поки що працює ідеально. Я інкапсулював функції як об'єкт, щоб покращити його роботу з моїм додатком. jsfiddle.net/ohaf4ytL/1 Я думаю, це буде дуже корисно і для інших.
user1122069 04

11

Наскільки мені відомо, ви не можете.

Єдине, що я можу придумати, це помістити кожне зі слів у свій власний елемент, а потім застосувати до цих елементів курсор миші.

<p><span>Some</span> <span>long</span> <span>text</span></p>

<script>
$(document).ready(function () {
  $('p span').bind('mouseenter', function () {
    alert($(this).html() + " is what you're currently hovering over!");
  });
});
</script>

2
Ось демонстрація вищевказаного коду на jsfiddle: jsfiddle.net/5bT4B
Андерсон Грін

9

Ось просте рішення, яке працює в Chrome для більшості випадків:

function getWordAtPoint(x, y) {
  var range = document.caretRangeFromPoint(x, y);

  if (range.startContainer.nodeType === Node.TEXT_NODE) {
    range.expand('word');
    return range.toString().trim();
  }

  return null;
}

Я залишаю фільтрування пунктуації та належну обробку перенесених слів як вправу для читача :).


1
Якраз те, що мені потрібно для розширення Chrome.
chemamolins

@chemamolins Саме це мене спонукало придумати цей рецепт :).
erwaman

Координати x / y мають бути event.clientX, а не event.pageX. Якщо використовується pageX, caretRangeFromPoint () поверне нуль, коли сторінка прокручується, а миша знаходиться поза початковими координатами області перегляду.
tyshock

5

Існує API для цього в поточному проекті CSSOM View :document.caretPositionFromPoint(x,y)

Однак вам доведеться перевірити, який браузер це підтримує. Firefox 7, здається, не підтримує його взагалі, тоді як звіти про помилки вказують, що Firefox 9 буде. Chrome 14 підтримує те, caretRangeFromPoint(x,y)що по суті те саме, але зі старішої версії CSSOM.


Схоже, ваша відповідь влаштовує мою щедрість за проект. Потрібно трохи попрацювати, щоб насправді знайти слово, розширене з точки зору. Власний метод розширення діапазону працює недостатньо добре. Я можу дослідити це сам, але якщо ви можете надати код для роботи з моєю демонстрацією jsfiddle.net/ohaf4ytL , це було б чудово.
user1122069

@ user1122069 Я реалізував це рішення ( stackoverflow.com/a/30606508/2576706 ). Чи відповідає він вашим потребам?
Людовик Фельц

5

Ось рішення для щедрості.

Як запропонував chrisv, ви можете використовувати document.caretRangeFromPoint(chrome) або document.caretPositionFromPoint(Firefox). Я думаю, що це рішення краще відповість на ваше запитання, оскільки воно не змінює ваш текст або DOM.

Ця функція повертає слово під курсор миші, не змінюючи DOM:

З document.caretRangeFromPoint документації :

Метод caretRangeFromPoint () інтерфейсу документа повертає об'єкт Range для фрагмента документа під заданими координатами.

З document.caretPositionFromPoint документації :

Цей метод використовується для отримання позиції каретки в документі на основі двох координат. Повертається CaretPosition, що містить знайдений вузол DOM та зміщення символів у цьому вузлі.

Ці дві функції дещо відрізняються, але обидва вони повертають вузол, що містить текст, і зміщення курсора в цьому тексті. Тож легко отримати слово під мишею.

Дивіться повний приклад:

$(function () {
    function getWordUnderCursor(event) {
        var range, textNode, offset;

        if (document.body.createTextRange) {           // Internet Explorer
            try {
                range = document.body.createTextRange();
                range.moveToPoint(event.clientX, event.clientY);
                range.select();
                range = getTextRangeBoundaryPosition(range, true);
  
                textNode = range.node;
                offset = range.offset;
            } catch(e) {
                return "";
            }
        }
        else if (document.caretPositionFromPoint) {    // Firefox
            range = document.caretPositionFromPoint(event.clientX, event.clientY);
            textNode = range.offsetNode;
            offset = range.offset;
        } else if (document.caretRangeFromPoint) {     // Chrome
            range = document.caretRangeFromPoint(event.clientX, event.clientY);
            textNode = range.startContainer;
            offset = range.startOffset;
        }

        //data contains a full sentence
        //offset represent the cursor position in this sentence
        var data = textNode.data,
            i = offset,
            begin,
            end;

        //Find the begin of the word (space)
        while (i > 0 && data[i] !== " ") { --i; };
        begin = i;

        //Find the end of the word
        i = offset;
        while (i < data.length && data[i] !== " ") { ++i; };
        end = i;

        //Return the word under the mouse cursor
        return data.substring(begin, end);
    }

    //Get the HTML in a div #hoverText and detect mouse move on it
    var $hoverText = $("#hoverText");
    $hoverText.mousemove(function (e) {
        var word = getWordUnderCursor(e);
        
        //Show the word in a div so we can test the result
        if (word !== "") 
            $("#testResult").text(word);
    });
});

// This code make it works with IE
// REF: /programming/3127369/how-to-get-selected-textnode-in-contenteditable-div-in-ie
function getTextRangeBoundaryPosition(textRange, isStart) {
  var workingRange = textRange.duplicate();
  workingRange.collapse(isStart);
  var containerElement = workingRange.parentElement();
  var workingNode = document.createElement("span");
  var comparison, workingComparisonType = isStart ?
    "StartToStart" : "StartToEnd";

  var boundaryPosition, boundaryNode;

  // Move the working range through the container's children, starting at
  // the end and working backwards, until the working range reaches or goes
  // past the boundary we're interested in
  do {
    containerElement.insertBefore(workingNode, workingNode.previousSibling);
    workingRange.moveToElementText(workingNode);
  } while ( (comparison = workingRange.compareEndPoints(
    workingComparisonType, textRange)) > 0 && workingNode.previousSibling);

  // We've now reached or gone past the boundary of the text range we're
  // interested in so have identified the node we want
  boundaryNode = workingNode.nextSibling;
  if (comparison == -1 && boundaryNode) {
    // This must be a data node (text, comment, cdata) since we've overshot.
    // The working range is collapsed at the start of the node containing
    // the text range's boundary, so we move the end of the working range
    // to the boundary point and measure the length of its text to get
    // the boundary's offset within the node
    workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange);

    boundaryPosition = {
      node: boundaryNode,
      offset: workingRange.text.length
    };
  } else {
    // We've hit the boundary exactly, so this must be an element
    boundaryPosition = {
      node: containerElement,
      offset: getChildIndex(workingNode)
    };
  }

  // Clean up
  workingNode.parentNode.removeChild(workingNode);

  return boundaryPosition;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> 
<b><div id="testResult"></div></b>
<div id="hoverText">   <p><span class="kinovar"><span id="selection_index3337" class="selection_index"></span>По f7-мъ часЁ твори1тъ сщ7eнникъ начaло съ кади1ломъ и3 со свэщeю, цrкимъ двeремъ tвeрзєннымъ, и3 поeтъ: Х</span>rт0съ воскRсе: <span class="kinovar">со стіхи2. И# по стісёхъ pал0мъ: Б</span>лгcви2 душE моS гDа: <span class="kinovar">И# є3ктеніA. Тaже каfjсма nбhчнаz.</span> </p> <div class="slavic"><p><span id="selection_index3737" class="selection_index"></span>(л. рo7з њб.)</p> <p><span class="kinovar"><span id="selection_index3738" class="selection_index"></span>Во вт0рникъ вeчера</span></p> <p><span class="kinovar"><span id="selection_index3739" class="selection_index"></span>tдaніе прaздника пaсхи.</span></p><p><span class="kinovar"><span id="selection_index3740" class="selection_index"></span>По f7-мъ часЁ твори1тъ сщ7eнникъ начaло съ кади1ломъ и3 со свэщeю, цrкимъ двeремъ tвeрзєннымъ, и3 поeтъ: Х</span>rт0съ воскRсе: <span class="kinovar">состіхи2. И# по стісёхъ pал0мъ: Б</span>лгcви2 душE моS гDа: <span class="kinovar">И# є3ктеніA. Тaже каfjсма nбhчнаz.</span> </p><p><span class="kinovar"><span id="selection_index3741" class="selection_index"></span>На ГDи воззвaхъ: поeмъ стіхи6ры самоглaсны, слэпaгw, на ѕ7. Глaсъ в7:</span> </p><p><span class="kinovar"><span id="selection_index3742" class="selection_index"></span>С</span>лэпhй роди1выйсz, въ своeмъ п0мыслэ глаг0лаше: є3дA ѓзъ грBхъ рaди роди1тельныхъ роди1хсz без8 џчію; (л. рo7и) є3дA ѓзъ за невёріе kзhкwвъ роди1хсz во њбличeніе; не домышлsюсz вопрошaти: когдA н0щь, когдA дeнь; не терпи1та ми2 н0зэ кaменнагw претыкaніz, не ви1дэхъ сlнца сіsюща, нижE во џбразэ менE создaвшагw. но молю1 ти сz хrтE б9е, при1зри на мS, и3 поми1луй мS.</p></div></div>


"Отримайте HTML у #hoverText - просто обгортка для зручності" - здається знайомим
Дрейкс

@Drakes Я прочитав тут всю відповідь і знайшов вашу ідею обернути текст у div, тому я залишив те саме ім'я. Але код після цього зовсім інший :)
Людовик Фельц

@Drakes До речі, я знайшов ваше рішення дуже хорошим. Удачі вам за нагороду;)
Людовик Фельц

"textNode невизначений" з якихось причин в IE.
Чан,

Дякую Людовику. Якраз те, що мені потрібно, і це чудово працює. Я використовую його лише для Safari і помітив, що caretRangeFromPoint поверне найближчий діапазон, навіть якщо натиснути будь-яке порожнє місце на сторінці. Здається, це давня помилка: bugs.webkit.org/show_bug.cgi?id=29249
empedocle

3

Ах ти! Ось хо!

Як це просто, без жодної Jquery чи будь-якої іншої фреймворкової скрипки: https://jsfiddle.net/703c96dr/

Він помістить прольоти на кожне слово та додасть функцію onmouseover та onomouseout. Я міг би створити простий клас, щоб зробити його більш корисним, але код настільки простий, що кожен може редагувати та використовувати.

<p>This is my text example of word highlighting or, if you want, word hovering</p>
<p>This is another text example of word highlighting or, if you want, word hovering</p>

Простий код

function onmouseoverspan(){
    this.style.backgroundColor = "red";
}
function onmouseoutspan(){
    this.style.backgroundColor = "transparent";
}
var spans,p = document.getElementsByTagName("p");
for(var i=0;i<p.length;i++) {
    if(p[i]==undefined) continue;
    p[i].innerHTML = p[i].innerHTML.replace(/\b(\w+)\b/g, "<span>$1</span>");
    spans = p[i].getElementsByTagName("span")
    for(var a=0;a<spans.length;a++) {
        spans[a].onmouseover = onmouseoverspan;
        spans[a].onmouseout = onmouseoutspan;
    }
}

2

Можливо, вам доведеться розбити абзац так, щоб кожне слово містилося всередині свого окремого елемента <span>, а потім додавати onmouseoverатрибути подій до кожного з них.

..І я думаю, ви маєте на увазі "<p> якийсь довгий текст </p>"; зворотні скісні риски не є частиною HTML.


2

У Firefox ви можете підключити подію mousemove. Зворотний виклик має один аргумент, напр. У зворотному дзвінку зробіть наступне:

var range = HTTparent.ownerDocument.createRange();
range.selectNode(e.rangeParent);
var str = range.toString();
range.detach();

Тепер str має весь текст, над яким миша закінчилася. e.rangeOffset - це розташування вказівника миші всередині цього рядка. У вашому випадку str буде "довгим текстом", а e.rangeOffset буде 11, якщо ви перевищите "e" у "text".

Цей код трохи заплутається, якщо ви знаходитесь на полях, наприклад, коли вказівник миші знаходиться в тому самому рядку, що і текст, але після його закінчення. Щоб це виправити, потрібно перевірити, чи справді ви перебуваєте поверх тексту. Ось тест:

if(e && e.rangeParent && e.rangeParent.nodeType == e.rangeParent.TEXT_NODE
   && e.rangeParent.parentNode == e.target)

Ця техніка працює у Firefox. Не працює в Chrome.


0

function escapeHtml(unsafe) {
  return unsafe
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#039;");
}

// REF: http://stackoverflow.com/questions/3127369/how-to-get-selected-textnode-in-contenteditable-div-in-ie
function getChildIndex(node) {
  var i = 0;
  while( (node = node.previousSibling) ) {
    i++;
  }
  return i;
}

// All this code just to make this work with IE, OTL
// REF: http://stackoverflow.com/questions/3127369/how-to-get-selected-textnode-in-contenteditable-div-in-ie
function getTextRangeBoundaryPosition(textRange, isStart) {
  var workingRange = textRange.duplicate();
  workingRange.collapse(isStart);
  var containerElement = workingRange.parentElement();
  var workingNode = document.createElement("span");
  var comparison, workingComparisonType = isStart ?
    "StartToStart" : "StartToEnd";

  var boundaryPosition, boundaryNode;

  // Move the working range through the container's children, starting at
  // the end and working backwards, until the working range reaches or goes
  // past the boundary we're interested in
  do {
    containerElement.insertBefore(workingNode, workingNode.previousSibling);
    workingRange.moveToElementText(workingNode);
  } while ( (comparison = workingRange.compareEndPoints(
    workingComparisonType, textRange)) > 0 && workingNode.previousSibling);

  // We've now reached or gone past the boundary of the text range we're
  // interested in so have identified the node we want
  boundaryNode = workingNode.nextSibling;
  if (comparison == -1 && boundaryNode) {
    // This must be a data node (text, comment, cdata) since we've overshot.
    // The working range is collapsed at the start of the node containing
    // the text range's boundary, so we move the end of the working range
    // to the boundary point and measure the length of its text to get
    // the boundary's offset within the node
    workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange);

    boundaryPosition = {
      node: boundaryNode,
      offset: workingRange.text.length
    };
  } else {
    // We've hit the boundary exactly, so this must be an element
    boundaryPosition = {
      node: containerElement,
      offset: getChildIndex(workingNode)
    };
  }

  // Clean up
  workingNode.parentNode.removeChild(workingNode);

  return boundaryPosition;
}

function onClick(event) {
  var elt = document.getElementById('info');
  elt.innerHTML = "";
  var textNode;
  var offset;
  // Internet Explorer
  if (document.body.createTextRange) {
		  elt.innerHTML = elt.innerHTML+("*************** IE **************<br/>");
      range = document.body.createTextRange();
      range.moveToPoint(event.clientX, event.clientY);
      range.select();
      range = getTextRangeBoundaryPosition(range, true);

      textNode = range.node;
      offset = range.offset;
      elt.innerHTML = elt.innerHTML + "IE ok, result: [" + escapeHtml(textNode.nodeName) + "]/[" + escapeHtml(textNode.textContent) + "] @" + offset + "</br>";

  }
  
  // Internet Explorer method 2
  if (document.body.createTextRange) {
		  elt.innerHTML = elt.innerHTML+("*************** IE, Method 2 **************<br/>");
      range = document.body.createTextRange();
      range.moveToPoint(event.clientX, event.clientY);
      range.select();
			var sel = document.getSelection();
      textNode = sel.anchorNode;
      offset = sel.anchorOffset;
      elt.innerHTML = elt.innerHTML + "IE M2 ok, result: [" + escapeHtml(textNode.nodeName) + "]/[" + escapeHtml(textNode.textContent) + "] @" + offset + "</br>";
  }  

  // Firefox, Safari
  // REF: https://developer.mozilla.org/en-US/docs/Web/API/Document/caretPositionFromPoint
  if (document.caretPositionFromPoint) {
		  elt.innerHTML = elt.innerHTML+("*************** Firefox, Safari **************<br/>");  
    range = document.caretPositionFromPoint(event.clientX, event.clientY);
    textNode = range.offsetNode;
    offset = range.offset;
    elt.innerHTML = elt.innerHTML + "caretPositionFromPoint ok, result: [" + escapeHtml(textNode.nodeName) + "]/[" + escapeHtml(textNode.textContent) + "] @" + offset + "</br>";
    // Chrome
    // REF: https://developer.mozilla.org/en-US/docs/Web/API/document/caretRangeFromPoint
  }
  if (document.caretRangeFromPoint) {
		  elt.innerHTML = elt.innerHTML+("*************** Chrome **************<br/>");  
    range = document.caretRangeFromPoint(event.clientX, event.clientY);
    textNode = range.startContainer;
    offset = range.startOffset;
    elt.innerHTML = elt.innerHTML + "caretRangeFromPoint ok, result: [" + escapeHtml(textNode.nodeName) + "]/[" + escapeHtml(textNode.textContent) + "] @" + offset + "</br>";
  }
}

document.addEventListener('click', onClick);
#info {
  position: absolute;
  bottom: 0;
  background-color: cyan;
}
<div class="parent">
  <div class="child">SPACE&nbsp;SPACE Bacon ipsum dolor amet <span>SPAN SPANTT SPOOR</span> meatball bresaola t-bone tri-tip brisket. Jowl pig picanha cupim SPAXE landjaeger, frankfurter spare ribs chicken. Porchetta jowl pancetta drumstick shankle cow spare ribs jerky
    tail kevin biltong capicola brisket venison bresaola. Flank sirloin jowl andouille meatball venison salami ground round rump boudin turkey capicola t-bone. Sirloin filet mignon tenderloin beef, biltong doner bresaola brisket shoulder pork loin shankle
    turducken shank cow. Bacon ball tip sirloin ham.
  </div>
  <div id="info">Click somewhere in the paragraph above</div>
</div>

Моя відповідь випливає з Дрейка "Рішення 2 - Інспекція Каре і обхід DOM". Дякуємо Дрейку за вказівку на це рішення!

Однак є дві проблеми з рішенням Дрейкса 2 під час роботи над IE. (1) зсув, як обчислюється, неправильний, і (2) занадто складний, багато коду.

Подивіться мою демонстрацію на JSFiddle тут .

Для задачі 1, якщо ви клацнете десь приблизно в останньому рядку тексту, наприклад, десь у "плечі свиняча корейка шерстяний корінь коров’ячий корінь. Бекон куля нарізка філе шинка.", Ви можете помітити, що розрахунок зсуву відрізняється від IE (оригінал рішення) та IE метод 2 (моє рішення). Крім того, результати методу IE 2 (моє рішення) та Chrome, Firefox однакові.

Моє рішення також набагато простіше. Фокус полягає в тому, що після використання TextRange, щоб зробити вибір у абсолютній позиції X / Y, отримати тип IHTMLSelection, викликавши document.getSelection (). Це не працює для IE <9, але якщо для вас це нормально, цей метод набагато простіший. Іншим застереженням є те, що при IE побічним ефектом методу (таким же, як оригінальний метод) є зміна виділення (тобто втрата оригінального вибору користувача).

  // Internet Explorer method 2
  if (document.body.createTextRange) {
          elt.innerHTML = elt.innerHTML+("*************** IE, Method 2 **************<br/>");
      range = document.body.createTextRange();
      range.moveToPoint(event.clientX, event.clientY);
      range.select();
      var sel = document.getSelection();
      textNode = sel.anchorNode;
      offset = sel.anchorOffset;
      elt.innerHTML = elt.innerHTML + "IE M2 ok, result: [" + escapeHtml(textNode.nodeName) + "]/[" + escapeHtml(textNode.textContent) + "] @" + offset + "</br>";
  }  
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.