Selecionar texto programaticamente em um elemento HTML editável de conteúdo?

119

Em JavaScript, é possível selecionar texto de forma programática em um elemento inputou textarea. Você pode focar uma entrada com ipt.focus()e selecionar seu conteúdo com ipt.select(). Você pode até selecionar um intervalo específico com ipt.setSelectionRange(from,to).

Minha pergunta é: existe alguma maneira de fazer isso em um contenteditableelemento também?

Descobri que posso fazer elem.focus(), colocar o cursor em um contenteditableelemento, mas posteriormente a execução elem.select()não funciona (e nem funciona setSelectionRange). Não consigo encontrar nada sobre isso na web, mas talvez eu esteja procurando a coisa errada ...

Aliás, se faz alguma diferença, só preciso que funcione no Google Chrome, pois se trata de uma extensão do Chrome.

callum
fonte

Respostas:

170

Se você deseja selecionar todo o conteúdo de um elemento (editável ou não) no Chrome, veja como. Isso também funcionará no Firefox, Safari 3+, Opera 9+ (possivelmente versões anteriores também) e IE 9. Você também pode criar seleções até o nível do personagem. As APIs de que você precisa são DOM Range (a especificação atual é DOM Nível 2 , consulte também MDN ) e Selection, que está sendo especificado como parte de uma nova especificação de Range ( documentos MDN ).

function selectElementContents(el) {
    var range = document.createRange();
    range.selectNodeContents(el);
    var sel = window.getSelection();
    sel.removeAllRanges();
    sel.addRange(range);
}

var el = document.getElementById("foo");
selectElementContents(el);
Tim Down
fonte
15
Para obter compatibilidade extra, você deve chamar selectElementContents()a setTimeout()ou requestAnimationFrame()se for chamado de onfocus. Veja jsfiddle.net/rudiedirkx/MgASG/1/show
Rudie
@Dylan: Não tenho certeza: a pergunta menciona que o OP já está usando focus().
Tim Down de
4
Compatibilidade do @Rudie para qual aplicativo?
Yckart de
Funciona muito bem no desktop. Em navegadores móveis, não funciona. Nenhuma seleção feita. Experimentei o Safari e o Chrome no iPhone iOS 11.
campbell
1
@campbell: Funciona no Safari pelo menos no iOS, desde que você já tenha uma seleção . Caso contrário, não, o navegador simplesmente não permite que o JavaScript mostre uma seleção, provavelmente por motivos de experiência do usuário.
Tim Down
34

Além da resposta de Tim Downs , criei uma solução que funciona até no antigo IE:

var selectText = function() {
  var range, selection;
  if (document.body.createTextRange) {
    range = document.body.createTextRange();
    range.moveToElementText(this);
    range.select();
  } else if (window.getSelection) {
    selection = window.getSelection();
    range = document.createRange();
    range.selectNodeContents(this);
    selection.removeAllRanges();
    selection.addRange(range);
  }
};

document.getElementById('foo').ondblclick = selectText;​

Testado em IE 8+, Firefox 3+, Opera 9+ e Chrome 2+. Até eu o configurei em um plugin jQuery:

jQuery.fn.selectText = function() {
  var range, selection;
  return this.each(function() {
    if (document.body.createTextRange) {
      range = document.body.createTextRange();
      range.moveToElementText(this);
      range.select();
    } else if (window.getSelection) {
      selection = window.getSelection();
      range = document.createRange();
      range.selectNodeContents(this);
      selection.removeAllRanges();
      selection.addRange(range);
    }
  });
};

$('#foo').on('dblclick', function() {
  $(this).selectText();
});

... e quem está interessado em, aqui está o mesmo para todos os viciados em café:

jQuery.fn.selectText = ->
  @each ->
    if document.body.createTextRange
      range = document.body.createTextRange()
      range.moveToElementText @
      range.select()
    else if window.getSelection
      selection = window.getSelection()
      range = document.createRange()
      range.selectNodeContents @
      selection.removeAllRanges()
      selection.addRange range
    return

Atualizar:

