jQuery Definir posição do cursor na área de texto

435

Como você define a posição do cursor em um campo de texto usando o jQuery? Eu tenho um campo de texto com conteúdo e quero que o cursor do usuário seja posicionado em um determinado deslocamento quando se concentrar no campo. O código deve ficar assim:

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

Como seria a implementação dessa função setCursorPosition? Se você tivesse um campo de texto com o conteúdo abcdefg, essa chamada resultaria no posicionamento do cursor da seguinte maneira: abcd ** | ** efg.

Java tem uma função semelhante, setCaretPosition. Existe um método semelhante para javascript?

Atualização: modifiquei o código do CMS para funcionar com o jQuery da seguinte maneira:

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
fonte
78
$(this).get(0).setSelectionRange)? Você sabe que é exatamente o mesmo que this.setSelectionRange, apenas mais lento e mais difícil de ler, certo? O jQuery não está literalmente fazendo nada por você aqui.
bobince
2
Para adicionar ao comentário @bobince, a função deve iterar para cada um dos elementos selecionados e retornar isso. O código correto está na minha resposta.
HRJ
21
O @bobince também não é muito correto. 'this' não é o nó DOM, mas o objeto jQuery. Portanto, $ (this) .get (0) .setSelectionRange é igual a this.get (0) .setSelectionRange, não é o mesmo que this.setSelectionRange.
Prestaul
$ (this) [0] é mais rápido que $ (this) .get (0)
EminezArtus 1/14
Confira este tutorial para obter uma solução completa. Você está em
Home

Respostas:

254

Eu tenho duas funções:

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

Então você pode usar o setCaretToPos assim:

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

Exemplo ao vivo com a textareae an input, mostrando o uso do jQuery:

A partir de 2016, testou e trabalhou no Chrome, Firefox, IE11 e até o IE8 (veja o último aqui ; o Stack Snippets não suporta o IE8).

CMS
fonte
3
Por que o recolhimento (verdadeiro) seria necessário, pois você definiria o final e iniciaria as compensações de seleção?
Alexis Wilke
@mareoraft: Funciona em textarea(e input) para mim no Chrome, Firefox, IE8 e IE11.
TJ Crowder
Parece que não consigo fazer isso funcionar com meu script. Eu tenho uma área de texto que está vazia no carregamento da página e preenchida por javascript conforme o aplicativo é usado. Desejo que o cursor seja retornado a 0 antes de cada nova gravação (um registro do uso). são os dados dinâmicos que estão me causando problemas? Se sim, como eu poderia contornar isso?
Chris
Qual é o significado da string literal 'character'? Essa cadeia específica precisa ser usada?
Jon Schneider
299

Aqui está uma solução 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();
        }
    });
};

Com isso, você pode fazer

$('#elem').selectRange(3,5); // select a range of text
$('#elem').selectRange(3); // set cursor position
mpen
fonte
2
@ Jess: Não sei como isso aconteceu, eu costumo usar 4. Corrigido.
MPEN
1
@UberNeet: atualizado com base em sua sugestão.
MPEN
1
@ Enve: Eu não tenho uma cópia do IE 5.5 para testar, mas isso provavelmente seria porque o jQuery não suporta o IE 5.5 .
MPEN
1
@ JaroslavZáruba: Sim. Isto é. Mas permite que você não precise escrever selectRange($('.my_input')[0], 3, 5)se já estiver usando o jQuery. Além disso, ele deve funcionar com mais de um elemento, se você precisar, por qualquer motivo. Se você quiser nativo puro, use a solução do CMS.
MPEN
2
Eu precisava adicionar $('#elem').focus()antes para que o cursor piscasse para aparecer.
mareoraft
37

As soluções aqui estão corretas, exceto pelo código de extensão jQuery.

A função de extensão deve percorrer cada elemento selecionado e retornar thisao suporte ao encadeamento. Aqui está a uma versão correta:

$.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
fonte
4
A função each retorna o objeto jquery. para que você possa realmente fazer: return this.each(function...)e remover a linha independente.
Jhummel
23

