У меня была та же проблема сегодня без чистого решения, поэтому я разработал следующий подход.Он использует только Selection
- нет Range
или специфичные для поставщика функции.Он также учитывает переводы строк в начале и конце содержимого.
Работает в современных Chrome, Firefox, Safari и Opera.Microsoft Edge снова является выбросом, поскольку само выделение текста частично прерывается в contenteditable
div
s, когда в начале или конце содержимого появляются новые строки.К сожалению, я еще не нашел обходной путь для этой проблемы.
Стоит также отметить, что логика отличается не только между браузерами, но и между режимами white-space
(normal
против pre*
), потому чтобраузер будет генерировать разные узлы для каждого при наборе.
document.addEventListener("selectionchange", function() {
updateCaretInfo(document.getElementById('input-normal'))
updateCaretInfo(document.getElementById('input-pre'))
});
function updateCaretInfo(input) {
function isAcceptableNode(node, side) {
if (node === input) { return true }
const childProperty = side === 'start' ? 'firstChild' : 'lastChild'
while (node && node.parentNode && node.parentNode[childProperty] === node) {
if (node.parentNode === input) {
return true
}
node = node.parentNode
}
return false
}
function isAcceptableOffset(offset, node, side) {
if (side === 'start') {
return offset === 0
}
if (node.nodeType === Node.TEXT_NODE) {
return offset >= node.textContent.replace(/\n$/, '').length
}
else {
return offset >= node.childNodes.length - 1
}
}
function isAcceptableSelection(selection, side) {
return selection &&
selection.isCollapsed &&
isAcceptableNode(selection.anchorNode, side) &&
isAcceptableOffset(selection.anchorOffset, selection.anchorNode, side)
}
const selection = document.getSelection()
const isAtStart = isAcceptableSelection(selection, 'start')
const isAtEnd = isAcceptableSelection(selection, 'end')
document.getElementById('start-' + input.id).innerText = isAtStart ? 'YES' : 'no'
document.getElementById('end-' + input.id).innerText = isAtEnd ? 'YES' : 'no'
}
body {
padding: 10px;
}
[id^="input-"] {
border: 1px solid black;
display: inline-block;
margin-bottom: 10px;
padding: 5px;
}
<div contenteditable id="input-normal">Move the caret inside here!</div>
(<code>white-space: normal</code>)
<p>
Caret at start: <span id="start-input-normal">no</span><br>
Caret at end: <span id="end-input-normal">no</span>
</p>
<hr>
<div contenteditable id="input-pre" style="white-space: pre-wrap">Move the caret inside here!</div>
(<code>white-space: pre-wrap</code>)
<p>
Caret at start: <span id="start-input-pre">no</span><br>
Caret at end: <span id="end-input-pre">no</span>
</p>