Отримати початкове та кінцеве зміщення діапазону відносно батьківського контейнера


79

Припустимо, у мене є цей елемент HTML:

<div id="parent">
 Hello everyone! <a>This is my home page</a>
 <p>Bye!</p>
</div>

І користувач вибирає "будинок" за допомогою миші.

Я хочу мати можливість визначити, скільки символів у #parentйого відборі починається (і скільки символів з кінця #parentйого відбору закінчується). Це має спрацювати, навіть якщо він вибере тег HTML. (І мені це потрібно для роботи у всіх браузерах)

range.startOffset виглядає багатообіцяючим, але це зміщення відносно лише безпосереднього контейнера діапазону, і є зміщенням символів лише у тому випадку, якщо контейнер є текстовим вузлом.


> Це має спрацювати, навіть якщо він вибере тег HTML <Що ви маєте на увазі під цим? Як хтось вибере тег HTML? Будь ласка, поясніть.
Сатьяджіт

Якщо користувач вибирає все #parent, його вибір включатиме деякі HTML-теги (<a> та <p>)
Том Леман,

Відповіді:


201

ОНОВЛЕННЯ

Як зазначалося в коментарях, моя оригінальна відповідь (нижче) повертає лише кінець відбору або позицію каретки. Досить легко адаптувати код для повернення початкового та кінцевого зміщення; ось приклад, який робить це:

Ось функція, яка отримає зміщення символів каретки в межах вказаного елемента; однак, це наївна реалізація, яка майже напевно матиме невідповідність розривам рядків і не робить спроб мати справу з текстом, прихованим через CSS (я підозрюю, IE буде правильно ігнорувати такий текст, тоді як інші браузери цього не роблять). Правильно впоратися з усіма цими речами було б складно. Зараз я спробував це зробити для свого Rangy бібліотеки .

Приклад у реальному часі : http://jsfiddle.net/TjXEG/900/

function getCaretCharacterOffsetWithin(element) {
    var caretOffset = 0;
    var doc = element.ownerDocument || element.document;
    var win = doc.defaultView || doc.parentWindow;
    var sel;
    if (typeof win.getSelection != "undefined") {
        sel = win.getSelection();
        if (sel.rangeCount > 0) {
            var range = win.getSelection().getRangeAt(0);
            var preCaretRange = range.cloneRange();
            preCaretRange.selectNodeContents(element);
            preCaretRange.setEnd(range.endContainer, range.endOffset);
            caretOffset = preCaretRange.toString().length;
        }
    } else if ( (sel = doc.selection) && sel.type != "Control") {
        var textRange = sel.createRange();
        var preCaretTextRange = doc.body.createTextRange();
        preCaretTextRange.moveToElementText(element);
        preCaretTextRange.setEndPoint("EndToEnd", textRange);
        caretOffset = preCaretTextRange.text.length;
    }
    return caretOffset;
}

2
@TimDown: Чудово, дякую! Для свого конкретного прикладу (за допомогою TinyMCE) я насправді знайшов простіший спосіб:, tinyMCE.execCommand('mceInsertContent', false, newContent);який я знайшов тут . Але +1 за допомогою!
Травесті3,

4
@RafaelDiaz: Так, та будь-які інші розриви рядків, передбачені HTML або CSS. Це не ідеальне рішення.
Тім Даун

1
@TimDown чи є спосіб змінити це, щоб отримати позицію через елементи html замість цього? напр. рядок HTML: "<div class =" c1 "> Привіт </div> І курсор вибрав" el "у Hello, він поверне 17
Фред Джонсон,

1
@TimDown Я б запропонував додати win.getSelection().rangeCount > 0перевірку перед запуском win.getSelection().getRangeAt(0), щоб запобігти помилкам, як описано у stackoverflow.com/questions/22935320/… . Я сам зіткнувся з проблемою, перевірка діапазону виправила
Грегор

2
@KimchiMan: Ти більше цього не робиш. element.documentбув для IE 5 та 5.5.
Тім Даун

25

Я знаю, що йому рік, але ця публікація є найкращим результатом пошуку з багатьох питань щодо пошуку позиції Каре, і я знайшов це корисним.

