Programistycznie zaznaczać tekst w edytowalnym z treścią elemencie HTML?

119

W JavaScript można programowo zaznaczyć tekst w elemencie inputlub textarea. Możesz ipt.focus()zaznaczyć wejście za pomocą , a następnie wybrać jego zawartość za pomocą ipt.select(). Możesz nawet wybrać konkretny zakres za pomocą ipt.setSelectionRange(from,to).

Moje pytanie brzmi: czy istnieje sposób, aby to zrobić również w contenteditableelemencie?

Odkryłem, że mogę to zrobić elem.focus(), aby umieścić kursor w contenteditableelemencie, ale później uruchomienie elem.select()nie działa (i nie działa setSelectionRange). Nie mogę znaleźć nic na ten temat w sieci, ale może szukam niewłaściwej rzeczy ...

Nawiasem mówiąc, jeśli to robi jakąkolwiek różnicę, potrzebuję go tylko do pracy w Google Chrome, ponieważ to jest dla rozszerzenia Chrome.

callum
źródło

Odpowiedzi:

170

Jeśli chcesz zaznaczyć całą zawartość elementu (z możliwością edycji treści lub nie) w Chrome, oto jak to zrobić. Działa to również w przeglądarkach Firefox, Safari 3+, Opera 9+ (prawdopodobnie także we wcześniejszych wersjach) i IE 9. Możesz także tworzyć zaznaczenia aż do poziomu postaci. Potrzebne interfejsy API to DOM Range (aktualna specyfikacja to DOM Level 2 , zobacz także MDN ) i Selection, która jest określana jako część nowej specyfikacji Range ( dokumentacja 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
źródło
15
Aby uzyskać dodatkową kompatybilność, powinieneś wezwać selectElementContents()a setTimeout()lub requestAnimationFrame()jeśli wywołano z onfocus. Zobacz jsfiddle.net/rudiedirkx/MgASG/1/show
Rudie
@Dylan: Nie jestem pewien: pytanie wspomina, że ​​OP już używa focus().
Tim Down
4
@Rudie Zgodność z którą aplikacją?
yckart
Działa świetnie na komputerze. W przeglądarkach mobilnych nie działa. Nie dokonano wyboru. Wypróbowałem Safari i Chrome na iPhonie iOS 11.
campbell
1
@campbell: Działa w Safari przynajmniej na iOS, pod warunkiem , że masz już wybór . W przeciwnym razie nie, przeglądarka po prostu nie pozwala JavaScriptowi pokazywać wyboru, prawdopodobnie ze względu na wygodę użytkownika.
Tim Down
34

Oprócz odpowiedzi Tima Downsa stworzyłem rozwiązanie, które działa nawet w oldIE:

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;​

Przetestowano w IE 8+, Firefox 3+, Opera 9+ i Chrome 2+. Nawet skonfigurowałem go we wtyczce 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();
});

... a kogo to interesuje, oto to samo dla wszystkich miłośników kawy:

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

Aktualizacja:

Jeśli chcesz zaznaczyć całą stronę lub zawartość edytowalnego regionu (oznaczonego flagą contentEditable), możesz to zrobić znacznie prościej, przełączając się na designModei używając document.execCommand:

W MDN jest dobry punkt wyjścia i niewielka dokumentacja .

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

(działa dobrze w IE6 +, Opera 9+, Firefoy 3+, Chrome 2+) http://caniuse.com/#search=execCommand

yckart
źródło
10

Ponieważ wszystkie istniejące odpowiedzi dotyczą divelementów, wyjaśnię, jak to zrobić za pomocą spans.

Istnieje subtelna różnica podczas wybierania zakresu tekstu w pliku span. Aby móc przekazać indeks początkowy i końcowy tekstu, musisz użyć Textwęzła, jak opisano tutaj :

Jeśli startNode jest węzłem typu Text, Comment lub CDATASection, to startOffset to liczba znaków od początku startNode. W przypadku innych typów węzłów startOffset to liczba węzłów podrzędnych między początkiem węzła 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
źródło
Naprawdę powinno być: r.setStart(e.firstChild,0); r.setEnd(e.lastChild,e.lastChild.textContent.length); Oczywiście powinieneś sprawdzić, czy e.firstChild w rzeczywistości nie jest zerowe.
Yorick
2
Nie ma różnicy między dokonaniem wyboru w a <div>i <span>elemencie. Przynajmniej nie tak, jak opisujesz.
Tim Down
Istnieją różnice między div i span, w niektórych przypadkach rozwiązanie div nie działa poprawnie w span. Na przykład, jeśli zaznaczysz tekst programowo za pomocą rozwiązania div, a następnie wkleisz nową zawartość, zastąpi on nie cały tekst, tylko część i istnieją różnice między chrome i firefox
neosonne
6

Rangy pozwala ci to zrobić w różnych przeglądarkach z tym samym kodem. Rangy to implementacja metod DOM do selekcji w różnych przeglądarkach. Jest dobrze przetestowany i sprawia, że ​​jest to o wiele mniej bolesne. Nie chcę dotykać treści do edycji bez tego.

Możesz znaleźć rangy tutaj:

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

Mając rangy w swoim projekcie, zawsze możesz to napisać, nawet jeśli przeglądarka to IE 8 lub starsza i ma zupełnie inny natywny interfejs API do wyboru:

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

Gdzie „contentEditableNode” to węzeł DOM z atrybutem contenteditable. Możesz to pobrać w ten sposób:

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

Lub jeśli jQuery jest już częścią twojego projektu i uważasz to za wygodne:

var contentEditable = $('.some-selector')[0];
Tom Boutell
źródło
Projekt Rangy został teraz przeniesiony na Github: github.com/timdown/rangy
tanius
4

Taki jest nowoczesny sposób robienia rzeczy. Więcej informacji o MDN

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


źródło
Dzięki, to działa świetnie! Fwiw, że strona MDN oznacza tę technologię jako eksperymentalną. Ale działa w aktualnej wersji Chrome i FF w czerwcu 2020 r.
JohnK
2

[Zaktualizowano, aby naprawić błąd]

Oto przykład zaadaptowany na podstawie tej odpowiedzi, który wydaje się działać dobrze w przeglądarce Chrome - Wybierz zakres w edytowalnym div

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 to:

<div id="myText" contenteditable>test</div>
patorjk
źródło