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'

reprend le focus. Il apparaît la fonctionnalité par défaut d'un div modifiable de contenu est de déplacer le curseur caret / au début du texte dans le div chaque fois que vous cliquez dessus, ce qui est indésirable.

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

. Peut-être seulement pour textarea ( comment déplacer le curseur à la fin de l'entité contenteditable )

136
demandé sur Community 2009-07-25 13:18:17

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);
};
56
répondu eyelidlessness 2009-07-28 08:31:07

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>
95
répondu Nico Burns 2013-07-06 15:31:46

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.

19
répondu Tim Down 2017-05-23 12:25:45

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!

15
répondu Zane Claes 2012-12-24 14:57:38

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>
7
répondu Gatsbimantico 2015-01-28 23:10:24

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);
    });
};
4
répondu mkaj 2013-08-17 08:12:29

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();
1
répondu zoonman 2017-12-27 05:45:49

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);
0
répondu yoav 2012-11-18 09:24:18