Se quiser selecionar a página inteira ou o conteúdo de uma região editável (marcada com contentEditable), você pode fazer isso de forma muito mais simples alternando para designModee usando document.execCommand:

Há um bom ponto de partida no MDN e uma pequena documentação .

var selectText = function () {
  document.execCommand('selectAll', false, null);
};

(funciona bem no IE6 +, Opera 9+, Firefoy 3+, Chrome 2+) http://caniuse.com/#search=execCommand

Yckart
fonte
10

Como todas as respostas existentes lidam com divelementos, explicarei como fazer isso com spans.

Há uma diferença sutil ao selecionar um intervalo de texto em a span. Para poder passar o índice inicial e final do texto, você deve usar um Textnó, conforme descrito aqui :

Se startNode for um Nó do tipo Texto, Comentário ou CDATASection, startOffset é o número de caracteres desde o início de startNode. Para outros tipos de Nó, startOffset é o número de nós filhos entre o início do startNode.

var e = document.getElementById("id of the span element you want to select text in");
var textNode = e.childNodes[0]; //text node is the first child node of a span

var r = document.createRange();
var startIndex = 0;
var endIndex = textNode.textContent.length;
r.setStart(textNode, startIndex);
r.setEnd(textNode, endIndex);

var s = window.getSelection();
s.removeAllRanges();
s.addRange(r);
Domysee
fonte
Realmente deveria ser: r.setStart(e.firstChild,0); r.setEnd(e.lastChild,e.lastChild.textContent.length); claro que você deve verificar se e.firstChild não é realmente nulo.
Yorick
2
Não há diferença entre fazer uma seleção em a <div>e em um <span>elemento. Pelo menos, não como você descreve.
Tim Down
Existem diferenças entre div e span; em alguns casos, uma solução para div não funciona bem em span. Por exemplo, se você selecionar o texto programaticamente com solução div e colar o novo conteúdo, ele não substituirá o texto inteiro, apenas uma parte, e há diferenças entre chrome e firefox
neosonne
6

O Rangy permite que você faça esse cross-browser com o mesmo código. Rangy é uma implementação entre navegadores dos métodos DOM para seleções. É bem testado e torna isso muito menos doloroso. Recuso-me a tocar em conteúdo editável sem ele.

Você pode encontrar rangy aqui:

http://code.google.com/p/rangy/

Com o rangy em seu projeto, você sempre pode escrever isso, mesmo se o navegador for IE 8 ou anterior e tiver uma API nativa completamente diferente para as seleções:

var range = rangy.createRange();
range.selectNodeContents(contentEditableNode);
var sel = rangy.getSelection();
sel.removeAllRanges();
sel.addRange(range);

Onde "contentEditableNode" é o nó DOM que possui o atributo contenteditable. Você pode buscá-lo assim:

var contentEditable = document.getElementById('my-editable-thing');

Ou se jQuery já faz parte do seu projeto e você achar conveniente:

var contentEditable = $('.some-selector')[0];
Tom Boutell
fonte
O projeto Rangy mudou-se para Github agora: github.com/timdown/rangy
tanius
4

A maneira moderna de fazer as coisas é assim. Mais detalhes sobre MDN

document.addEventListener('dblclick', (event) => {
  window.getSelection().selectAllChildren(event.target)
})
<div contenteditable="true">Some text</div>


fonte
Obrigado, isso funciona muito bem! Fwiw, essa página MDN marca esta tecnologia como experimental. Mas funciona na versão atual do Chrome e FF em junho de 2020.
JohnK
2

[Atualizado para corrigir o erro]

Aqui está um exemplo adaptado a partir desta resposta que parece funcionar bem no Chrome - Selecione o intervalo em div contenteditable

var elm = document.getElementById("myText"),
    fc = elm.firstChild,
    ec = elm.lastChild,
    range = document.createRange(),
    sel;
elm.focus();
range.setStart(fc,1);
range.setEnd(ec,3);
sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);

HTML é:

<div id="myText" contenteditable>test</div>
patorjk
fonte