У меня есть файл HTML, и у меня есть предполагаемая позиция каретки. С JS мне нужно найти html-узел, на котором была бы размещена каретка, если бы она находилась в заданной позиции.
function findNodeForCaretPosition(caretPosition) {
var node = null; // type Node (https://developer.mozilla.org/en-US/docs/Web/API/Node)
// TODO find node
return node;
}
Пример HTML
<html>
<body>
<div>
<p id = "a">Time waits for no man. Unless that man is Chuck Norris.</p>
</div>
<div>
<p id = "b">Chuck Norris can touch <span id = "c" style = "color:blue">MC Hammer</span>.</p>
</div>
</body>
</html>
Пример обычного текста HTML
Time waits for no man. Unless that man is Chuck Norris.
Chuck Norris can touch MC Hammer.
Тесты
Позиция каретки = 4 (время | ожидания)
Ответ = <p id = "a">
Положение каретки = 16 (можно | коснуться).
Ответ = <p id = "b">
Положение каретки = 25 (MC | Молоток).
Ответ = <span id = "c">
Позиция каретки = 1000), нет ответа (null).
Какой вариант использования здесь? Зачем нужно такое отображение?
@TarunLalwani Мне нужно перенести локаторы чтения из одной библиотеки чтения epub в другую. Старый хранит место чтения как позицию каретки в обычном тексте. Новый хранит его в формате CFI (Canonical Fragment Identifier). Мне нужно сделать преобразование между ними. Для CFI мне нужно знать узел, который хотя бы приблизительно соответствует положению каретки. Есть право на ошибку. Мне позволено промахнуться на несколько узлов. Но в целом ответ Узел должен быть близок к положению каретки.
Что такое положение каретки относительно? Начало строки?
@jhpratt Да. В моем примере, если позиция каретки = 0, она находится прямо в начале строки, как здесь |Time waits.
Безопасно ли работать, предполагая, что это только текст, а не изображения/диаграммы/что-то еще?
@jhpratt на практике будут случаи с изображениями / диаграммами и т. д. Но наиболее распространенным случаем является то, что html будет содержать только текстовые элементы, поэтому да, вы можете работать с этим предположением. Если функция не может вернуть правильный узел, когда есть изображения/диаграммы - меня это устраивает.
Судя по всему, первый пример «порожден» первым div, а два последних — вторым? Если нет, то позиция каретки не имеет большого смысла с указанными вами числами.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Я довольно много прокомментировал код, поэтому не думаю, что он нуждается в дополнительных пояснениях. Просто вызовите caretPosition с родительским узлом и индексом в качестве параметров. Поскольку строки в JavaScript имеют кодировку UTF-16, любые смайлики или символы, отличные от ASCII, должен считаются одним символом, а не несколькими.
Следует отметить, что пробелы важны. Таким образом, HTML из вашего вопроса технически не будет работать, так как он сначала считает новые строки и начальные пробелы. Я удалил это здесь для простоты.
Если у вас есть какие-либо вопросы, дайте мне знать в комментариях.
/**
* @param {Element} parent
* @param {number} index
*/
function caretPosition(parent, index) {
// The index is too large to fit in the element, return `null` per requirements.
// We also return `null` if the element isn't a text or element node,
// as there is no text to check against.
if (
(parent.nodeType === Node.ELEMENT_NODE && index > parent.innerText.length)
|| (parent.nodeType === Node.TEXT_NODE && index > parent.data.length)
|| ![Node.ELEMENT_NODE, Node.TEXT_NODE].includes(parent.nodeType)
){
return null;
}
// The length of all text combined to this point
// (zero is the beginning of `parent`).
let combinedLength = 0;
// Iterate over the children until we find
// the element where we cross the boundry.
for (const child of parent.childNodes) {
// Store this in case we need to recurse.
const previousLength = combinedLength;
// For the current child, add the length of its text content.
// As text and element nodes don't share a common property,
// we need to explicitly check for both. Other node types
// (such as comments) are irrelevant to the task at hand.
if (child.nodeType === Node.TEXT_NODE) {
combinedLength += child.data.length;
} else if (child.nodeType === Node.ELEMENT_NODE) {
combinedLength += child.innerText.length;
} else {
// We don't have a text or element node,
// so there's no text that we could care about.
// The recursive case will handle the fact that nothing changed,
// and will return `null`.
continue;
}
// Our cursor is inside or at the end of this node.
if (index <= combinedLength) {
// We are in a text node and have nothing to recurse on.
// Return the parent element of the text node,
// which is a DOM element.
if (child.nodeType === Node.TEXT_NODE) {
return child.parentElement;
}
// If we are in an element node, then we have no children.
// Without children, there is nothing to recurse on;
// we should return the element.
// If we are _not_ in an element node,
// this will be `false`, and we will enter the recursive case.
else if (child.childElementCount === 0) {
return child;
}
// We have children to iterate over, so do that.
// It is necessary to change the index to search for
// as we've got a new reference frame.
return caretPosition(child, index - previousLength);
}
}
}
// Make sure everything works!
const div = document.querySelectorAll('div');
console.assert(caretPosition(div[0], 4) === document.querySelector('#a'));
console.assert(caretPosition(div[1], 16) === document.querySelector('#b'));
console.assert(caretPosition(div[1], 25) === document.querySelector('#c'));
console.assert(caretPosition(div[0], 1000) === null);
console.assert(caretPosition(div[1], 1000) === null);<div><p id='a'>Time waits for no man. Unless that man is Chuck Norris.</p></div>
<div><p id='b'>Chuck Norris can touch <span id='c' style='color:blue'>MC Hammer</span>.</p></div>
Вы можете использовать
NodeIteratorдля перебора всех текстовых узлов и подсчета их символов. Вы также можете использовать APISelection.