Encontrei uma solução que funciona para mim:

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

Agora você pode mover o foco para o final de qualquer elemento chamando:

$(element).focusEnd();

Ou você especifica a posição.

$(element).setCursorPosition(3); // This will focus on the third character.
AVProgrammer
fonte
3
Para elementos da área de texto, uma melhoria no focusEnd é adicionar this.scrollTop(this[0].scrollHeight);, para garantir que a área de texto seja rolada para tornar o ponto de inserção visível.
Tirou
12

Isso funcionou para mim no Safari 5 no Mac OSX, jQuery 1.4:

$("Selector")[elementIx].selectionStart = desiredStartPos; 
$("Selector")[elementIx].selectionEnd = desiredEndPos;
BobFromBris
fonte
para mim, que não estava funcionando bem com acesso direto, mas funcionou perfeitamente. $ (myID) .prop ('selectionStart', position); $ (myID) .prop ('selectionEnd', posição);
amdan 18/04
9

Percebo que este é um post muito antigo, mas achei que talvez eu devesse oferecer uma solução mais simples para atualizá-lo usando apenas 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 adicionar uma nova linha (como no 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);
    }
});

Estou aberto à crítica. Obrigado.

ATUALIZAÇÃO: Esta solução não permite que a funcionalidade normal de copiar e colar funcione (por exemplo, ctrl-c, ctrl-v), por isso terei que editar isso no futuro para garantir que a peça funcione novamente. Se você tem uma idéia de como fazer isso, comente aqui e terei prazer em testá-lo. Obrigado.

tofirius
fonte
7

No IE para mover o cursor em alguma posição, este código é suficiente:

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

fonte
7

Defina o foco antes de inserir o texto na área de texto assim?

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

Isso funciona para mim no chrome

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

Aparentemente, você precisa de um atraso de um microssegundo ou mais, porque geralmente um usuário se concentra no campo de texto clicando em alguma posição no campo de texto (ou pressionando a guia) que deseja substituir, portanto, é necessário aguardar até que a posição seja definido pelo usuário, clique e altere-o.

Hung Tran
fonte
Propriedades atribuição de ler-apenas não é permitido no modo estrito
Ivan Rubinson
4

Lembre-se de retornar falso logo após a chamada da função, se você estiver usando as teclas de seta, pois o Chrome facilita o processo.

{
    document.getElementById('moveto3').setSelectionRange(3,3);
    return false;
}
erich
fonte
2
Não é uma prática recomendada return false;. Você quer, em event.preventDefault();vez disso. Se você retornar falso, você está implicando event.stopPropagation()que nem sempre é desejável
Alan H.
4

Com base nessa pergunta , a resposta não funcionará perfeitamente para ie e opera quando houver nova linha na área de texto. A resposta explica como ajustar o selectionStart, selectionEnd antes de chamar setSelectionRange.

Eu tentei o AdjustOffset da outra pergunta com a solução proposta pelo @AVProgrammer e ele 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
fonte
4

Pequena modificação no código que encontrei no bitbucket

O código agora pode selecionar / destacar com pontos de início / fim se houver duas posições. Testado e funciona bem no FF / Chrome / IE9 / Opera.

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

O código está listado abaixo, apenas algumas linhas foram alteradas:

(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)
galho
fonte
Executa no Chrome 39, IE11, Safari 5.1.7, mas não no Firefox 34: jsfiddle.net/0t94z82k/6
jbobbins
3

Eu tive que fazer isso funcionar para elementos editáveis ​​por conteúdo e jQuery e pensei que alguém poderia querer que estivesse pronto 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;
};

O uso $(selector).getCaret()retorna o deslocamento do número e $(selector).setCaret(num)estabelece o deslocamento e define o foco no elemento.

Também é uma pequena dica: se você executar a $(selector).setCaret(num)partir do console, ele retornará o console.log, mas você não visualizará o foco, pois ele é estabelecido na janela do console.

Bests; D

FGZ
fonte
1

Você pode alterar diretamente o protótipo se setSelectionRange não existir.

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

jsFiddle link

Anoop
fonte