jQuery Définir la position du curseur dans la zone de texte

435

Comment définir la position du curseur dans un champ de texte à l'aide de jQuery? J'ai un champ de texte avec du contenu et je veux que le curseur des utilisateurs soit positionné à un certain décalage lorsqu'ils se concentrent sur le champ. Le code devrait ressembler à ceci:

$('#input').focus(function() {
  $(this).setCursorPosition(4);
});

À quoi ressemblerait l'implémentation de cette fonction setCursorPosition? Si vous aviez un champ de texte avec le contenu abcdefg, cet appel entraînerait le positionnement du curseur comme suit: abcd ** | ** efg.

Java a une fonction similaire, setCaretPosition. Existe-t-il une méthode similaire pour javascript?

Mise à jour: j'ai modifié le code de CMS pour fonctionner avec jQuery comme suit:

new function($) {
  $.fn.setCursorPosition = function(pos) {
    if (this.setSelectionRange) {
      this.setSelectionRange(pos, pos);
    } else if (this.createTextRange) {
      var range = this.createTextRange();
      range.collapse(true);
      if(pos < 0) {
        pos = $(this).val().length + pos;
      }
      range.moveEnd('character', pos);
      range.moveStart('character', pos);
      range.select();
    }
  }
}(jQuery);
jcnnghm
la source
78
$(this).get(0).setSelectionRange)? Vous savez que c'est exactement la même chose que this.setSelectionRange, plus lent et plus difficile à lire, non? jQuery ne fait littéralement rien pour vous ici.
bobince
2
Pour ajouter au commentaire @bobince, la fonction doit itérer pour chacun des éléments sélectionnés et le renvoyer. Le bon code est dans ma réponse.
HRJ
21
@bobince n'est pas non plus tout à fait correct. 'ceci' n'est pas le nœud DOM, mais l'objet jQuery. Ainsi, $ (this) .get (0) .setSelectionRange est le même que this.get (0) .setSelectionRange, pas le même que this.setSelectionRange.
Prestaul
$ (this) [0] est plus rapide que $ (this) .get (0)
EminezArtus
Consultez ce tutoriel pour une solution complète. webdesignpluscode.blogspot.com/2017/05/…
Waqas Ali

Réponses:

254

J'ai deux fonctions:

function setSelectionRange(input, selectionStart, selectionEnd) {
  if (input.setSelectionRange) {
    input.focus();
    input.setSelectionRange(selectionStart, selectionEnd);
  }
  else if (input.createTextRange) {
    var range = input.createTextRange();
    range.collapse(true);
    range.moveEnd('character', selectionEnd);
    range.moveStart('character', selectionStart);
    range.select();
  }
}

function setCaretToPos (input, pos) {
  setSelectionRange(input, pos, pos);
}

Ensuite, vous pouvez utiliser setCaretToPos comme ceci:

setCaretToPos(document.getElementById("YOURINPUT"), 4);

Exemple en direct avec a textareaet an input, montrant l'utilisation de jQuery:

Depuis 2016, testé et fonctionnant sur Chrome, Firefox, IE11, voire IE8 (voir ce dernier ici ; les extraits de pile ne prennent pas en charge IE8).

