ⓘ
在 contentEditable 的 HTML div/body元素中,如何判断当前光标所在的位置是开头(Ctrl+Home)还是在末尾(Ctrl + End)?
Contenteditable DIV - how can I determine if the cursor is at the start or end of the content
如下面的方法,无法对某些复杂DIV内部,有新行的时候,在新行前面得到的位置为0,和在页面顶部得到的pos = 0 一样:
const cursorPosition = () => {
const sel = document.getSelection();
sel.modify("extend", "backward", "paragraphboundary");
const pos = sel.toString().length;
if (sel.anchorNode != undefined) sel.collapseToEnd();
return pos;
}
网上有一些解决方法,但是或多或少有一些问题,判断不准确,下面是一个比较完备的判断方法,供参考。
function isAtHome(el) {
selection = window.getSelection();
if (selection.focusOffset != 0) return false;
range = selection.getRangeAt(0);
tr = document.createRange();
tr.setStart(el.firstChild, 0);
tr.setEnd(selection.focusNode, selection.focusOffset);
return tr.cloneContents().textContent.trim() == ""
}
function isAtEnd(el) {
var selection = window.getSelection(),
range = selection.getRangeAt(0);
tr = document.createRange();
tr.setStart(selection.focusNode, selection.focusOffset);
tr.setEnd(el, el.childNodes.length);
if (tr.cloneContents().textContent.trim() != "") return false;
offset = selection.focusOffset;
selection.modify("move", "forward", "character");
if (offset == selection.focusOffset) {
return true
}
selection.modify("move", "backward", "character");
return false
}
使用方法示例,下面的代码,可以在文档开头按回车的时候,自动插入一个新的DIV,防止某些情况下,例如表格插在开头后,就无法插入新的DIV的问题:
function addNewDiv(el, first) {
var newDiv = document.createElement('div');
newDiv.innerHTML = '<br>';
if (first) {
el.insertBefore(newDiv, el.firstChild)
} else {
el.appendChild(newDiv)
}
var selection = window.getSelection();
var range = selection.getRangeAt(0);
range.setStart(newDiv.firstChild, 0);
range.collapse(true);
selection.removeAllRanges();
selection.addRange(range);
event.preventDefault()
}
document.body.addEventListener('keydown', function(event) {
if (event.key === 'Enter' && !event.shiftKey) {
if (isAtHome(this)) {
addNewDiv(this, true)
} else if (isAtEnd(this)) {
addNewDiv(this, false)
}
}
}
此外下面的方法也是比较有效的方法:
<html>
<body>
<div contenteditable id="edit"><br/></div>
<script>
document.addEventListener("selectionchange", function() {
updateCaretInfo(document.getElementById('edit'))
});
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')
console.log("Start: ", isAtStart)
console.log("End: ", isAtEnd)
}
</script>
</body>
</html>
参考资料: