91

このHTML要素があるとします。

<div id="parent">
 Hello everyone! <a>This is my home page</a>
 <p>Bye!</p>
</div>

そして、ユーザーはマウスで「ホーム」を選択します。

#parent彼の選択の開始文字数(および彼の選択の終了からの文字数)を判別できるようにしたいと思います#parent。これは、彼がHTMLタグを選択した場合でも機能するはずです。(そして私はそれがすべてのブラウザで動作する必要があります)

range.startOffset有望に見えますが、範囲の直接のコンテナに対してのみ相対的なオフセットであり、コンテナがテキストノードである場合にのみ文字オフセットです。

4

4 に答える 4

225

アップデート

コメントで指摘されているように、私の元の回答(以下)は、選択の最後またはキャレットの位置のみを返します。開始オフセットと終了オフセットを返すようにコードを適応させるのはかなり簡単です。これを行う例を次に示します。

function getSelectionCharacterOffsetWithin(element) {
    var start = 0;
    var end = 0;
    var doc = element.ownerDocument || element.document;
    var win = doc.defaultView || doc.parentWindow;
    var sel;
    if (typeof win.getSelection != "undefined") {
        sel = win.getSelection();
        if (sel.rangeCount > 0) {
            var range = win.getSelection().getRangeAt(0);
            var preCaretRange = range.cloneRange();
            preCaretRange.selectNodeContents(element);
            preCaretRange.setEnd(range.startContainer, range.startOffset);
            start = preCaretRange.toString().length;
            preCaretRange.setEnd(range.endContainer, range.endOffset);
            end = preCaretRange.toString().length;
        }
    } else if ( (sel = doc.selection) && sel.type != "Control") {
        var textRange = sel.createRange();
        var preCaretTextRange = doc.body.createTextRange();
        preCaretTextRange.moveToElementText(element);
        preCaretTextRange.setEndPoint("EndToStart", textRange);
        start = preCaretTextRange.text.length;
        preCaretTextRange.setEndPoint("EndToEnd", textRange);
        end = preCaretTextRange.text.length;
    }
    return { start: start, end: end };
}

function reportSelection() {
  var selOffsets = getSelectionCharacterOffsetWithin( document.getElementById("editor") );
  document.getElementById("selectionLog").innerHTML = "Selection offsets: " + selOffsets.start + ", " + selOffsets.end;
}

window.onload = function() {
  document.addEventListener("selectionchange", reportSelection, false);
  document.addEventListener("mouseup", reportSelection, false);
  document.addEventListener("mousedown", reportSelection, false);
  document.addEventListener("keyup", reportSelection, false);
};
#editor {
  padding: 5px;
  border: solid green 1px;
}
Select something in the content below:

<div id="editor" contenteditable="true">A <i>wombat</i> is a marsupial native to <b>Australia</b></div>
<div id="selectionLog"></div>

これは、指定された要素内のカレットの文字オフセットを取得する関数です。ただし、これは、ほぼ確実に改行との不整合があり、CSSを介して隠されたテキストを処理しようとしない単純な実装です(IEはそのようなテキストを正しく無視しますが、他のブラウザーは無視しないと思います)。これらすべてを適切に処理するには注意が必要です。私は今、私のRangyライブラリでそれを試みました。

実例: http: //jsfiddle.net/TjXEG/900/

function getCaretCharacterOffsetWithin(element) {
    var caretOffset = 0;
    var doc = element.ownerDocument || element.document;
    var win = doc.defaultView || doc.parentWindow;
    var sel;
    if (typeof win.getSelection != "undefined") {
        sel = win.getSelection();
        if (sel.rangeCount > 0) {
            var range = win.getSelection().getRangeAt(0);
            var preCaretRange = range.cloneRange();
            preCaretRange.selectNodeContents(element);
            preCaretRange.setEnd(range.endContainer, range.endOffset);
            caretOffset = preCaretRange.toString().length;
        }
    } else if ( (sel = doc.selection) && sel.type != "Control") {
        var textRange = sel.createRange();
        var preCaretTextRange = doc.body.createTextRange();
        preCaretTextRange.moveToElementText(element);
        preCaretTextRange.setEndPoint("EndToEnd", textRange);
        caretOffset = preCaretTextRange.text.length;
    }
    return caretOffset;
}
于 2011-01-27T01:08:06.240 に答える
27

これは1年前のことですが、この投稿は、Caretの位置を見つけるための多くの質問に対する上位の検索結果であり、これが役立つことがわかりました。

上記のTimの優れたスクリプトを使用して、コンテンツ編集可能なdiv内のある位置から別の位置に要素をドラッグドロップした後、新しいカーソル位置を見つけようとしていました。FFとIEでは完全に機能しましたが、Chromeでは、ドラッグアクションにより、ドラッグの開始から終了までのすべてのコンテンツが強調表示され、戻り値caretOffsetが大きすぎたり小さすぎたりしました(選択した領域の長さ)。

