Как получить элемент, когда выделен весь объемный текст?

#html #contenteditable

#HTML #contenteditable

Вопрос:

В редактируемом содержимом div я хочу окружить выделенный текст символом <span> или изменить существующий <span> , когда я выделяю весь текст внутри него. Я могу сделать первое, но не второе.

Во втором случае, если у меня есть A<span>B</span>C , и я выбираю B , в выделении отображается anchorNode as A , focusNode as C и toString() возвращает B . Родительский узел — это заключающий абзац, а не <span> . Я не могу найти никакого способа отличить выбор B in A<span>B</span>C и in ABC , поскольку я не могу найти никакого способа обнаружить существование <span> элемента, который окружает B в первом случае. Должен же быть способ сделать это, не так ли? Может кто-нибудь сказать мне, как это сделать?

Вот код:

 var sel = window.getSelection();
var range = sel.getRangeAt(0);
var selected = range.compareBoundaryPoints(range.START_TO_END, range);
if (selected > 0) {    // some text is selected
  var el = range.commonAncestorContainer;
  //
  // would expect to find el == <span> if selection is B
  // in <p>A<span>B</span>C<p>, but find <p> instead!
  //
  //... code to surround selection with new span or to process
  //    existing span... but there is never an existing span!
}
  

Комментарии:

1. В какой среде вы пытаетесь это сделать? У вас есть еще код?

2. Добавлен некоторый код выше. Что я делаю: использую Firefox, дважды щелкаю по слову в абзаце внутри редактируемого содержимого div, добавляю интервал (который работает), затем дважды щелкаю по тому же слову, пытаюсь изменить интервал, который я только что вставил, и устанавливаю точку останова в отладчике браузера сразу послеприсвоение «el». Значение «el» является диапазоном, ТОЛЬКО если я нажимаю кнопку, чтобы изменить диапазон сразу после его вставки — если я щелкну где-нибудь еще, затем вернусь и дважды щелкните слово еще раз, оно не сможет найти диапазон, даже если выбранный текст полностью находится внутри него.

3. Поскольку он не находит диапазон, я получаю <p>A <span><span>B</span></span> C</p> .

Ответ №1:

Range.commonAncestorContainer имеет свойство parentElement . Это родительский элемент выделенного текста.


Во фрагменте кода и Лондон, и Париж обернуты элементом span .

 const text = "<p>Hello from <span>London</span> and <span>Paris</span></p>"
const parser = new DOMParser();
const html = parser.parseFromString(text, "text/html").body.innerHTML
const editor = document.querySelector('div[contenteditable]')
editor.innerHTML = html


function getParent() {
  var selection = window.getSelection();
  if (selection.rangeCount) {
    const range = selection.getRangeAt(0);
    const parent = range.commonAncestorContainer.parentElement;

    document.querySelector("#result").innerText = parent instanceof HTMLSpanElement
    if (parent instanceof HTMLSpanElement) {
      console.log(parent)
    }
  }
}


document.querySelector("button").onclick = getParent;  
 <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/css/bootstrap.min.css" rel="stylesheet" />
<div class="container py-5">
  <div class="row align-items-center">
    <div class="col">
      <div class="w-100 p-5 border" contenteditable>

      </div>
    </div>
    <div class="col">
      <button class="btn btn-primary">Get Text</button>
    </div>
  </div>
  <div class="row align-items-center">
    <div class="col">
      <p>Is Is parent instanceof HTMLSpanElement?  <span id="result"></span> </p>
    </div>
  </div>
</div>  

Комментарии:

1. Это работает только до тех пор, пока вы выбираете текст в пределах диапазона, а не все содержимое диапазона. Например. если вы выбираете все слово «Лондон», общим предком является <span><p>, который заключает в , а parentElement — это тогда заключающий <div> из> . Я пытаюсь разобраться со случаем, когда выделение представляет собой содержимое всего диапа зона. Кажется, что когда вы выбираете «Лондон» в приведенном выше, вы на самом деле выбираете «<span>Лондон</span>», и, похоже, нет простого способа отличить «Лондон» от «<span>Лондон</span>»

2. Рассмотрите возможность изменения цвета шрифта путем переноса текста <span>. Если текст не является содержимым всего диапазона (например, «ondo» внутри «London»), тогда я оборачиваю его, чтобы дать, например, <span> L <span> ondo</spa n> n</span> . Если я выберу весь <span> <span> диапазон, я получу Лондон, который все еще синий</span></span>, потому что я оборачиваю за пределы существующ его диапазона, а не внутри. В этом случае я хочу просто изменить охватывающий диапазон с синего на красный.

3. @user1636349 London — это экземпляр текста, а <span>London</span> — экземпляр HTMLS panElement . Если вы добавите все сценарии с примерами [действие, ожидаемый результат], я вам помогу. Если вы это сделаете, включите его в свой вопрос.

4. Ну, в вашем примере, если вы выберете слово «Лондон» и нажмете «Получить текст», ничего не произойдет (потому что выбран весь диапазон, а его родительским элементом является <p> ), но если вы выберете «ondo» из середины слова, там будет написано «<span>Лон дон</span>». Мне бы хотелось, чтобы при выборе «ondo» в середине слова было написано «ondo», а при выборе «Лондон» отображалось «<span>Лондон</span>».

5. Как я уже сказал ниже, у меня есть обходной путь, но это полная ошибка. Так что более аккуратное решение было бы высоко оценено!

Ответ №2:

Я придумал довольно ужасный обходной путь для вышеупомянутой проблемы. Чтобы проверить, является ли выделение целым <span> элементом, я клонирую диапазон, который, когда это весь диапазон, содержит диапазон, которому предшествуют и за которым следуют пустые строки. Итак, что я делаю, это удаляю любой из них и проверяю, остался ли у меня один элемент span. Если это так, я беру следующий элемент после начала исходного диапазона, который будет <span> :

 var el = range.commonAncestorContainer;
var copy = range.cloneContents();
while (copy.childNodes.length > 1 amp;amp; copy.firstChild.nodeValue === "") {
  copy.removeChild(copy.firstChild);
}
while (copy.childNodes.length > 1 amp;amp; copy.lastChild.nodeValue === "") {
  copy.removeChild(copy.lastChild);
}
if (copy.childNodes.length == 1 amp;amp; copy.childNodes[0] instanceof HTMLSpanElement) {
  el = range.startContainer.nextSibling;
}
  

Это работает, но не очень привлекательно. Я был бы рад, если у кого-нибудь есть более аккуратное решение.