Je suis en train de trouver des tonnes de réponses intéressantes sur la définition du curseur ou de la position du curseur dans une DIV contentEditable, mais pas sur la façon d'obtenir ou de trouver sa position ...
Ce que je veux faire, c'est connaître la position du curseur dans cette division, sur keyup.
Ainsi, lorsque l'utilisateur tape du texte, je peux à tout moment connaître la position de son curseur dans la div.
EDIT: Je cherche l'INDEX dans le contenu div (texte), pas les coordonnées du curseur.
<div id="contentBox" contentEditable="true"></div>
$('#contentbox').keyup(function() {
// ... ?
});
Le code suivant suppose:
<div>
Éditable et aucun autre nœudwhite-space
Définie sur pre
Code:
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>
$("#editable").on('keydown keyup mousedown mouseup',function(e){
if($(window.getSelection().anchorNode).is($(this))){
$('#position').html('0')
}else{
$('#position').html(window.getSelection().anchorOffset);
}
});
body{
padding:40px;
}
#editable{
height:50px;
width:400px;
border:1px solid #000;
}
#editable p{
margin:0;
padding:0;
}
<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>
Essaye ça:
Caret.js Obtenir la position du caret et son décalage par rapport au champ de texte
Quelques rides que je ne vois pas être abordées dans d'autres réponses:
Voici un moyen d'obtenir les positions de début et de fin en tant que décalages par rapport à la valeur textContent de l'élément:
// 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 peu tard pour la fête, mais au cas où quelqu'un d'autre se débat. Aucune des recherches Google que j'ai trouvées ces deux derniers jours n'a abouti à un résultat positif, mais j'ai proposé une solution concise et élégante qui fonctionnera toujours, quel que soit le nombre de balises imbriquées que vous possédez:
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;
}
Il sélectionne jusqu'au début du paragraphe, puis compte la longueur de la chaîne pour obtenir la position actuelle, puis annule la sélection pour ramener le curseur à la position actuelle. Si vous souhaitez effectuer cette opération pour un document entier (plusieurs paragraphes), remplacez paragraphboundary
par documentboundary
ou toute autre précision relative à votre cas. Découvrez l'API pour plus de détails . À votre santé! :)
//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)
}
});
Remarque: l'objet plage lui-même peut être stocké dans une variable et peut être sélectionné à tout moment, sauf si le contenu de la div contenteditable est modifié.
Référence pour IE 8 et versions antérieures: http://msdn.Microsoft.com/en-us/library/ms535872 (VS.85) .aspx
Référence pour les navigateurs standards (tous les autres): https://developer.mozilla.org/en/DOM/range (ce sont les documents mozilla, mais le code fonctionne en chrome, safari, opera et ie9 aussi)
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
};
}
Celui-ci fonctionne pour moi:
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;
}
la ligne appelante dépend du type d'événement. Pour un événement clé, utilisez ceci:
getCaretCharOffsetInDiv(e.target) + ($(window.getSelection().getRangeAt(0).startContainer.parentNode).index());
pour un événement de souris, utilisez ceci:
getCaretCharOffsetInDiv(e.target.parentElement) + ($(e.target).index())
sur ces deux cas, je prends soin des lignes de rupture en ajoutant l'indice cible
Comme cela m’a pris une éternité à comprendre en utilisant la nouvelle API window.getSelection que je vais partager pour la postérité. Notez que MDN suggère une prise en charge plus large de window.getSelection. Toutefois, votre kilométrage peut varier.
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,
}
}
Voici un jsfiddle qui se déclenche sur keyup. Notez cependant que les appuis rapides sur les touches directionnelles, ainsi que les suppressions rapides, semblent être des événements ignorés.
Un moyen simple, qui parcourt tous les enfants de la div contente jusqu'à ce qu'il atteigne le conteneur final. Ensuite, j'ajoute le décalage du conteneur final et nous avons l'index de caractères. Devrait fonctionner avec n'importe quel nombre de nidifications. utilise la récursivité.
Remarque: nécessite un remplissage multiple pour, par exemple, prendre en charge Element.closest('div[contenteditable]')
https://codepen.io/alockwood05/pen/vMpdmZ
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;
}
}