jQuery Establecer posición del cursor en el área de texto

435

¿Cómo se configura la posición del cursor en un campo de texto usando jQuery? Tengo un campo de texto con contenido, y quiero que el cursor de los usuarios se coloque en un cierto desplazamiento cuando se centran en el campo. El código debería verse así:

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

¿Cómo sería la implementación de esa función setCursorPosition? Si tuviera un campo de texto con el contenido abcdefg, esta llamada resultaría en la posición del cursor de la siguiente manera: abcd ** | ** efg.

Java tiene una función similar, setCaretPosition. ¿Existe un método similar para javascript?

Actualización: modifiqué el código de CMS para trabajar con jQuery de la siguiente manera:

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
fuente
78
$(this).get(0).setSelectionRange)? Sabes que es exactamente lo mismo this.setSelectionRange, pero más lento y más difícil de leer, ¿verdad? jQuery no está haciendo literalmente nada por usted aquí.
bobince
2
Para agregar al comentario @bobince, la función debe iterar para cada uno de los elementos seleccionados y devolver esto. El código correcto está en mi respuesta.
HRJ
21
@bobince tampoco es del todo correcto. 'this' no es el nodo DOM, sino el objeto jQuery. Entonces, $ (this) .get (0) .setSelectionRange es lo mismo que this.get (0) .setSelectionRange, no es lo mismo que this.setSelectionRange.
Prestaul
$ (this) [0] es más rápido que $ (this) .get (0)
EminezArtus
Consulte este tutorial para obtener una solución completa. webdesignpluscode.blogspot.com/2017/05/…
Waqas Ali

Respuestas:

254

Tengo dos funciones:

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);
}

Entonces puedes usar setCaretToPos de esta manera:

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

Ejemplo en vivo con a textareay an input, que muestra el uso de jQuery:

A partir de 2016, probado y trabajando en Chrome, Firefox, IE11, incluso IE8 (vea esto último aquí ; los fragmentos de pila no son compatibles con IE8).

CMS
fuente
3
¿Por qué sería necesario colapsar (verdadero) ya que va a establecer el final y comenzar las compensaciones de selección?
Alexis Wilke
@mareoraft: funciona en textarea(y input) para mí en Chrome, Firefox, IE8 e IE11.
TJ Crowder
Parece que no puedo hacer que esto funcione con mi script. Tengo un área de texto que está vacía en la carga de la página, luego se llena con javascript cuando se usa la aplicación. Quiero que el cursor se devuelva a 0 antes de cada nueva escritura (un registro del uso). ¿Son los datos dinámicos los que me están causando problemas? Si es así, ¿cómo podría evitar eso?
Chris
¿Cuál es el significado de la cadena literal 'carácter'? ¿Se necesita usar esa cadena específica?
Jon Schneider
299

Aquí hay una solución 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();
        }
    });
};

Con esto, puedes hacer

$('#elem').selectRange(3,5); // select a range of text
$('#elem').selectRange(3); // set cursor position
mpen
fuente
2
@Jesse: No sé cómo sucedió eso, generalmente uso 4. Solucionado.
mpen
1
@UberNeet: actualizado según su sugerencia.
mpen
1
@Enve: No tengo una copia de IE 5.5 para probar, pero eso probablemente se deba a que jQuery no es compatible con IE 5.5 .
mpen
1
@ JaroslavZáruba: Sí. Está. Pero le permite no tener que escribir selectRange($('.my_input')[0], 3, 5)si ya está usando jQuery. Además, debería funcionar con más de un elemento, si lo necesita, por cualquier razón. Si desea nativos puros, utilice la solución de CMS.
mpen
2
Necesitaba agregar de $('#elem').focus()antemano para que apareciera el cursor parpadeante.
mareoraft
37

Las soluciones aquí son correctas, excepto el código de extensión jQuery.

La función de extensión debe iterar sobre cada elemento seleccionado y volver thisa admitir el encadenamiento. Aquí es el de una versión correcta:

$.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
fuente
44
Cada función devuelve el objeto jquery. para que realmente pueda hacer: return this.each(function...)y eliminar la línea independiente.
jhummel
23

Encontré una solución que me funciona:

$.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;
}

Ahora puede mover el foco al final de cualquier elemento llamando a:

$(element).focusEnd();

O usted especifica la posición.

$(element).setCursorPosition(3); // This will focus on the third character.
AVProgrammer
fuente
3
Para los elementos de área de texto, una mejora para focusEnd es agregar this.scrollTop(this[0].scrollHeight);, para garantizar que el área de texto se desplace para hacer visible el punto de inserción.
Dibujó
12

Esto funcionó para mí en Safari 5 en Mac OSX, jQuery 1.4:

$("Selector")[elementIx].selectionStart = desiredStartPos; 
$("Selector")[elementIx].selectionEnd = desiredEndPos;
BobFromBris
fuente
para mí eso no funcionaba bien con un acceso directo, pero esto funcionó perfectamente. $ (myID) .prop ('selectionStart', posición); $ (myID) .prop ('selectionEnd', posición);
Amdan
9

Me doy cuenta de que esta es una publicación muy antigua, pero pensé que debería ofrecer una solución más simple para actualizarla usando solo 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;
}

Uso para usar ctrl-enter para agregar una nueva línea (como en 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);
    }
});

Estoy abierto a la crítica. Gracias.

ACTUALIZACIÓN: Esta solución no permite que funcione la funcionalidad normal de copiar y pegar (es decir, ctrl-c, ctrl-v), por lo que tendré que editar esto en el futuro para asegurarme de que la parte vuelva a funcionar. Si tiene una idea de cómo hacerlo, comente aquí, y con gusto lo probaré. Gracias.

tofirius
fuente
7

En IE para mover el cursor en alguna posición, este código es suficiente:

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

fuente
7

¿Establece el foco antes de insertar el texto en el área de texto?

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

Esto me funciona en cromo

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

Aparentemente necesita un retraso de un microsegundo o más, porque generalmente un usuario se enfoca en el campo de texto haciendo clic en alguna posición en el campo de texto (o presionando la pestaña) que desea anular, por lo que debe esperar hasta que la posición esté establecido por el usuario, haga clic y luego cámbielo.

Hung Tran
fuente
La asignación a propiedades de solo lectura no está permitida en modo estricto
Ivan Rubinson
4

Solo recuerde devolver falso justo después de la llamada a la función si está utilizando las teclas de flecha, ya que Chrome de lo contrario arruina el frack.

{
    document.getElementById('moveto3').setSelectionRange(3,3);
    return false;
}
erich
fuente
2
No es la mejor práctica return false;. Quieres en su event.preventDefault();lugar. Si devuelve falso, está insinuando lo event.stopPropagation()que no siempre es deseable
Alan H.
4

En base a esta pregunta , la respuesta no funcionará perfectamente para ie y opera cuando hay una nueva línea en el área de texto. La respuesta explica cómo ajustar el selectionStart, selectionEnd antes de llamar a setSelectionRange.

Probé el ajustarOffset de la otra pregunta con la solución propuesta por @AVProgrammer y funciona.

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
fuente
4

Pequeña modificación al código que encontré en bitbucket

El código ahora puede seleccionar / resaltar con puntos de inicio / finalización si se le dan 2 posiciones. Probado y funciona bien en FF / Chrome / IE9 / Opera.

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

El código se enumera a continuación, solo se modificaron algunas líneas:

(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)
ramita
fuente
Se ejecuta en Chrome 39, IE11, Safari 5.1.7 pero no en Firefox 34: jsfiddle.net/0t94z82k/6
jbobbins
3

Tenía que hacer que esto funcionara para elementos contentos y jQuery y pensé que alguien podría quererlo listo para usar:

$.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;
};

El uso $(selector).getCaret()devuelve el desplazamiento del número y $(selector).setCaret(num)establece el desplazamiento y establece el foco en el elemento.

También un pequeño consejo, si ejecuta $(selector).setCaret(num)desde la consola, devolverá el archivo console.log pero no visualizará el foco ya que está establecido en la ventana de la consola.

Bests; D

FGZ
fuente
1

Puede cambiar directamente el prototipo si setSelectionRange no existe.

(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);

Enlace jsFiddle

Anoop
fuente