Position du curseur sur contentEditable
je suis à la recherche d'une solution de cross-browser définitive pour régler la position du curseur/caret à la dernière position connue lorsqu'un contentteditable='on'
je crois que je devrais stocker dans une variable la position actuelle du curseur quand ils quittent le foyer de la div, Et puis re-réglez cela quand ils ont mis l'accent à l'intérieur de nouveau, mais je n'ai pas été en mesure de mettre ensemble, ou de trouver un échantillon de code de travail encore.
si quelqu'un a des idées, des morceaux de code ou des échantillons, je serais heureux de les voir.
Je n'ai pas encore vraiment de code mais voici ce que j'ai:
<script type="text/javascript">
// jQuery
$(document).ready(function() {
$('#area').focus(function() { .. } // focus I would imagine I need.
}
</script>
<div id="area" contentEditable="true"></div>
PS. J'ai essayé cette ressource, mais il semble qu'il ne fonctionne pas pour un
8 réponses
c'est compatible avec les navigateurs standards, mais cela échouera probablement dans IE. Je le fournis comme point de départ. IE ne supporte pas DOM Range.
var editable = document.getElementById('editable'),
selection, range;
// Populates selection and range variables
var captureSelection = function(e) {
// Don't capture selection outside editable region
var isOrContainsAnchor = false,
isOrContainsFocus = false,
sel = window.getSelection(),
parentAnchor = sel.anchorNode,
parentFocus = sel.focusNode;
while(parentAnchor && parentAnchor != document.documentElement) {
if(parentAnchor == editable) {
isOrContainsAnchor = true;
}
parentAnchor = parentAnchor.parentNode;
}
while(parentFocus && parentFocus != document.documentElement) {
if(parentFocus == editable) {
isOrContainsFocus = true;
}
parentFocus = parentFocus.parentNode;
}
if(!isOrContainsAnchor || !isOrContainsFocus) {
return;
}
selection = window.getSelection();
// Get range (standards)
if(selection.getRangeAt !== undefined) {
range = selection.getRangeAt(0);
// Get range (Safari 2)
} else if(
document.createRange &&
selection.anchorNode &&
selection.anchorOffset &&
selection.focusNode &&
selection.focusOffset
) {
range = document.createRange();
range.setStart(selection.anchorNode, selection.anchorOffset);
range.setEnd(selection.focusNode, selection.focusOffset);
} else {
// Failure here, not handled by the rest of the script.
// Probably IE or some older browser
}
};
// Recalculate selection while typing
editable.onkeyup = captureSelection;
// Recalculate selection after clicking/drag-selecting
editable.onmousedown = function(e) {
editable.className = editable.className + ' selecting';
};
document.onmouseup = function(e) {
if(editable.className.match(/\sselecting(\s|$)/)) {
editable.className = editable.className.replace(/ selecting(\s|$)/, '');
captureSelection();
}
};
editable.onblur = function(e) {
var cursorStart = document.createElement('span'),
collapsed = !!range.collapsed;
cursorStart.id = 'cursorStart';
cursorStart.appendChild(document.createTextNode('—'));
// Insert beginning cursor marker
range.insertNode(cursorStart);
// Insert end cursor marker if any text is selected
if(!collapsed) {
var cursorEnd = document.createElement('span');
cursorEnd.id = 'cursorEnd';
range.collapse();
range.insertNode(cursorEnd);
}
};
// Add callbacks to afterFocus to be called after cursor is replaced
// if you like, this would be useful for styling buttons and so on
var afterFocus = [];
editable.onfocus = function(e) {
// Slight delay will avoid the initial selection
// (at start or of contents depending on browser) being mistaken
setTimeout(function() {
var cursorStart = document.getElementById('cursorStart'),
cursorEnd = document.getElementById('cursorEnd');
// Don't do anything if user is creating a new selection
if(editable.className.match(/\sselecting(\s|$)/)) {
if(cursorStart) {
cursorStart.parentNode.removeChild(cursorStart);
}
if(cursorEnd) {
cursorEnd.parentNode.removeChild(cursorEnd);
}
} else if(cursorStart) {
captureSelection();
var range = document.createRange();
if(cursorEnd) {
range.setStartAfter(cursorStart);
range.setEndBefore(cursorEnd);
// Delete cursor markers
cursorStart.parentNode.removeChild(cursorStart);
cursorEnd.parentNode.removeChild(cursorEnd);
// Select range
selection.removeAllRanges();
selection.addRange(range);
} else {
range.selectNode(cursorStart);
// Select range
selection.removeAllRanges();
selection.addRange(range);
// Delete cursor marker
document.execCommand('delete', false, null);
}
}
// Call callbacks here
for(var i = 0; i < afterFocus.length; i++) {
afterFocus[i]();
}
afterFocus = [];
// Register selection again
captureSelection();
}, 10);
};
Cette solution fonctionne dans tous les principaux navigateurs:
saveSelection()
est attaché aux événements onmouseup
et onkeyup
du div Et sauve la sélection à la variable savedRange
.
restoreSelection()
est attaché à l'événement onfocus
du div et resélectionne la sélection sauvegardée dans savedRange
.
cela fonctionne parfaitement sauf si vous voulez que la sélection soit restaurée quand l'utilisateur clique sur le div aswell (qui est un peu désintuitatif comme normalement vous attendez le curseur pour aller où vous cliquez mais le code inclus pour l'exhaustivité)
pour ce faire, les événements onclick
et onmousedown
sont annulés par la fonction cancelEvent()
qui est une fonction de navigation croisée pour annuler l'événement. La fonction cancelEvent()
exécute aussi la fonction restoreSelection()
parce que comme l'événement de clic est annulé la div ne reçoit pas de focus et donc rien est sélectionné à moins que cette fonction ne soit exécutée.
la variable isInFocus
stocke si elle est en mise au point et est changée en" false " onblur
et" true " onfocus
. Cela permet d'annuler des événements de clic seulement si le div n'est pas en mise au point (autrement vous ne seriez pas en mesure de changer la sélection du tout).
si vous souhaitez modifier la sélection lorsque le div est focalisé par un clic, et ne pas restaurer la sélection onclick
(et ce n'est que lorsque l'accent est mis sur l'élément programmtiquement en utilisant document.getElementById("area").focus();
ou similaire qu'il suffit de supprimer les événements onclick
et onmousedown
. L'événement onblur
et les fonctions onDivBlur()
et cancelEvent()
peuvent également être supprimés en toute sécurité dans ces circonstances.
ce code devrait fonctionner si vous le laissez tomber directement dans le corps d'une page html si vous voulez le tester rapidement:
<div id="area" style="width:300px;height:300px;" onblur="onDivBlur();" onmousedown="return cancelEvent(event);" onclick="return cancelEvent(event);" contentEditable="true" onmouseup="saveSelection();" onkeyup="saveSelection();" onfocus="restoreSelection();"></div>
<script type="text/javascript">
var savedRange,isInFocus;
function saveSelection()
{
if(window.getSelection)//non IE Browsers
{
savedRange = window.getSelection().getRangeAt(0);
}
else if(document.selection)//IE
{
savedRange = document.selection.createRange();
}
}
function restoreSelection()
{
isInFocus = true;
document.getElementById("area").focus();
if (savedRange != null) {
if (window.getSelection)//non IE and there is already a selection
{
var s = window.getSelection();
if (s.rangeCount > 0)
s.removeAllRanges();
s.addRange(savedRange);
}
else if (document.createRange)//non IE and no selection
{
window.getSelection().addRange(savedRange);
}
else if (document.selection)//IE
{
savedRange.select();
}
}
}
//this part onwards is only needed if you want to restore selection onclick
var isInFocus = false;
function onDivBlur()
{
isInFocus = false;
}
function cancelEvent(e)
{
if (isInFocus == false && savedRange != null) {
if (e && e.preventDefault) {
//alert("FF");
e.stopPropagation(); // DOM style (return false doesn't always work in FF)
e.preventDefault();
}
else {
window.event.cancelBubble = true;//IE stopPropagation
}
restoreSelection();
return false; // false = IE style
}
}
</script>
mise à Jour
j'ai écrit une bibliothèque de sélection et de sélection appelée Rangy qui intègre une version améliorée du code que j'ai posté ci-dessous. Vous pouvez utiliser la sélection pour sauvegarder et restaurer le module pour cette question particulière, bien que je serais tenté d'utiliser quelque chose comme @Nico Burns réponse si vous ne faites rien d'autre avec les sélections dans votre projet et n'ont pas besoin de l'essentiel d'une bibliothèque.
précédente réponse
vous pouvez utiliser IERange ( http://code.google.com/p/ierange / ) pour convertir le TextRange D'IE en quelque chose comme une gamme DOM et l'utiliser en conjonction avec quelque chose comme le point de départ de eyelidlessness. Personnellement, je n'utiliserais que les algorithmes de IERange qui font les conversions <-> TextRange plutôt que d'utiliser les toute chose. Et L'objet de sélection D'IE n'a pas les propriétés focusNode et anchorNode, mais vous devriez être en mesure d'utiliser la plage/TextRange obtenue de la sélection à la place.
je pourrais rassembler quelque chose pour le faire, je posterai ici si et quand je le ferai.
EDIT:
j'ai créé une démo d'un script qui fait cela. Cela fonctionne dans tout ce que j'ai essayé jusqu'à présent sauf pour un bug dans Opera 9, que je n'ai pas eu le temps de regarder dans encore. Les navigateurs dans lesquels il fonctionne sont IE 5.5, 6 et 7, Chrome 2, Firefox 2, 3 et 3.5, et Safari 4, tous sur Windows.
http://www.timdown.co.uk/code/selections /
noter que les sélections peuvent être faites à l'envers dans les navigateurs de sorte que le noeud de mise au point est au début de la sélection et en appuyant sur la touche droite ou gauche du curseur déplacera le caret à une position relative au début de la sélection. Je n'ai pas pense qu'il est possible de reproduire ce lors de la restauration d'une sélection, de sorte que l'objet nœud est toujours à la fin de la sélection.
je vais écrire ceci complètement à un moment donné bientôt.
j'ai eu une situation connexe, où j'avais spécifiquement besoin de mettre la position du curseur à la fin d'une div contenteditable. Je ne voulais pas utiliser une bibliothèque complète comme Rangy, et beaucoup de solutions étaient beaucoup trop lourdes.
finalement, j'ai trouvé cette simple fonction jQuery pour régler la position du carat à la fin d'une div contentetable:
$.fn.focusEnd = function() {
$(this).focus();
var tmp = $('<span />').appendTo($(this)),
node = tmp.get(0),
range = null,
sel = null;
if (document.selection) {
range = document.body.createTextRange();
range.moveToElementText(node);
range.select();
} else if (window.getSelection) {
range = document.createRange();
range.selectNode(node);
sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
tmp.remove();
return this;
}
La théorie est simple: ajouter une plage à la fin de l'modifiable, sélectionnez-le,, puis retirez la durée, nous laissant avec un curseur à la fin de la div. Vous pouvez adapter cette solution pour insérer la portée où vous voulez, mettant ainsi le curseur à un endroit spécifique.
L'Usage est simple:
$('#editable').focusEnd();
C'est ça!
J'ai pris la réponse de Nico Burns et l'ai faite en utilisant jQuery:
- générique: pour chaque
div contentEditable="true"
- plus court
Vous aurez besoin de jQuery 1.6 ou supérieur:
savedRanges = new Object();
$('div[contenteditable="true"]').focus(function(){
var s = window.getSelection();
var t = $('div[contenteditable="true"]').index(this);
if (typeof(savedRanges[t]) === "undefined"){
savedRanges[t]= new Range();
} else if(s.rangeCount > 0) {
s.removeAllRanges();
s.addRange(savedRanges[t]);
}
}).bind("mouseup keyup",function(){
var t = $('div[contenteditable="true"]').index(this);
savedRanges[t] = window.getSelection().getRangeAt(0);
}).on("mousedown click",function(e){
if(!$(this).is(":focus")){
e.stopPropagation();
e.preventDefault();
$(this).focus();
}
});
savedRanges = new Object();
$('div[contenteditable="true"]').focus(function(){
var s = window.getSelection();
var t = $('div[contenteditable="true"]').index(this);
if (typeof(savedRanges[t]) === "undefined"){
savedRanges[t]= new Range();
} else if(s.rangeCount > 0) {
s.removeAllRanges();
s.addRange(savedRanges[t]);
}
}).bind("mouseup keyup",function(){
var t = $('div[contenteditable="true"]').index(this);
savedRanges[t] = window.getSelection().getRangeAt(0);
}).on("mousedown click",function(e){
if(!$(this).is(":focus")){
e.stopPropagation();
e.preventDefault();
$(this).focus();
}
});
div[contenteditable] {
padding: 1em;
font-family: Arial;
outline: 1px solid rgba(0,0,0,0.5);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div contentEditable="true"></div>
<div contentEditable="true"></div>
<div contentEditable="true"></div>
après avoir joué autour de j'ai modifié eyelidless 'réponse ci-dessus et en a fait un plugin jQuery donc vous pouvez juste faire l'un de ces:
var html = "The quick brown fox";
$div.html(html);
// Select at the text "quick":
$div.setContentEditableSelection(4, 5);
// Select at the beginning of the contenteditable div:
$div.setContentEditableSelection(0);
// Select at the end of the contenteditable div:
$div.setContentEditableSelection(html.length);
Excusez-long code post, mais il peut aider quelqu'un:
$.fn.setContentEditableSelection = function(position, length) {
if (typeof(length) == "undefined") {
length = 0;
}
return this.each(function() {
var $this = $(this);
var editable = this;
var selection;
var range;
var html = $this.html();
html = html.substring(0, position) +
'<a id="cursorStart"></a>' +
html.substring(position, position + length) +
'<a id="cursorEnd"></a>' +
html.substring(position + length, html.length);
console.log(html);
$this.html(html);
// Populates selection and range variables
var captureSelection = function(e) {
// Don't capture selection outside editable region
var isOrContainsAnchor = false,
isOrContainsFocus = false,
sel = window.getSelection(),
parentAnchor = sel.anchorNode,
parentFocus = sel.focusNode;
while (parentAnchor && parentAnchor != document.documentElement) {
if (parentAnchor == editable) {
isOrContainsAnchor = true;
}
parentAnchor = parentAnchor.parentNode;
}
while (parentFocus && parentFocus != document.documentElement) {
if (parentFocus == editable) {
isOrContainsFocus = true;
}
parentFocus = parentFocus.parentNode;
}
if (!isOrContainsAnchor || !isOrContainsFocus) {
return;
}
selection = window.getSelection();
// Get range (standards)
if (selection.getRangeAt !== undefined) {
range = selection.getRangeAt(0);
// Get range (Safari 2)
} else if (
document.createRange &&
selection.anchorNode &&
selection.anchorOffset &&
selection.focusNode &&
selection.focusOffset
) {
range = document.createRange();
range.setStart(selection.anchorNode, selection.anchorOffset);
range.setEnd(selection.focusNode, selection.focusOffset);
} else {
// Failure here, not handled by the rest of the script.
// Probably IE or some older browser
}
};
// Slight delay will avoid the initial selection
// (at start or of contents depending on browser) being mistaken
setTimeout(function() {
var cursorStart = document.getElementById('cursorStart');
var cursorEnd = document.getElementById('cursorEnd');
// Don't do anything if user is creating a new selection
if (editable.className.match(/\sselecting(\s|$)/)) {
if (cursorStart) {
cursorStart.parentNode.removeChild(cursorStart);
}
if (cursorEnd) {
cursorEnd.parentNode.removeChild(cursorEnd);
}
} else if (cursorStart) {
captureSelection();
range = document.createRange();
if (cursorEnd) {
range.setStartAfter(cursorStart);
range.setEndBefore(cursorEnd);
// Delete cursor markers
cursorStart.parentNode.removeChild(cursorStart);
cursorEnd.parentNode.removeChild(cursorEnd);
// Select range
selection.removeAllRanges();
selection.addRange(range);
} else {
range.selectNode(cursorStart);
// Select range
selection.removeAllRanges();
selection.addRange(range);
// Delete cursor marker
document.execCommand('delete', false, null);
}
}
// Register selection again
captureSelection();
}, 10);
});
};
Vous pouvez tirer parti de la selectNodeContents qui est pris en charge par les navigateurs modernes.
var el = document.getElementById('idOfYoursContentEditable');
var selection = window.getSelection();
var range = document.createRange();
selection.removeAllRanges();
range.selectNodeContents(el);
range.collapse(false);
selection.addRange(range);
el.focus();
dans Firefox vous pouvez avoir le texte du div dans un noeud enfant ( o_div.childNodes[0]
)
var range = document.createRange();
range.setStart(o_div.childNodes[0],last_caret_pos);
range.setEnd(o_div.childNodes[0],last_caret_pos);
range.collapse(false);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);