Obtener contenidoPosición de intercalación editable
Estoy encontrando toneladas de buenas respuestas en varios navegadores sobre cómo establecer la posición del cursor en un contentEditable
elemento, pero ninguna sobre cómo obtener la posición del cursor en primer lugar.
Lo que quiero hacer es saber la posición del cursor dentro de una división keyup
. Entonces, cuando el usuario escribe texto, puedo, en cualquier momento, conocer la posición del cursor dentro del contentEditable
elemento.
<div id="contentBox" contentEditable="true"></div>
$('#contentbox').keyup(function() {
// ... ?
});
El siguiente código supone:
- Siempre hay un único nodo de texto dentro del editable
<div>
y ningún otro nodo. - El div editable no tiene la
white-space
propiedad CSS configurada enpre
Si necesita un enfoque más general que funcione con contenido con elementos anidados, pruebe esta respuesta:
https://stackoverflow.com/a/4812022/96100
Código:
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>
Algunas arrugas que no veo que se aborden en otras respuestas:
- el elemento puede contener múltiples niveles de nodos secundarios (por ejemplo, nodos secundarios que tienen nodos secundarios que tienen nodos secundarios...)
- una selección puede constar de diferentes posiciones inicial y final (por ejemplo, se seleccionan varios caracteres)
- el nodo que contiene un inicio/fin de Caret no puede ser ni el elemento ni sus hijos directos
A continuación se muestra una forma de obtener las posiciones inicial y final como desplazamientos del valor textContent del elemento:
// 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]];
}
Un poco tarde para la fiesta, pero por si alguien más tiene problemas. Ninguna de las búsquedas de Google que he encontrado durante los últimos dos días ha arrojado nada que funcione, pero se me ocurrió una solución concisa y elegante que siempre funcionará sin importar cuántas etiquetas anidadas tengas:
function cursor_position() {
var sel = document.getSelection();
sel.modify("extend", "backward", "paragraphboundary");
var pos = sel.toString().length;
if(sel.anchorNode != undefined) sel.collapseToEnd();
return pos;
}
// Demo:
var elm = document.querySelector('[contenteditable]');
elm.addEventListener('click', printCaretPosition)
elm.addEventListener('keydown', printCaretPosition)
function printCaretPosition(){
console.log( cursor_position(), 'length:', this.textContent.trim().length )
}
<div contenteditable>some text here <i>italic text here</i> some other text here <b>bold text here</b> end of text</div>
Selecciona todo el camino hasta el principio del párrafo y luego cuenta la longitud de la cadena para obtener la posición actual y luego deshace la selección para devolver el cursor a la posición actual. Si desea hacer esto para un documento completo (más de un párrafo), cambie paragraphboundary
a documentboundary
o cualquier granularidad para su caso. Consulte la API para obtener más detalles . ¡Salud! :)