Получить смещение начала и конца диапазона относительно его родительского контейнера
Предположим, что у меня есть этот элемент HTML:
<div id="parent">
Hello everyone! <a>This is my home page</a>
<p>Bye!</p>
</div>
И пользователь выбирает "дом" с помощью мыши.
Я хочу, чтобы определить, сколько символов в #parent
начинается его выбор (и сколько символов в конце #parent
заканчивается). Это должно работать, даже если он выберет HTML-тег. (И мне нужно, чтобы он работал во всех браузерах)
range.startOffset
выглядит многообещающим, но это смещение относительно только ближайшего контейнера диапазона и является смещением символа, только если контейнер это текст node.
Ответы
Ответ 1
UPDATE
Как указано в комментариях, мой первоначальный ответ (ниже) возвращает только конец выбора или позицию каретки. Это довольно легко адаптировать код, чтобы вернуть начало и конец смещения; вот пример, который делает так:
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>
Ответ 2
Я знаю, что это год, но этот пост является лучшим результатом поиска для многих вопросов по поиску позиции Caret, и я нашел это полезным.
Я попытался использовать Тим отличный script выше, чтобы найти новую позицию курсора после перетаскивания элемента из одной позиции в другую в редактируемом содержимом div. Он отлично работал в FF и IE, но в Chrome действие перетаскивания выделило все содержимое между началом и концом перетаскивания, что привело к тому, что возвращаемый caretOffset
был слишком большим или маленьким (по длине выделенной области).
Я добавил несколько строк в первый оператор if, чтобы проверить, был ли выбран текст, и соответствующим образом скорректировать результат. Новое утверждение приведено ниже. Простите меня, если неуместно добавить это здесь, поскольку это не то, что пытался сделать OP, но, как я уже сказал, несколько поисков по информации, связанной с положением Каре, привели меня к этому сообщению, поэтому он (надеюсь), скорее всего, поможет кому-то другому.
Тим первым, если оператор с добавленными строками (*):
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);
if(selected){ // *
caretOffset = preCaretRange.toString().length - selected; // *
} else { // *
caretOffset = preCaretRange.toString().length;
} // *
}
Ответ 3
После нескольких дней экспериментов я нашел подход, который выглядит многообещающим. Поскольку selectNodeContents()
неправильно обрабатывает теги <br>
, я написал собственный алгоритм для определения длины текста каждого node
внутри contenteditable
. Для вычисления, например, начала выбора, я суммирую длину текста всех предыдущих узлов. Таким образом, я могу обрабатывать (несколько) разрывов строк:
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>