问题
This question has some answers here but there's a few problems.
Basically I want to do the following:
- get caret position
- set innerHTML of the contenteditable (this resets the caret position)
- set the caret position to the value obtained in step 1.
A lot of the existing answers seem to be complicated by cross-browser support but I only need it to work on modern chrome. It also needs to work with html. Ideally it would look exactly like this:
var index = getCaretPosition(contentEditableDiv);
onEdit(contentEditableDiv); // <-- callback function that manipulates the text and sets contentEditableDiv.innerHTML = theManipulatedText
setCaretPosition(contentEditableDiv, index);
I've tried looking through the documentation but it's not straightforward and I think this question is due for a leaner answer anyways.
回答1:
This seems to work for me but I've only tested it for my use cases.
GET
function getCaretIndex(win, contentEditable) {
var index = 0;
var selection = win.getSelection();
var textNodes = textNodesUnder(contentEditable);
for(var i = 0; i < textNodes.length; i++) {
var node = textNodes[i];
var isSelectedNode = node === selection.focusNode;
if(isSelectedNode) {
index += selection.focusOffset;
break;
}
else {
index += node.textContent.length;
}
}
return index;
}
SET
function setCaretIndex(win, contentEditable, newCaretIndex) {
var cumulativeIndex = 0;
var relativeIndex = 0;
var targetNode = null;
var textNodes = textNodesUnder(contentEditable);
for(var i = 0; i < textNodes.length; i++) {
var node = textNodes[i];
if(newCaretIndex <= cumulativeIndex + node.textContent.length) {
targetNode = node;
relativeIndex = newCaretIndex - cumulativeIndex;
break;
}
cumulativeIndex += node.textContent.length;
}
var range = win.document.createRange();
range.setStart(targetNode, relativeIndex);
range.setEnd(targetNode, relativeIndex);
range.collapse();
var sel = win.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
REQUIRED HELPER
function textNodesUnder(node) { // https://stackoverflow.com/a/10730777/3245937
var all = [];
for (node=node.firstChild;node;node=node.nextSibling){
if (node.nodeType==3) {
all.push(node);
}
else {
all = all.concat(textNodesUnder(node));
}
}
return all;
}
TEST (just call this function)
It loops through the text in a contenteditable, sets the caret index, then reads it. Console output is: (setIndex | getIndex)
function testContentEditable() {
document.body.innerHTML = "<div contenteditable></div>"
var ce = document.querySelector("[contenteditable]");
ce.focus();
ce.innerHTML = "HELLO <span data-foo='true' style='text-decoration: underline;'><span style='color:red;'>WORLD</span> MY</span> NAME IS BOB";
var i = 0;
var intv = setInterval(function() {
if(i == ce.innerText.length) {
clearInterval(intv);
}
setCaretIndex(window, ce, i);
var currentIndex = getCaretIndex(window, ce);
console.log(i + " | " + currentIndex);
i++;
}, 100);
}
FIDDLE
function getCaretIndex(win, contentEditable) {
var index = 0;
var selection = win.getSelection();
var textNodes = textNodesUnder(contentEditable);
for(var i = 0; i < textNodes.length; i++) {
var node = textNodes[i];
var isSelectedNode = node === selection.focusNode;
if(isSelectedNode) {
index += selection.focusOffset;
break;
}
else {
index += node.textContent.length;
}
}
return index;
}
function setCaretIndex(win, contentEditable, newCaretIndex) {
var cumulativeIndex = 0;
var relativeIndex = 0;
var targetNode = null;
var textNodes = textNodesUnder(contentEditable);
for(var i = 0; i < textNodes.length; i++) {
var node = textNodes[i];
if(newCaretIndex <= cumulativeIndex + node.textContent.length) {
targetNode = node;
relativeIndex = newCaretIndex - cumulativeIndex;
break;
}
cumulativeIndex += node.textContent.length;
}
var range = win.document.createRange();
range.setStart(targetNode, relativeIndex);
range.setEnd(targetNode, relativeIndex);
range.collapse();
var sel = win.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
function textNodesUnder(node) { // https://stackoverflow.com/a/10730777/3245937
var all = [];
for (node=node.firstChild;node;node=node.nextSibling){
if (node.nodeType==3) {
all.push(node);
}
else {
all = all.concat(textNodesUnder(node));
}
}
return all;
}
function testContentEditable() {
document.body.innerHTML = "<div contenteditable></div>"
var ce = document.querySelector("[contenteditable]");
ce.focus();
ce.innerHTML = "HELLO <span data-foo='true' style='text-decoration: underline;'><span style='color:red;'>WORLD</span> MY</span> NAME IS BOB";
var i = 0;
var intv = setInterval(function() {
if(i == ce.innerText.length) {
clearInterval(intv);
}
setCaretIndex(window, ce, i);
var currentIndex = getCaretIndex(window, ce);
console.log(i + " | " + currentIndex);
i++;
}, 100);
}
testContentEditable();
来源:https://stackoverflow.com/questions/64618729/how-do-you-get-and-set-the-caret-position-in-a-contenteditable