最初のifステートメントに数行を追加して、テキストが選択されているかどうかを確認し、それに応じて結果を調整しました。新しいステートメントは以下のとおりです。OPがやろうとしていたことではないので、ここにこれを追加するのが不適切な場合はご容赦ください。しかし、私が言ったように、Caretの位置に関連する情報を何度か検索すると、この投稿にたどり着きました。 。

行が追加されたTimの最初のifステートメント(*):

if (typeof window.getSelection != "undefined") {
  var range = window.getSelection().getRangeAt(0);
  var selected = range.toString().length; // *
  var preCaretRange = range.cloneRange();
  preCaretRange.selectNodeContents(element);
  preCaretRange.setEnd(range.endContainer, range.endOffset);

  caretOffset = preCaretRange.toString().length - selected; // *
}
于 2012-09-19T18:35:42.577 に答える
23

数日実験した後、私は有望に見えるアプローチを見つけました。selectNodeContents()タグを正しく処理しないため、内<br>のそれぞれのテキストの長さを決定するカスタムアルゴリズムを作成しました。たとえば、選択開始を計算するために、先行するすべてのノードのテキストの長さを合計します。そうすれば、(複数の)改行を処理できます。nodecontenteditable

var editor = null;
var output = null;

const getTextSelection = function (editor) {
    const selection = window.getSelection();

    if (selection != null && selection.rangeCount > 0) {
        const range = selection.getRangeAt(0);

        return {
            start: getTextLength(editor, range.startContainer, range.startOffset),
            end: getTextLength(editor, range.endContainer, range.endOffset)
        };
    } else
        return null;
}

const getTextLength = function (parent, node, offset) {
    var textLength = 0;

    if (node.nodeName == '#text')
        textLength += offset;
    else for (var i = 0; i < offset; i++)
        textLength += getNodeTextLength(node.childNodes[i]);

    if (node != parent)
        textLength += getTextLength(parent, node.parentNode, getNodeOffset(node));

    return textLength;
}

const getNodeTextLength = function (node) {
    var textLength = 0;

    if (node.nodeName == 'BR')
        textLength = 1;
    else if (node.nodeName == '#text')
        textLength = node.nodeValue.length;
    else if (node.childNodes != null)
        for (var i = 0; i < node.childNodes.length; i++)
            textLength += getNodeTextLength(node.childNodes[i]);

    return textLength;
}

const getNodeOffset = function (node) {
    return node == null ? -1 : 1 + getNodeOffset(node.previousSibling);
}

window.onload = function () {
    editor = document.querySelector('.editor');
    output = document.querySelector('#output');

    document.addEventListener('selectionchange', handleSelectionChange);
}

const handleSelectionChange = function () {
    if (isEditor(document.activeElement)) {
        const textSelection = getTextSelection(document.activeElement);

        if (textSelection != null) {
            const text = document.activeElement.innerText;
            const selection = text.slice(textSelection.start, textSelection.end);
            print(`Selection: [${selection}] (Start: ${textSelection.start}, End: ${textSelection.end})`);
        } else
            print('Selection is null!');
    } else
        print('Select some text above');
}

const isEditor = function (element) {
    return element != null && element.classList.contains('editor');
}

const print = function (message) {
    if (output != null)
        output.innerText = message;
    else
        console.log('output is null!');
}
* {
    font-family: 'Georgia', sans-serif;
    padding: 0;
    margin: 0;
}

body {
    margin: 16px;
}

.p {
    font-size: 16px;
    line-height: 24px;
    padding: 0 2px;
}

.editor {
    border: 1px solid #0000001e;
    border-radius: 2px;
    white-space: pre-wrap;
}

#output {
    margin-top: 16px;
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="./script.js" async></script>
    <link href="./stylesheet.css" rel="stylesheet">
    <title>Caret Position</title>
</head>
<body>
    <p class="editor" contenteditable="true"><em>Write<br></em><br>some <br>awesome <b><em>text </em></b>here...</p>
    <p id="output">Select some text above</p>
</body>
</html>

于 2019-01-24T17:33:22.370 に答える
1

このソリューションは、親コンテナに戻って歩いている前の兄弟のテキストコンテンツの長さをカウントすることによって機能します。あらゆる深さのネストされたタグを処理しますが、おそらくすべてのエッジケースをカバーしているわけではありませんが、同様のニーズがある場合は、開始するのに適したシンプルな場所です。

  calculateTotalOffset(node, offset) {
    let total = offset
    let curNode = node

    while (curNode.id != 'parent') {
      if(curNode.previousSibling) {
        total += curNode.previousSibling.textContent.length

        curNode = curNode.previousSibling
      } else {
        curNode = curNode.parentElement
      }
    }

   return total
 }

 // after selection

let start = calculateTotalOffset(range.startContainer, range.startOffset)
let end = calculateTotalOffset(range.endContainer, range.endOffset)
于 2020-06-16T20:30:58.767 に答える