web-dev-qa-db-ja.com

JavaScriptを使用してカーソルの下にWordを取得する方法は?

たとえば私が持っている場合

<p> some long text </p>

hTMLページで、たとえばマウスのカーソルがWordの「テキスト」の上にあることを確認するにはどうすればよいですか?

70
Ivan

他の2つの答えに加えて、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の例

40
Damovisa

私の他の答えは、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);
}    
_

Mousemoveハンドラーで、getWordAtPoint(e.target, e.x, e.y);を呼び出します

38
Eyal

前文:

単語(または単語内の文字)を分離する複数のスパンとネストされたHTMLがある場合、上記のすべてのソリューションでは、完全で正しいWordを返すのに問題があります。

賞金の質問の例:_Х</span>rт0съ_。 _Хrт0съ_を適切に返す方法は?これらの問題は2010年には対処されていなかったため、2つのソリューション(2015年)を提示します。


解決策1-内部タグを削除し、完全な各Wordをスパンします。

1つの解決策は、段落内のspanタグを取り除き、テキストを保持することです。したがって、分割された単語やフレーズは、通常のテキストとして結合されます。各単語は、空白だけでなく(スペースだけでなく)分割され、それらの単語は、個別にアクセスできるスパンにラップされます。

デモでは、Word全体を強調表示して、Word全体のテキストを取得できます。


pic 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トラバーサル:

これは、より洗練されたソリューションです。これは、ノードトラバーサルを使用したアルゴリズムソリューションであり、テキストノードのカーソルの下にある完全で正しいWordを正確にキャプチャします。

キャレットの位置を確認することにより、一時的なWordが見つかります(caretPositionFromPointまたはcaretRangeFromPointを使用して、@ chrisvにアイデアをクレジットします)。これはまだ完全なWordかもしれませんし、そうでないかもしれません。

次に、テキストノードのどちらかのエッジ(開始または終了)にあるかどうかを確認するために分析されます。存在する場合、前のテキストノードまたは次のテキストノードを調べて、このWordフラグメントを長くするために結合する必要があるかどうかを確認します。

例:

_Х</span>rт0съ_は、_Хrт0съ_や_Х_ではなく、_rт0съ_を返す必要があります。

DOMツリーを走査して、次の非バリアテキストノードを取得します。 2つのWordフラグメントが_<p>_またはその他のバリアタグで区切られている場合、それらは隣接していないため、同じWordの一部ではありません。

例:

_њб.)</p><p>Во_は_њб.)Во_を返すべきではありません


デモでは、左のフローティングdivはカーソルの下のWordです。表示されている場合、右側のフローティングdivは、境界上のワードがどのように形成されたかを示します。他のタグは、このソリューションのテキストで安全にインライン化できます。

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

注:テキストノードの境界がどこにあるかを示すために、サンプルHTMLに存在するspanタグにスタイルを適用する自由を取りました。)

ソリューション2フルテキストデモ

(ChromeとIEこれまでのところ。IEの場合、 IERange のメソッドをcrossのシムとして使用する必要がありました。 -ブラウザの互換性)

34
Drakes

私の知る限り、できません。

私が考えることができるのは、各単語を独自の要素に入れてから、マウスオーバーイベントをそれらの要素に適用することだけです。

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

現在の CSSOM Viewドラフトdocument.caretPositionFromPoint(x,y)には、このためのAPIがあります

ただし、どのブラウザがこれをサポートしているかを確認する必要があります。 Firefox 7はそれをまったくサポートしていないようですが、バグレポートはFirefox 9がサポートすることを示しています。 Chrome 14はcaretRangeFromPoint(x,y)をサポートしています。これは基本的に同じですが、古いCSSOMドラフトからのものです。

5
chrisv

これが賞金の解決策です。

chrisv で示唆されているように、document.caretRangeFromPoint(クローム)またはdocument.caretPositionFromPoint(Firefox)。この解決策は、テキストやDOMを変更しないので、あなたの質問によりよく答えると思います。

この関数は、DOMを変更せずに、マウスカーソルの下にWordを返します。

から document.caretRangeFromPointドキュメント

DocumentインターフェイスのcaretRangeFromPoint()メソッドは、指定された座標の下でドキュメントフラグメントのRangeオブジェクトを返します。

から document.caretPositionFromPointドキュメント

このメソッドは、2つの座標に基づいてドキュメント内のキャレット位置を取得するために使用されます。見つかったDOMノードとそのノードの文字オフセットを含むCaretPositionが返されます。

2つの関数はわずかに異なりますが、どちらもテキストを含むノードとこのテキスト内のカーソルのオフセットを返します。したがって、Wordをマウスの下に置くのは簡単です。

完全な例を参照してください。

$(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: https://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;
}
<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>
5
Ludovic Feltz

ほとんどの場合、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;
}

読者への演習として、句読点を除外し、ハイフン付きの単語を適切に処理します。

4
erwaman

ああ!ほらほら!

そのままでJqueryまたはその他のフレームワークFiddleなし: https://jsfiddle.net/703c96dr/

各Wordにスパンを設定し、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;
    }
}
3

各Wordが独自の個別の<span>要素内に含まれるように段落を分割し、それぞれにonmouseoverイベント属性を追加する必要があるでしょう。

..そして、「<p> some long text </ p>」という意味だと思います。バックスラッシュはHTMLの一部ではありません。

2
amphetamachine

Firefoxでは、mousemoveイベントをフックできます。コールバックには1つの引数eがあります。コールバックで、これを行います:

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

これで、strにはマウスが上にあったテキスト全体が含まれます。 e.rangeOffsetは、その文字列内のマウスポインターの位置です。あなたの場合、strは「いくつかの長いテキスト」であり、「text」の「e」を超えている場合、e.rangeOffsetは11になります。

たとえば、マウスポインターがテキストと同じ行にあるが、テキストの終わり以降にある場合、マージンにいると、このコードは少し混乱します。これを修正するには、実際にテキストの上にいることを確認する必要があります。テストは次のとおりです。

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

この手法はFirefoxで機能します。 Chromeでは機能しません。

1
Eyal
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トラバーサル」に由来しています。このソリューションを指摘してくれたDrakesに感謝します!

ただし、IEで作業する場合、Drakesのソリューション2には2つの問題があります。 (1)計算されたオフセットが正しくない、(2)複雑すぎる、大量のコード。

here のJSFiddleでのデモをご覧ください。

問題1では、テキストの最後の行のどこか、たとえば「肩ロース肉のシャンクルターダッケンシャンク牛。ベーコンボールチップサーロインハム」のどこかをクリックすると、オフセット計算がIE(元のソリューション)およびIE方法2(私のソリューション)。また、IEメソッド2(私のソリューション)とChrome、Firefoxの結果は同じです。

私の解決策もはるかに簡単です。秘Theは、TextRangeを使用して絶対X/Y位置で選択を行った後、document.getSelection()を呼び出してIHTMLSelectionのタイプを取得することです。これはIE <9では機能しませんが、それで問題なければ、この方法ははるかに簡単です。もう1つの注意点は、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>";
  }  
0
Bing Ren