Javascript 如何在contenteditable元素(div)中设置插入符号(光标)位置?
我以这个简单的HTML为例:Javascript 如何在contenteditable元素(div)中设置插入符号(光标)位置?,javascript,jquery,contenteditable,caret,cursor-position,Javascript,Jquery,Contenteditable,Caret,Cursor Position,我以这个简单的HTML为例: <div id="editable" contenteditable="true"> text text text<br> text text text<br> text text text<br> </div> <button id="button">focus</button> 可以这样手动设置插入符号位置吗?在大多数浏览器中,您需要和对象。将每个选择边界指定为节点
<div id="editable" contenteditable="true">
text text text<br>
text text text<br>
text text text<br>
</div>
<button id="button">focus</button>
可以这样手动设置插入符号位置吗?在大多数浏览器中,您需要和对象。将每个选择边界指定为节点,并在该节点内指定偏移。例如,要将插入符号设置为第二行文本的第五个字符,请执行以下操作:
函数setCaret(){
var el=document.getElementById(“可编辑”)
var range=document.createRange()
var sel=window.getSelection()
range.setStart(el.childNodes[2],5)
范围。折叠(真)
选择removeAllRanges()
选择添加范围(范围)
}
文本
文本
文本
文本
焦点
功能集\鼠标(){
var as=document.getElementById(“可编辑”);
el=as.childNodes[1].childNodes[0];//目标是获取要写入的('we')id(对象文本),因为它只在对象文本中工作
var range=document.createRange();
var sel=window.getSelection();
范围。设置开始(el,1);
范围。塌陷(真);
选择removeAllRanges();
选择添加范围(范围);
document.getElementById(“we”).innerHTML=el;//请参阅输出we id
}
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
ddPSS
dd
dd
文本文本文本
聚焦
您在内容可编辑光标定位上找到的大多数答案都相当简单,因为它们只适合普通文本的输入。一旦在容器中使用html元素,输入的文本将被分割成节点,并在树结构中自由分布
要设置光标位置,我有一个函数,该函数在提供的节点内循环所有子文本节点,并设置从初始节点开始到chars.count字符的范围:
function createRange(node, chars, range) {
if (!range) {
range = document.createRange()
range.selectNode(node);
range.setStart(node, 0);
}
if (chars.count === 0) {
range.setEnd(node, chars.count);
} else if (node && chars.count >0) {
if (node.nodeType === Node.TEXT_NODE) {
if (node.textContent.length < chars.count) {
chars.count -= node.textContent.length;
} else {
range.setEnd(node, chars.count);
chars.count = 0;
}
} else {
for (var lp = 0; lp < node.childNodes.length; lp++) {
range = createRange(node.childNodes[lp], chars, range);
if (chars.count === 0) {
break;
}
}
}
}
return range;
};
range.collapse(false)将光标设置为范围的末尾。我已经用最新版本的Chrome、IE、Mozilla和Opera对它进行了测试,它们都运行良好
注:如果有人感兴趣,我可以使用以下代码获取当前光标位置:
function isChildOf(node, parentId) {
while (node !== null) {
if (node.id === parentId) {
return true;
}
node = node.parentNode;
}
return false;
};
function getCurrentCursorPosition(parentId) {
var selection = window.getSelection(),
charCount = -1,
node;
if (selection.focusNode) {
if (isChildOf(selection.focusNode, parentId)) {
node = selection.focusNode;
charCount = selection.focusOffset;
while (node) {
if (node.id === parentId) {
break;
}
if (node.previousSibling) {
node = node.previousSibling;
charCount += node.textContent.length;
} else {
node = node.parentNode;
if (node === null) {
break
}
}
}
}
}
return charCount;
};
代码的作用与set函数相反-它获取当前window.getSelection().focusNode和focusOffset,并向后计算遇到的所有文本字符,直到它到达id为containerId的父节点。isChildOf函数只是在运行之前检查所提供的节点实际上是所提供的parentId的子节点
代码应该可以直接工作而不做任何更改,但我刚刚从我开发的jQuery插件中获取了它,所以我已经破解了几个这样的插件-如果有任何东西不起作用,请告诉我 如果不想使用jQuery,可以尝试以下方法:
public setCaretPosition() {
const editableDiv = document.getElementById('contenteditablediv');
const lastLine = this.input.nativeElement.innerHTML.replace(/.*?(<br>)/g, '');
const selection = window.getSelection();
selection.collapse(editableDiv.childNodes[editableDiv.childNodes.length - 1], lastLine.length);
}
public setCaretPosition(){
const editableDiv=document.getElementById('contenteditablediv');
const lastLine=this.input.nativeElement.innerHTML.replace(/.*?(
)/g',);
const selection=window.getSelection();
折叠(editableDiv.childNodes[editableDiv.childNodes.length-1],lastLine.length);
}
editableDiv
在编辑元素时,不要忘记为它设置一个id
。然后,您需要从元素中获取innerHTML
,并剪切所有制动线。然后用下一个参数设置collapse。我正在编写一个语法高亮(和基本代码编辑器),我需要知道如何自动键入单引号字符并将插入符号向后移动(就像现在很多代码编辑器一样)
这是我的解决方案的一个片段,多亏了这个线程、MDN文档和大量moz控制台的帮助
//onKeyPress event
if (evt.key === "\"") {
let sel = window.getSelection();
let offset = sel.focusOffset;
let focus = sel.focusNode;
focus.textContent += "\""; //setting div's innerText directly creates new
//nodes, which invalidate our selections, so we modify the focusNode directly
let range = document.createRange();
range.selectNode(focus);
range.setStart(focus, offset);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
//end onKeyPress event
这在contenteditable div元素中
我在这里留下这个作为感谢,意识到已经有了一个公认的答案。我认为在contenteditable元素中将插入符号设置到某个位置并不简单。我为此编写了自己的代码。它绕过节点树,计算剩余字符数,并在所需元素中设置插入符号。我没有对这段代码进行太多测试
const el = document.getElementById("editable");
el.focus()
let char = 1, sel; // character at which to place caret
if (document.selection) {
sel = document.selection.createRange();
sel.moveStart('character', char);
sel.select();
}
else {
sel = window.getSelection();
sel.collapse(el.lastChild, char);
}
//在当前contenteditable字段中设置偏移量(用于默认启动或用于with forEnd=true)
函数setCurSelectionOffset(偏移量,forEnd=false){
const sel=window.getSelection();
if(sel.rangeCount!==1 | |!document.activeElement)返回;
常数firstRange=sel.getRangeAt(0);
如果(偏移量>0){
绕过子节点(document.activeElement,offset);
}否则{
if(forEnd)
setEnd(document.activeElement,0);
其他的
firstRange.setStart(document.activeElement,0);
}
//纵深绕道
函数节点(el、leftOffset){
const childNodes=el.childNodes;
对于(设i=0;i=leftOffset){
if(forEnd)
setEnd(childNode,leftOffset);
其他的
setStart(childNode,leftOffset);
返回0;
}否则{
leftOffset-=卷曲;
}
}否则
if(childNode.nodeType==1){
leftOffset=bypassChildNodes(childNode,leftOffset);
}
}
返回左偏移量;
}
}
我还编写了获取当前插入符号位置的代码(未测试):
//获取当前contenteditable字段中的偏移量(默认情况下为起始偏移量或calcEnd=true的结束偏移量)
函数GetCurseElectionOffset(calcEnd=false){
const sel=window.getSelection();
if(sel.rangeCount!==1 | |!document.activeElement)返回0;
常数firstRange=sel.getRangeAt(0),
startContainer=calcEnd?firstRange.endContainer:firstRange.startContainer,
startOffset=calcEnd?firstRange.endOffset:firstRange.startOffset;
让我们停下来
//onKeyPress event
if (evt.key === "\"") {
let sel = window.getSelection();
let offset = sel.focusOffset;
let focus = sel.focusNode;
focus.textContent += "\""; //setting div's innerText directly creates new
//nodes, which invalidate our selections, so we modify the focusNode directly
let range = document.createRange();
range.selectNode(focus);
range.setStart(focus, offset);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
//end onKeyPress event
const el = document.getElementById("editable");
el.focus()
let char = 1, sel; // character at which to place caret
if (document.selection) {
sel = document.selection.createRange();
sel.moveStart('character', char);
sel.select();
}
else {
sel = window.getSelection();
sel.collapse(el.lastChild, char);
}
// get current selection
const [start, end] = getSelectionOffset(container)
// change container html
container.innerHTML = newHtml
// restore selection
setSelectionOffset(container, start, end)
// use this instead innerText for get text with keep all spaces
const innerText = getInnerText(container)
const textBeforeCaret = innerText.substring(0, start)
const textAfterCaret = innerText.substring(start)
/** return true if node found */
function searchNode(
container: Node,
startNode: Node,
predicate: (node: Node) => boolean,
excludeSibling?: boolean,
): boolean {
if (predicate(startNode as Text)) {
return true
}
for (let i = 0, len = startNode.childNodes.length; i < len; i++) {
if (searchNode(startNode, startNode.childNodes[i], predicate, true)) {
return true
}
}
if (!excludeSibling) {
let parentNode = startNode
while (parentNode && parentNode !== container) {
let nextSibling = parentNode.nextSibling
while (nextSibling) {
if (searchNode(container, nextSibling, predicate, true)) {
return true
}
nextSibling = nextSibling.nextSibling
}
parentNode = parentNode.parentNode
}
}
return false
}
function createRange(container: Node, start: number, end: number): Range {
let startNode
searchNode(container, container, node => {
if (node.nodeType === Node.TEXT_NODE) {
const dataLength = (node as Text).data.length
if (start <= dataLength) {
startNode = node
return true
}
start -= dataLength
end -= dataLength
return false
}
})
let endNode
if (startNode) {
searchNode(container, startNode, node => {
if (node.nodeType === Node.TEXT_NODE) {
const dataLength = (node as Text).data.length
if (end <= dataLength) {
endNode = node
return true
}
end -= dataLength
return false
}
})
}
const range = document.createRange()
if (startNode) {
if (start < startNode.data.length) {
range.setStart(startNode, start)
} else {
range.setStartAfter(startNode)
}
} else {
if (start === 0) {
range.setStart(container, 0)
} else {
range.setStartAfter(container)
}
}
if (endNode) {
if (end < endNode.data.length) {
range.setEnd(endNode, end)
} else {
range.setEndAfter(endNode)
}
} else {
if (end === 0) {
range.setEnd(container, 0)
} else {
range.setEndAfter(container)
}
}
return range
}
export function setSelectionOffset(node: Node, start: number, end: number) {
const range = createRange(node, start, end)
const selection = window.getSelection()
selection.removeAllRanges()
selection.addRange(range)
}
function hasChild(container: Node, node: Node): boolean {
while (node) {
if (node === container) {
return true
}
node = node.parentNode
}
return false
}
function getAbsoluteOffset(container: Node, offset: number) {
if (container.nodeType === Node.TEXT_NODE) {
return offset
}
let absoluteOffset = 0
for (let i = 0, len = Math.min(container.childNodes.length, offset); i < len; i++) {
const childNode = container.childNodes[i]
searchNode(childNode, childNode, node => {
if (node.nodeType === Node.TEXT_NODE) {
absoluteOffset += (node as Text).data.length
}
return false
})
}
return absoluteOffset
}
export function getSelectionOffset(container: Node): [number, number] {
let start = 0
let end = 0
const selection = window.getSelection()
for (let i = 0, len = selection.rangeCount; i < len; i++) {
const range = selection.getRangeAt(i)
if (range.intersectsNode(container)) {
const startNode = range.startContainer
searchNode(container, container, node => {
if (startNode === node) {
start += getAbsoluteOffset(node, range.startOffset)
return true
}
const dataLength = node.nodeType === Node.TEXT_NODE
? (node as Text).data.length
: 0
start += dataLength
end += dataLength
return false
})
const endNode = range.endContainer
searchNode(container, startNode, node => {
if (endNode === node) {
end += getAbsoluteOffset(node, range.endOffset)
return true
}
const dataLength = node.nodeType === Node.TEXT_NODE
? (node as Text).data.length
: 0
end += dataLength
return false
})
break
}
}
return [start, end]
}
export function getInnerText(container: Node) {
const buffer = []
searchNode(container, container, node => {
if (node.nodeType === Node.TEXT_NODE) {
buffer.push((node as Text).data)
}
return false
})
return buffer.join('')
}
let richText = document.getElementById('rich-text');
let offset = Cursor.getCurrentCursorPosition(richText);
// insert code here that does stuff to the innerHTML, such as adding/removing <span> tags
Cursor.setCurrentCursorPosition(offset, richText);
richText.focus();
// Credit to Liam (Stack Overflow)
// https://stackoverflow.com/a/41034697/3480193
class Cursor {
static getCurrentCursorPosition(parentElement) {
var selection = window.getSelection(),
charCount = -1,
node;
if (selection.focusNode) {
if (Cursor._isChildOf(selection.focusNode, parentElement)) {
node = selection.focusNode;
charCount = selection.focusOffset;
while (node) {
if (node === parentElement) {
break;
}
if (node.previousSibling) {
node = node.previousSibling;
charCount += node.textContent.length;
} else {
node = node.parentNode;
if (node === null) {
break;
}
}
}
}
}
return charCount;
}
static setCurrentCursorPosition(chars, element) {
if (chars >= 0) {
var selection = window.getSelection();
let range = Cursor._createRange(element, { count: chars });
if (range) {
range.collapse(false);
selection.removeAllRanges();
selection.addRange(range);
}
}
}
static _createRange(node, chars, range) {
if (!range) {
range = document.createRange()
range.selectNode(node);
range.setStart(node, 0);
}
if (chars.count === 0) {
range.setEnd(node, chars.count);
} else if (node && chars.count >0) {
if (node.nodeType === Node.TEXT_NODE) {
if (node.textContent.length < chars.count) {
chars.count -= node.textContent.length;
} else {
range.setEnd(node, chars.count);
chars.count = 0;
}
} else {
for (var lp = 0; lp < node.childNodes.length; lp++) {
range = Cursor._createRange(node.childNodes[lp], chars, range);
if (chars.count === 0) {
break;
}
}
}
}
return range;
}
static _isChildOf(node, parentElement) {
while (node !== null) {
if (node === parentElement) {
return true;
}
node = node.parentNode;
}
return false;
}
}
var sel = window.getSelection();
sel?.setPosition(wordDiv.childNodes[0], 5);
event.preventDefault();