Я намагався використати чудовий сценарій Тіма вище, щоб знайти нову позицію курсора після перетягування елемента з однієї позиції на іншу у div, який можна редагувати. Це чудово працювало в FF та IE, але в Chrome дія перетягування виділила весь вміст між початком та кінцем перетягування, що призвело до поверненняcaretOffset було занадто великим або маленьким (за довжиною вибраної області).

Я додав кілька рядків до першого твердження if, щоб перевірити, чи виділено текст, і відповідно відрегулювати результат. Нове твердження знаходиться нижче. Вибачте мене, якщо недоречно додавати це сюди, оскільки це не те, що намагався зробити ОП, але, як я вже сказав, кілька пошуків в інформації, пов’язаній з позицією Каре, призвели мене до цієї посади, тому (сподіваюся), можливо, це допоможе комусь іншому .

Перше твердження Тима if з доданими рядками (*):

if (typeof window.getSelection != "undefined") {
  var range = window.getSelection().getRangeAt(0);
  var selected = range.toString().length; // *
  var preCaretRange = range.cloneRange();
  preCaretRange.selectNodeContents(element);
  preCaretRange.setEnd(range.endContainer, range.endOffset);

  if(selected){ // *
    caretOffset = preCaretRange.toString().length - selected; // *
  } else { // *
    caretOffset = preCaretRange.toString().length; 
  } // *
}

1
Хтось знає, як виділити слово, якщо я вже маю зі собою значення зміщення (я отримую відповідь JSON на стороні сервера) і хочу виділити слово на основі цього? Я не міг знайти нічого в ранговій бібліотеці, де я міг би просто підключити ці два значення ( startзміщення stopсимволів та зміщення символів) та виділити слово. Порадьте, будь ласка.
Іван

Я знаю, що це 4-річна посада, але чи можемо ми виділити слово за таким сценарієм, коли у мене є діапазон для вибору
Варун

Чи є причина, яку ви перевіряєте selectedперед відніманням? Якщо це 0, то ви не змінюєте значення caretOffset, віднімаючи 0, так?
Донні Д'Амато,

1
@ DonnieD'Amato Хороший момент. Я провів тестування і завжди отримував вибраний == 0, коли не було виділення (ніколи не визначено, не має значення або щось інше несподіване), тому ви повинні бути впевнені, щоб пропустити цю перевірку і завжди віднімати вибране.
Cody Crumrine

1
@CodyCrumrine Окрім того, віднімання чогось від nullеквівалентно використанню 0(але не впевнений, чи це відповідає всім механізмам Javascript). :)
aleclarson

16

Після експериментів кілька днів я знайшов такий підхід, який виглядає багатообіцяючим. Оскільки selectNodeContents()неправильно обробляє <br>теги, я написав власний алгоритм для визначення довжини тексту кожного nodeвсередині a contenteditable. Для обчислення, наприклад, початку вибору, я підсумую довжини тексту всіх попередніх вузлів. Таким чином, я можу обробляти (кілька) розривів рядків:


Мені довелося обернути getTextLength та getNodeTextLength у перевірку умови if if (node != null). Додатково я додав перевірку на нуль node.parentNodeпісляif (node != parent)
RugerSR9,

Працює чудово, дякую. document.activeElementз якихось причин не працював, тому я використав єдиний div, з яким працював.
Xertz

1

Це рішення працює шляхом підрахунку довжини текстового вмісту попередніх братів і сестер, які поверталися до батьківського контейнера. Ймовірно, він охоплює не всі випадки ребер, хоча він і обробляє вкладені теги будь-якої глибини, але це хороше, просте місце, з якого слід почати, якщо у вас є подібні потреби.

  calculateTotalOffset(node, offset) {
    let total = offset
    let curNode = node

    while (curNode.id != 'parent') {
      if(curNode.previousSibling) {
        total += curNode.previousSibling.textContent.length

        curNode = curNode.previousSibling
      } else {
        curNode = curNode.parentElement
      }
    }

   return total
 }

 // after selection

let start = calculateTotalOffset(range.startContainer, range.startOffset)
let end = calculateTotalOffset(range.endContainer, range.endOffset)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.