我有一个contenteditable div包含典型的所见即所得编辑器html(粗体,锚点,列表)。
我需要在div的开头和结尾确定当前光标是onKeyDown。原因是,基于光标位置和按下的键,我可能希望将此div与退格键上的前一个div合并,或者在输入时创建一个新的跟随div。
我一直在摆弄范围,但是当你在元素中使用html时,事情会变得非常复杂。
我希望我必须忽略一些简单的解决方案。
是否有一种相对简单的方法来确定这一点 - 我愿意使用像Rangy这样的库。
谢谢!
编辑:我正在考虑以下几点:
$('.mycontenteditable').bind('keydown', handle_keydown)
handle_keydown = function(e) {
range = window.getSelection().getRangeAt(0)
start_range = document.createRange()
start_range.selectNodeContents(this.firstChild)
start_range.collapse(true) // collapse to start
is_start = start_range.compareBoundaryPoints(Range.START_TO_START,range)
end_range = document.createRange()
end_range.selectNodeContents(this.lastChild)
end_range.collapse(false)
is_end = end_range.compareBoundaryPoints(Range.END_TO_END,range)
}
我会遇到类似这样的奇怪问题吗?
答案 0 :(得分:19)
除了使用toString()
对象而不是Range
的{{1}}方法以外,我会使用与您类似的方法来避免不必要的克隆。此外,在IE< 9(不支持范围),您可以使用cloneContents()
text
属性的类似方法。
请注意,当内容中存在前导和/或尾随换行符时,这会出现问题,因为范围的TextRange
方法就像节点的toString()
属性一样,只考虑文本节点,因此不考虑textContent
或块元素暗示的换行符。此外,还不考虑CSS:例如,包含通过<br>
隐藏的元素内的文本。
以下是一个例子:
现场演示:http://jsfiddle.net/YA3Pu/1/
代码:
display: none
答案 1 :(得分:15)
这就是我最终解决这个问题的方法。我上面提出的解决方案有时会工作但是有很多边缘情况,所以我最后考虑了光标前后有多少文本,如果是0个字符,那么我就在开始或结束时:
handle_keydown = function(e) {
// Get the current cusor position
range = window.getSelection().getRangeAt(0)
// Create a new range to deal with text before the cursor
pre_range = document.createRange();
// Have this range select the entire contents of the editable div
pre_range.selectNodeContents(this);
// Set the end point of this range to the start point of the cursor
pre_range.setEnd(range.startContainer, range.startOffset);
// Fetch the contents of this range (text before the cursor)
this_text = pre_range.cloneContents();
// If the text's length is 0, we're at the start of the div.
at_start = this_text.textContent.length === 0;
// Rinse and repeat for text after the cursor to determine if we're at the end.
post_range = document.createRange();
post_range.selectNodeContents(this);
post_range.setStart(range.endContainer, range.endOffset);
next_text = post_range.cloneContents();
at_end = next_text.textContent.length === 0;
}
仍然不完全确定还有其他任何边缘情况,因为我不完全确定如何对其进行单元测试,因为它需要鼠标交互 - 可能有一个库来处理这个问题。
答案 2 :(得分:1)
我想出了一种非常一致且简短的方法:
function isAtTextEnd() {
var sel = window.getSelection(),
offset = sel.focusOffset;
sel.modify ("move","forward","character");
if (offset == sel.focusOffset) return true;
else {
sel.modify ("move","backward","character");
return false;
}
}
关键:尝试强制将其向前移动一个字符-如果它确实移动了:
不是在结尾处(向后移动一个字符),如果没有的话-在结尾处(无需向后移动,它不会移动)。
文本开头的实现是相反的,并且是“留给读者练习” ...
空腔:
MDN将modify
标记为“非标准”
该表显示了相当广泛的支持(根据该表,经测试可在最新的Chrome和Firefox上运行-Edge不支持)。
我尝试使用受支持程度更高的extend()
-但是,看来奇怪的是,扩展确实起作用,即使在文本结尾也是如此。
如果您检查用户是否发起了插入符号的移动(例如,在键盘或鼠标事件处理程序中),则应处理这种检查迫使插入符号以意外方式移动的情况。
答案 3 :(得分:0)
我今天遇到了同样的问题,没有明确的解决方案,因此我开发了以下方法。它仅使用Selection
-不使用Range
或特定于供应商的功能。它还考虑了内容开头和结尾的换行符。
它可在当前的Chrome,Firefox,Safari和Opera中运行。
Microsoft Edge再次成为离群值,因为当内容的开头或结尾处有换行符时,文本选择本身在contenteditable
div
中被打断了。不幸的是,我还没有找到解决该问题的方法。
还值得注意的是,逻辑不仅在浏览器之间不同,而且在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>
答案 4 :(得分:0)
检查光标/插入符号是否在输入末尾的简单解决方案:
this.$('input').addEventListener('keydown', (e) => {
if (e.target.selectionEnd == e.target.value.length) {
// DO SOMETHING
}
})