CMS
la source
3
Pourquoi l'effondrement (vrai) serait-il nécessaire puisque vous allez définir les décalages de fin et de début de sélection?
Alexis Wilke
@mareoraft: Fonctionne sur textarea(et input) pour moi sur Chrome, Firefox, IE8 et IE11.
TJ Crowder du
Je ne semble pas être en mesure de faire fonctionner cela avec mon script. J'ai une zone de texte vide au chargement de la page, puis remplie par javascript lorsque l'application est utilisée. Je veux que le curseur soit remis à 0 avant chaque nouvelle écriture (un enregistrement de l'utilisation). Est-ce que ce sont les données dynamiques qui me posent problème? si oui, comment pourrais-je contourner cela?
Chris
Quelle est la signification du «caractère» littéral de la chaîne? Cette chaîne spécifique doit-elle être utilisée?
Jon Schneider
299

Voici une solution jQuery:

$.fn.selectRange = function(start, end) {
    if(end === undefined) {
        end = start;
    }
    return this.each(function() {
        if('selectionStart' in this) {
            this.selectionStart = start;
            this.selectionEnd = end;
        } else if(this.setSelectionRange) {
            this.setSelectionRange(start, end);
        } else if(this.createTextRange) {
            var range = this.createTextRange();
            range.collapse(true);
            range.moveEnd('character', end);
            range.moveStart('character', start);
            range.select();
        }
    });
};

Avec cela, vous pouvez faire

$('#elem').selectRange(3,5); // select a range of text
$('#elem').selectRange(3); // set cursor position
mpen
la source
2
@Jesse: Je ne sais pas comment cela s'est produit, j'utilise habituellement 4. Correction.
mpen
1
@UberNeet: mis à jour en fonction de votre suggestion.
mpen
1
@Enve: Je n'ai pas de copie d'IE 5.5 pour tester, mais ce serait probablement parce que jQuery ne prend pas en charge IE 5.5 .
mpen
1
@ JaroslavZáruba: Oui. C'est. Mais vous permet de ne pas avoir à écrire selectRange($('.my_input')[0], 3, 5)si vous utilisez déjà jQuery. De plus, il devrait fonctionner avec plus d'un élément, si vous en avez besoin, pour une raison quelconque. Si vous voulez du natif pur, veuillez utiliser la solution de CMS.
mpen
2
J'ai dû ajouter $('#elem').focus()au préalable pour faire apparaître le curseur clignotant.
mareoraft
37

Les solutions ici sont correctes, sauf pour le code d'extension jQuery.

La fonction d'extension doit parcourir chaque élément sélectionné et revenir thisau support du chaînage. Voici la une version correcte:

$.fn.setCursorPosition = function(pos) {
  this.each(function(index, elem) {
    if (elem.setSelectionRange) {
      elem.setSelectionRange(pos, pos);
    } else if (elem.createTextRange) {
      var range = elem.createTextRange();
      range.collapse(true);
      range.moveEnd('character', pos);
      range.moveStart('character', pos);
      range.select();
    }
  });
  return this;
};
HRJ
la source
4
La fonction each renvoie l'objet jquery. de sorte que vous pouvez réellement faire: return this.each(function...)et supprimer la ligne autonome.
jhummel
23

J'ai trouvé une solution qui fonctionne pour moi:

$.fn.setCursorPosition = function(position){
    if(this.length == 0) return this;
    return $(this).setSelection(position, position);
}

$.fn.setSelection = function(selectionStart, selectionEnd) {
    if(this.length == 0) return this;
    var input = this[0];

    if (input.createTextRange) {
        var range = input.createTextRange();
        range.collapse(true);
        range.moveEnd('character', selectionEnd);
        range.moveStart('character', selectionStart);
        range.select();
    } else if (input.setSelectionRange) {
        input.focus();
        input.setSelectionRange(selectionStart, selectionEnd);
    }

    return this;
}

$.fn.focusEnd = function(){
    this.setCursorPosition(this.val().length);
            return this;
}

Vous pouvez maintenant déplacer le focus à la fin de n'importe quel élément en appelant:

$(element).focusEnd();

Ou vous spécifiez la position.

$(element).setCursorPosition(3); // This will focus on the third character.
AVProgrammer
la source
3
Pour les éléments de zone de texte, une amélioration de focusEnd consiste à ajouter this.scrollTop(this[0].scrollHeight);, pour garantir que la zone de texte défile pour rendre le point d'insertion visible.
Drew
12

Cela a fonctionné pour moi sur Safari 5 sur Mac OSX, jQuery 1.4:

$("Selector")[elementIx].selectionStart = desiredStartPos; 
$("Selector")[elementIx].selectionEnd = desiredEndPos;
BobFromBris
la source
pour moi cela ne fonctionnait pas bien avec un accès direct, mais cela fonctionnait parfaitement. $ (monID) .prop ('selectionStart', position); $ (monID) .prop ('selectionEnd', position);
amdan
9

Je me rends compte que c'est un très ancien article, mais j'ai pensé que je devrais peut-être proposer une solution plus simple pour le mettre à jour en utilisant uniquement jQuery.

function getTextCursorPosition(ele) {   
    return ele.prop("selectionStart");
}

function setTextCursorPosition(ele,pos) {
    ele.prop("selectionStart", pos + 1);
    ele.prop("selectionEnd", pos + 1);
}

function insertNewLine(text,cursorPos) {
    var firstSlice = text.slice(0,cursorPos);
    var secondSlice = text.slice(cursorPos);

    var new_text = [firstSlice,"\n",secondSlice].join('');

    return new_text;
}

Utilisation pour utiliser ctrl-enter pour ajouter une nouvelle ligne (comme dans Facebook):

$('textarea').on('keypress',function(e){
    if (e.keyCode == 13 && !e.ctrlKey) {
        e.preventDefault();
        //do something special here with just pressing Enter
    }else if (e.ctrlKey){
        //If the ctrl key was pressed with the Enter key,
        //then enter a new line break into the text
        var cursorPos = getTextCursorPosition($(this));                

        $(this).val(insertNewLine($(this).val(), cursorPos));
        setTextCursorPosition($(this), cursorPos);
    }
});

Je suis ouvert à la critique. Je vous remercie.

MISE À JOUR: Cette solution ne permet pas aux fonctionnalités normales de copier-coller de fonctionner (c'est-à-dire ctrl-c, ctrl-v), donc je devrai les modifier à l'avenir pour m'assurer que la pièce fonctionne à nouveau. Si vous avez une idée de la façon de procéder, veuillez commenter ici, et je serai heureux de le tester. Merci.

tofirius
la source
7

Dans IE pour déplacer le curseur sur une position, ce code suffit:

var range = elt.createTextRange();
range.move('character', pos);
range.select();

la source
7

Réglez le focus avant d'avoir inséré le texte dans la zone de texte ainsi?

$("#comments").focus();
$("#comments").val(comments);
Steven Whitby
la source
6

Cela fonctionne pour moi en chrome

$('#input').focus(function() {
    setTimeout( function() {
        document.getElementById('input').selectionStart = 4;
        document.getElementById('input').selectionEnd = 4;
    }, 1);
});

Apparemment, vous avez besoin d'un délai d'une microseconde ou plus, car généralement un utilisateur se concentre sur le champ de texte en cliquant à une position dans le champ de texte (ou en appuyant sur l'onglet) que vous souhaitez remplacer, vous devez donc attendre que la position soit défini par l'utilisateur, cliquez dessus, puis modifiez-le.

Hung Tran
la source
L'affectation à des propriétés en lecture seule n'est pas autorisée en mode strict
Ivan Rubinson
4

N'oubliez pas de renvoyer false juste après l'appel de fonction si vous utilisez les touches fléchées, car Chrome efface le problème autrement.

{
    document.getElementById('moveto3').setSelectionRange(3,3);
    return false;
}
erich
la source
2
Ce n'est pas la meilleure pratique de le faire return false;. Vous voulez event.preventDefault();plutôt. Si vous retournez faux, vous sous-entendez ce event.stopPropagation()qui n'est pas toujours souhaitable
Alan H.
4

Sur la base de cette question , la réponse ne fonctionnera pas parfaitement pour ie et opera lorsqu'il y a une nouvelle ligne dans la zone de texte. La réponse explique comment ajuster le selectionStart, selectionEnd avant d'appeler setSelectionRange.

J'ai essayé le adjustOffset de l'autre question avec la solution proposée par @AVProgrammer et ça marche.

function adjustOffset(el, offset) {
    /* From https://stackoverflow.com/a/8928945/611741 */
    var val = el.value, newOffset = offset;
    if (val.indexOf("\r\n") > -1) {
        var matches = val.replace(/\r\n/g, "\n").slice(0, offset).match(/\n/g);
        newOffset += matches ? matches.length : 0;
    }
    return newOffset;
}

$.fn.setCursorPosition = function(position){
    /* From https://stackoverflow.com/a/7180862/611741 */
    if(this.lengh == 0) return this;
    return $(this).setSelection(position, position);
}

$.fn.setSelection = function(selectionStart, selectionEnd) {
    /* From https://stackoverflow.com/a/7180862/611741 
       modified to fit https://stackoverflow.com/a/8928945/611741 */
    if(this.lengh == 0) return this;
    input = this[0];

    if (input.createTextRange) {
        var range = input.createTextRange();
        range.collapse(true);
        range.moveEnd('character', selectionEnd);
        range.moveStart('character', selectionStart);
        range.select();
    } else if (input.setSelectionRange) {
        input.focus();
        selectionStart = adjustOffset(input, selectionStart);
        selectionEnd = adjustOffset(input, selectionEnd);
        input.setSelectionRange(selectionStart, selectionEnd);
    }

    return this;
}

$.fn.focusEnd = function(){
    /* From https://stackoverflow.com/a/7180862/611741 */
    this.setCursorPosition(this.val().length);
}
Ghislain Hivon
la source
4

Petite modification au code que j'ai trouvé dans bitbucket

Le code est maintenant en mesure de sélectionner / surligner avec des points de début / fin si 2 positions sont données. Testé et fonctionne très bien dans FF / Chrome / IE9 / Opera.

$('#field').caret(1, 9);

Le code est répertorié ci-dessous, seules quelques lignes ont été modifiées:

(function($) {
  $.fn.caret = function(pos) {
    var target = this[0];
    if (arguments.length == 0) { //get
      if (target.selectionStart) { //DOM
        var pos = target.selectionStart;
        return pos > 0 ? pos : 0;
      }
      else if (target.createTextRange) { //IE
        target.focus();
        var range = document.selection.createRange();
        if (range == null)
            return '0';
        var re = target.createTextRange();
        var rc = re.duplicate();
        re.moveToBookmark(range.getBookmark());
        rc.setEndPoint('EndToStart', re);
        return rc.text.length;
      }
      else return 0;
    }

    //set
    var pos_start = pos;
    var pos_end = pos;

    if (arguments.length > 1) {
        pos_end = arguments[1];
    }

    if (target.setSelectionRange) //DOM
      target.setSelectionRange(pos_start, pos_end);
    else if (target.createTextRange) { //IE
      var range = target.createTextRange();
      range.collapse(true);
      range.moveEnd('character', pos_end);
      range.moveStart('character', pos_start);
      range.select();
    }
  }
})(jQuery)
brindille
la source
Fonctionne dans Chrome 39, IE11, Safari 5.1.7 mais pas dans Firefox 34: jsfiddle.net/0t94z82k/6
jbobbins
3

J'ai dû faire fonctionner cela pour les éléments modifiables et jQuery et j'ai pensé que quelqu'un pourrait le vouloir prêt à utiliser:

$.fn.getCaret = function(n) {
    var d = $(this)[0];
    var s, r;
    r = document.createRange();
    r.selectNodeContents(d);
    s = window.getSelection();
    console.log('position: '+s.anchorOffset+' of '+s.anchorNode.textContent.length);
    return s.anchorOffset;
};

$.fn.setCaret = function(n) {
    var d = $(this)[0];
    d.focus();
    var r = document.createRange();
    var s = window.getSelection();
    r.setStart(d.childNodes[0], n);
    r.collapse(true);
    s.removeAllRanges();
    s.addRange(r);
    console.log('position: '+s.anchorOffset+' of '+s.anchorNode.textContent.length);
    return this;
};

L'utilisation $(selector).getCaret()renvoie l'offset numérique, $(selector).setCaret(num)établit l'offset et définit le focus sur l'élément.

Également un petit conseil, si vous exécutez à $(selector).setCaret(num)partir de la console, il retournera le console.log mais vous ne visualiserez pas le focus car il est établi dans la fenêtre de la console.

Bests; D

FGZ
la source
1

Vous pouvez modifier directement le prototype si setSelectionRange n'existe pas.

(function() {
    if (!HTMLInputElement.prototype.setSelectionRange) {
        HTMLInputElement.prototype.setSelectionRange = function(start, end) {
            if (this.createTextRange) {
                var range = this.createTextRange();
                this.collapse(true);
                this.moveEnd('character', end);
                this.moveStart('character', start);
                this.select();
            }
        }
    }
})();
document.getElementById("input_tag").setSelectionRange(6, 7);

Lien jsFiddle

Anoop
la source