我找到了很多关于如何在contentEditable DIV中设置光标或插入位置的好的,交叉浏览器的答案,但没有关于如何获取或找到它的位置......
我想要做的是知道该div中的插入符号在键盘上的位置。
因此,当用户输入文本时,我可以随时知道其光标在div中的位置。
编辑:我在div内容(文本)中寻找INDEX,而不是光标坐标。
<div id="contentBox" contentEditable="true"></div>
$('#contentbox').keyup(function() {
// ... ?
});
答案 0 :(得分:102)
以下代码假定:
<div>
中只有一个文本节点,没有其他节点white-space
属性设置为pre
代码:
function getCaretPosition(editableDiv) {
var caretPos = 0,
sel, range;
if (window.getSelection) {
sel = window.getSelection();
if (sel.rangeCount) {
range = sel.getRangeAt(0);
if (range.commonAncestorContainer.parentNode == editableDiv) {
caretPos = range.endOffset;
}
}
} else if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
if (range.parentElement() == editableDiv) {
var tempEl = document.createElement("span");
editableDiv.insertBefore(tempEl, editableDiv.firstChild);
var tempRange = range.duplicate();
tempRange.moveToElementText(tempEl);
tempRange.setEndPoint("EndToEnd", range);
caretPos = tempRange.text.length;
}
}
return caretPos;
}
#caretposition {
font-weight: bold;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="contentbox" contenteditable="true">Click me and move cursor with keys or mouse</div>
<div id="caretposition">0</div>
<script>
var update = function() {
$('#caretposition').html(getCaretPosition(this));
};
$('#contentbox').on("mousedown mouseup keydown keyup", update);
</script>
答案 1 :(得分:15)
$("#editable").on('keydown keyup mousedown mouseup',function(e){
if($(window.getSelection().anchorNode).is($(this))){
$('#position').html('0')
}else{
$('#position').html(window.getSelection().anchorOffset);
}
});
&#13;
body{
padding:40px;
}
#editable{
height:50px;
width:400px;
border:1px solid #000;
}
#editable p{
margin:0;
padding:0;
}
&#13;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.1/jquery.min.js"></script>
<div contenteditable="true" id="editable">move the cursor to see position</div>
<div>
position : <span id="position"></span>
</div>
&#13;
答案 2 :(得分:13)
答案 3 :(得分:7)
在其他答案中我看不到的一些皱纹:
这是一种获取开始位置和结束位置作为元素的textContent值的偏移量的方法:
// node_walk: walk the element tree, stop when func(node) returns false
function node_walk(node, func) {
var result = func(node);
for(node = node.firstChild; result !== false && node; node = node.nextSibling)
result = node_walk(node, func);
return result;
};
// getCaretPosition: return [start, end] as offsets to elem.textContent that
// correspond to the selected portion of text
// (if start == end, caret is at given position and no text is selected)
function getCaretPosition(elem) {
var sel = window.getSelection();
var cum_length = [0, 0];
if(sel.anchorNode == elem)
cum_length = [sel.anchorOffset, sel.extentOffset];
else {
var nodes_to_find = [sel.anchorNode, sel.extentNode];
if(!elem.contains(sel.anchorNode) || !elem.contains(sel.extentNode))
return undefined;
else {
var found = [0,0];
var i;
node_walk(elem, function(node) {
for(i = 0; i < 2; i++) {
if(node == nodes_to_find[i]) {
found[i] = true;
if(found[i == 0 ? 1 : 0])
return false; // all done
}
}
if(node.textContent && !node.firstChild) {
for(i = 0; i < 2; i++) {
if(!found[i])
cum_length[i] += node.textContent.length;
}
}
});
cum_length[0] += sel.anchorOffset;
cum_length[1] += sel.extentOffset;
}
}
if(cum_length[0] <= cum_length[1])
return cum_length;
return [cum_length[1], cum_length[0]];
}
答案 4 :(得分:5)
金达晚了聚会,但万一其他人都在挣扎。在过去两天中,我发现的所有Google搜索都没有找到任何可行的方法,但我提出了一种简洁而优雅的解决方案,无论您拥有多少个嵌套标签,该解决方案都将始终有效:
cursor_position() {
var sel = document.getSelection();
sel.modify("extend", "backward", "paragraphboundary");
var pos = sel.toString().length;
console.log('pos: '+pos);
if(sel.anchorNode != undefined) sel.collapseToEnd();
return pos;
}
它一直选择到段落的开头,然后计算字符串的长度以获取当前位置,然后撤消选择以将光标返回到当前位置。如果要对整个文档(一个以上的段落)执行此操作,则将paragraphboundary
更改为documentboundary
或根据您的情况选择任何粒度。检出more details的API。干杯! :)
答案 5 :(得分:4)
//global savedrange variable to store text range in
var savedrange = null;
function getSelection()
{
var savedRange;
if(window.getSelection && window.getSelection().rangeCount > 0) //FF,Chrome,Opera,Safari,IE9+
{
savedRange = window.getSelection().getRangeAt(0).cloneRange();
}
else if(document.selection)//IE 8 and lower
{
savedRange = document.selection.createRange();
}
return savedRange;
}
$('#contentbox').keyup(function() {
var currentRange = getSelection();
if(window.getSelection)
{
//do stuff with standards based object
}
else if(document.selection)
{
//do stuff with microsoft object (ie8 and lower)
}
});
注意:范围对象的self可以存储在变量中,并且可以随时重新选择,除非contenteditable div的内容发生变化。
IE 8及更低版本的参考: http://msdn.microsoft.com/en-us/library/ms535872(VS.85).aspx
标准(所有其他)浏览器的参考: https://developer.mozilla.org/en/DOM/range(它的mozilla文档,但代码也适用于chrome,safari,opera和ie9)
答案 6 :(得分:4)
function getCaretPosition() {
var x = 0;
var y = 0;
var sel = window.getSelection();
if(sel.rangeCount) {
var range = sel.getRangeAt(0).cloneRange();
if(range.getClientRects()) {
range.collapse(true);
var rect = range.getClientRects()[0];
if(rect) {
y = rect.top;
x = rect.left;
}
}
}
return {
x: x,
y: y
};
}
答案 7 :(得分:3)
这个适用于我:
cashtoken
调用行取决于事件类型,对于键事件使用:
function getCaretCharOffsetInDiv(element) {
var caretOffset = 0;
if (typeof window.getSelection != "undefined") {
var range = window.getSelection().getRangeAt(0);
var preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(element);
preCaretRange.setEnd(range.endContainer, range.endOffset);
caretOffset = preCaretRange.toString().length;
}
else if (typeof document.selection != "undefined" && document.selection.type != "Control")
{
var textRange = document.selection.createRange();
var preCaretTextRange = document.body.createTextRange();
preCaretTextRange.moveToElementText(element);
preCaretTextRange.setEndPoint("EndToEnd", textRange);
caretOffset = preCaretTextRange.text.length;
}
return caretOffset;
}
用于鼠标事件:
getCaretCharOffsetInDiv(e.target) + ($(window.getSelection().getRangeAt(0).startContainer.parentNode).index());
在这两种情况下,我通过添加目标索引
来处理断行答案 8 :(得分:1)
这使我永远无法使用新的window.getSelection API,我将分享给后代。请注意,MDN建议对window.getSelection提供更广泛的支持,但是,您的里程可能会有所不同。
const getSelectionCaretAndLine = () => {
// our editable div
const editable = document.getElementById('editable');
// collapse selection to end
window.getSelection().collapseToEnd();
const sel = window.getSelection();
const range = sel.getRangeAt(0);
// get anchor node if startContainer parent is editable
let selectedNode = editable === range.startContainer.parentNode
? sel.anchorNode
: range.startContainer.parentNode;
if (!selectedNode) {
return {
caret: -1,
line: -1,
};
}
// in case there is nested doms inside editable
while(selectedNode.parentNode !== editable) {
selectedNode = selectedNode.parentNode;
}
// select to top of editable
range.setStart(editable.firstChild, 0);
// do not use 'this' sel anymore since the selection has changed
const content = window.getSelection().toString();
const text = JSON.stringify(content);
const lines = (text.match(/\\n/g) || []).length + 1;
// clear selection
window.getSelection().collapseToEnd();
// minus 2 because of strange text formatting
return {
caret: text.length - 2,
line: lines,
}
}
这是一个jsfiddle,可在键盘启动时触发。但是请注意,快速方向按键以及快速删除似乎是跳过事件。
答案 9 :(得分:0)
一种直接的方法,它遍历contenteditable div的所有子项,直到到达endContainer。然后,我添加结束容器偏移量,然后得到字符索引。应该与任何数量的嵌套一起使用。使用递归。
注意:需要JSON Request API / Parameters Mapping才能支持Element.closest('div[contenteditable]')
function caretPositionIndex() {
const range = window.getSelection().getRangeAt(0);
const { endContainer, endOffset } = range;
// get contenteditableDiv from our endContainer node
let contenteditableDiv;
const contenteditableSelector = "div[contenteditable]";
switch (endContainer.nodeType) {
case Node.TEXT_NODE:
contenteditableDiv = endContainer.parentElement.closest(contenteditableSelector);
break;
case Node.ELEMENT_NODE:
contenteditableDiv = endContainer.closest(contenteditableSelector);
break;
}
if (!contenteditableDiv) return '';
const countBeforeEnd = countUntilEndContainer(contenteditableDiv, endContainer);
if (countBeforeEnd.error ) return null;
return countBeforeEnd.count + endOffset;
function countUntilEndContainer(parent, endNode, countingState = {count: 0}) {
for (let node of parent.childNodes) {
if (countingState.done) break;
if (node === endNode) {
countingState.done = true;
return countingState;
}
if (node.nodeType === Node.TEXT_NODE) {
countingState.count += node.length;
} else if (node.nodeType === Node.ELEMENT_NODE) {
countUntilEndContainer(node, endNode, countingState);
} else {
countingState.error = true;
}
}
return countingState;
}
}
答案 10 :(得分:0)
如果你将可编辑的 div 样式设置为 "display:inline-block; white-space: pre-wrap" 你在输入新行时不会得到新的子 div,你只会得到 LF 字符(即 10);.
function showCursPos(){
selection = document.getSelection();
childOffset = selection.focusOffset;
const range = document.createRange();
eDiv = document.getElementById("eDiv");
range.setStart(eDiv, 0);
range.setEnd(selection.focusNode, childOffset);
var sHtml = range.toString();
p = sHtml.length;
sHtml=sHtml.replace(/(\r)/gm, "\\r");
sHtml=sHtml.replace(/(\n)/gm, "\\n");
document.getElementById("caretPosHtml").value=p;
document.getElementById("exHtml").value=sHtml;
}
click/type in div below:
<br>
<div contenteditable name="eDiv" id="eDiv"
onkeyup="showCursPos()" onclick="showCursPos()"
style="width: 10em; border: 1px solid; display:inline-block; white-space: pre-wrap; "
>123 456 789</div>
<p>
html caret position:<br> <input type="text" id="caretPosHtml">
<p>
html from start of div:<br> <input type="text" id="exHtml">
我注意到当您在可编辑 div 中按“enter”键时,它会创建一个新节点,因此 focusOffset 重置为零。这就是为什么我必须添加一个范围变量,并将其从子节点的 focusOffset 扩展回 eDiv 的开头(从而捕获其间的所有文本)。
答案 11 :(得分:0)
这个建立在@alockwood05 的答案之上,并为在 contenteditable div 内带有嵌套标签的插入符号以及节点内的偏移量提供 get 和 set 功能,以便您拥有一个既可通过偏移量序列化又可反序列化的解决方案
我在跨平台代码编辑器中使用此解决方案,需要在通过词法分析器/解析器突出显示语法之前获取插入符号开始/结束位置,然后立即将其设置回来。
function countUntilEndContainer(parent, endNode, offset, countingState = {count: 0}) {
for (let node of parent.childNodes) {
if (countingState.done) break;
if (node === endNode) {
countingState.done = true;
countingState.offsetInNode = offset;
return countingState;
}
if (node.nodeType === Node.TEXT_NODE) {
countingState.offsetInNode = offset;
countingState.count += node.length;
} else if (node.nodeType === Node.ELEMENT_NODE) {
countUntilEndContainer(node, endNode, offset, countingState);
} else {
countingState.error = true;
}
}
return countingState;
}
function countUntilOffset(parent, offset, countingState = {count: 0}) {
for (let node of parent.childNodes) {
if (countingState.done) break;
if (node.nodeType === Node.TEXT_NODE) {
if (countingState.count <= offset && offset < countingState.count + node.length)
{
countingState.offsetInNode = offset - countingState.count;
countingState.node = node;
countingState.done = true;
return countingState;
}
else {
countingState.count += node.length;
}
} else if (node.nodeType === Node.ELEMENT_NODE) {
countUntilOffset(node, offset, countingState);
} else {
countingState.error = true;
}
}
return countingState;
}
function getCaretPosition()
{
let editor = document.getElementById('editor');
let sel = window.getSelection();
if (sel.rangeCount === 0) { return null; }
let range = sel.getRangeAt(0);
let start = countUntilEndContainer(editor, range.startContainer, range.startOffset);
let end = countUntilEndContainer(editor, range.endContainer, range.endOffset);
let offsetsCounts = { start: start.count + start.offsetInNode, end: end.count + end.offsetInNode };
let offsets = { start: start, end: end, offsets: offsetsCounts };
return offsets;
}
function setCaretPosition(start, end)
{
let editor = document.getElementById('editor');
let sel = window.getSelection();
if (sel.rangeCount === 0) { return null; }
let range = sel.getRangeAt(0);
let startNode = countUntilOffset(editor, start);
let endNode = countUntilOffset(editor, end);
let newRange = new Range();
newRange.setStart(startNode.node, startNode.offsetInNode);
newRange.setEnd(endNode.node, endNode.offsetInNode);
sel.removeAllRanges();
sel.addRange(newRange);
return true;
}
答案 12 :(得分:0)
这个适用于角度
private getCaretPosition() {
let caretRevCount = 0;
if (window.getSelection) {
const selection = window.getSelection();
const currentNode = selection.focusNode.parentNode;
caretRevCount = selection.focusOffset;
let previousNode = currentNode.previousSibling;
while(previousNode && previousNode.nodeName === 'SPAN') {
// you can check specific element
caretRevCount += previousNode.textContent.length;
previousNode = previousNode.previousSibling;
}
}
return caretRevCount;
}
答案 13 :(得分:0)
此答案适用于嵌套文本元素,使用递归函数。 ?
奖励:将插入符号位置设置为保存位置。
function getCaretData(elem) {
var sel = window.getSelection();
return [sel.anchorNode, sel.anchorOffset];
}
function setCaret(el, pos) {
var range = document.createRange();
var sel = window.getSelection();
range.setStart(el,pos);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
let indexStack = [];
function checkParent(elem) {
let parent = elem.parentNode;
let parentChildren = Array.from(parent.childNodes);
let elemIndex = parentChildren.indexOf(elem);
indexStack.unshift(elemIndex);
if (parent !== cd) {
checkParent(parent);
} else {
return;
}
}
let stackPos = 0;
let elemToSelect;
function getChild(parent, index) {
let child = parent.childNodes[index];
if (stackPos < indexStack.length-1) {
stackPos++;
getChild(child, indexStack[stackPos]);
} else {
elemToSelect = child;
return;
}
}
let cd = document.querySelector('.cd'),
caretpos = document.querySelector('.caretpos');
cd.addEventListener('keyup', () => {
let caretData = getCaretData(cd);
let selectedElem = caretData[0];
let caretPos = caretData[1];
indexStack = [];
checkParent(selectedElem);
cd.innerHTML = 'Hello world! <span>Inline! <span>In inline!</span></span>';
stackPos = 0;
getChild(cd, indexStack[stackPos]);
setCaret(elemToSelect, caretPos);
caretpos.innerText = 'indexStack: ' + indexStack + '. Got child: ' + elemToSelect.data + '. Moved caret to child at pos: ' + caretPos;
})
.cd, .caretpos {
font-family: system-ui, Segoe UI, sans-serif;
padding: 10px;
}
.cd span {
display: inline-block;
color: purple;
padding: 5px;
}
.cd span span {
color: chocolate;
padding: 3px;
}
:is(.cd, .cd span):hover {
border-radius: 3px;
box-shadow: inset 0 0 0 2px #005ecc;
}
<div class="cd" contenteditable="true">Hello world! <span>Inline! <span>In inline!</span></span></div>
<div class="caretpos">Move your caret inside the elements above ⤴</div>