Contenteditable DIV - как определить, находится ли курсор в начале или конце содержимого - PullRequest
14 голосов
/ 17 сентября 2011

У меня есть contenteditable div, который содержит типичный wysiwyg редактор html (жирный, якоря, списки).

Мне нужно определить, находится ли текущий курсор onKeyDown в начале и в конце div. Причина этого заключается в том, что в зависимости от положения курсора и нажатой клавиши я могу захотеть объединить этот div с предыдущим div на backspace или создать новый следующий div при вводе.

Я возился с диапазонами, но когда вы работаете с html внутри элемента, все становится довольно сложно.

Надеюсь, я должен пропустить какое-то простое решение.

Есть ли относительно простой способ определить это - я открыт для использования библиотеки, подобной Rangy.

Спасибо!

Редактировать: я думаю что-то вроде этого:

$('.mycontenteditable').bind('keydown', handle_keydown)

handle_keydown = function(e) {

  range = window.getSelection().getRangeAt(0)
  start_range = document.createRange()
  start_range.selectNodeContents(this.firstChild)
  start_range.collapse(true) // collapse to start
  is_start = start_range.compareBoundaryPoints(Range.START_TO_START,range)
  end_range = document.createRange()
  end_range.selectNodeContents(this.lastChild)
  end_range.collapse(false)
  is_end = end_range.compareBoundaryPoints(Range.END_TO_END,range)
}

Собираюсь ли я столкнуться с какими-то странными проблемами с чем-то вроде этого?

Ответы [ 5 ]

19 голосов
/ 20 сентября 2011

Я бы использовал подход, аналогичный вашему, за исключением использования toString() метода Range объектов вместо cloneContents(), чтобы избежать ненужного клонирования.Кроме того, в IE <9 (который не поддерживает диапазоны) вы можете использовать аналогичный подход со свойством <code>text, равным TextRange.

. Обратите внимание, что это будет иметь проблемы, когда есть ведущие и /или конечные разрывы строк в содержимом, потому что метод диапазона toString() работает так же, как свойство textContent узла, и учитывает только текстовые узлы, поэтому не учитывает разрывы строк, подразумеваемые <br> или элементами блока.Также CSS не учитывается: например, текст внутри элементов, которые скрыты с помощью display: none.

Вот пример:

Демонстрационная версия: http://jsfiddle.net/YA3Pu/1/

код:

function getSelectionTextInfo(el) {
    var atStart = false, atEnd = false;
    var selRange, testRange;
    if (window.getSelection) {
        var sel = window.getSelection();
        if (sel.rangeCount) {
            selRange = sel.getRangeAt(0);
            testRange = selRange.cloneRange();

            testRange.selectNodeContents(el);
            testRange.setEnd(selRange.startContainer, selRange.startOffset);
            atStart = (testRange.toString() == "");

            testRange.selectNodeContents(el);
            testRange.setStart(selRange.endContainer, selRange.endOffset);
            atEnd = (testRange.toString() == "");
        }
    } else if (document.selection && document.selection.type != "Control") {
        selRange = document.selection.createRange();
        testRange = selRange.duplicate();

        testRange.moveToElementText(el);
        testRange.setEndPoint("EndToStart", selRange);
        atStart = (testRange.text == "");

        testRange.moveToElementText(el);
        testRange.setEndPoint("StartToEnd", selRange);
        atEnd = (testRange.text == "");
    }

    return { atStart: atStart, atEnd: atEnd };
}
15 голосов
/ 19 сентября 2011

Вот так я и решил эту проблему.Мое предложенное выше решение иногда работало, но было много вариантов, поэтому я решил, сколько текста было до или после курсора, и если это было 0 символов, то я был в начале или в конце:

handle_keydown = function(e) {
  // Get the current cusor position
  range = window.getSelection().getRangeAt(0)
  // Create a new range to deal with text before the cursor
  pre_range = document.createRange();
  // Have this range select the entire contents of the editable div
  pre_range.selectNodeContents(this);
  // Set the end point of this range to the start point of the cursor
  pre_range.setEnd(range.startContainer, range.startOffset);
  // Fetch the contents of this range (text before the cursor)
  this_text = pre_range.cloneContents();
  // If the text's length is 0, we're at the start of the div.
  at_start = this_text.textContent.length === 0;
  // Rinse and repeat for text after the cursor to determine if we're at the end.
  post_range = document.createRange();
  post_range.selectNodeContents(this);
  post_range.setStart(range.endContainer, range.endOffset);
  next_text = post_range.cloneContents();
  at_end = next_text.textContent.length === 0;
}

По-прежнему не совсем уверен, что есть какие-либо другие крайние случаи, поскольку я не совсем уверен, как выполнить это модульное тестирование, поскольку это требует взаимодействия с мышью - возможно, где-то есть библиотека, чтобы с этим справиться.

1 голос
/ 15 марта 2019

Я понял этот довольно последовательный и короткий метод:

function isAtTextEnd() {
    var sel = window.getSelection(),
      offset = sel.focusOffset;
  sel.modify ("move","forward","character");
  if (offset == sel.focusOffset) return true;
  else {
    sel.modify ("move","backward","character");
    return false;
  }
}

Ключ: попытаться заставить его переместиться на один символ вперед - если он действительно двигался: не в конце (переместите его на один символ назад), если этого не произошло - это в конце (не нужно двигаться назад, он не двигался).
Реализация для начала текста противоположна и «оставлена ​​как упражнение для читателя» ...

Кариес:

  • MDN отмечает modify как «Нестандартный», хотя совместимость Таблица показывает довольно широкую поддержку (протестирована для работы на последних версиях Chrome и Firefox, согласно таблице - не поддерживается в Edge).
    Я попытался использовать для него более поддерживаемый extend() - однако, кажется, что странно, расширение работает даже в конце текста.

  • Если вы проверяете, инициирует ли пользователь перемещение каретки (например, в обработчике событий клавиатуры или мыши), вы должны обрабатывать случаи, когда проверка вынуждает карету перемещаться неожиданным образом.

0 голосов
/ 05 июня 2019

Простое решение для проверки, находится ли курсор / каретка в конце ввода:

this.$('input').addEventListener('keydown', (e) => {
  if (e.target.selectionEnd == e.target.value.length) {
    // DO SOMETHING
  }
})
0 голосов
/ 11 октября 2018

У меня была та же проблема сегодня без чистого решения, поэтому я разработал следующий подход.Он использует только Selection - нет Range или специфичные для поставщика функции.Он также учитывает переводы строк в начале и конце содержимого.

Работает в современных Chrome, Firefox, Safari и Opera.Microsoft Edge снова является выбросом, поскольку само выделение текста частично прерывается в contenteditable div s, когда в начале или конце содержимого появляются новые строки.К сожалению, я еще не нашел обходной путь для этой проблемы.

Стоит также отметить, что логика отличается не только между браузерами, но и между режимами white-space (normal против pre*), потому чтобраузер будет генерировать разные узлы для каждого при наборе.

document.addEventListener("selectionchange", function() {
  updateCaretInfo(document.getElementById('input-normal'))
  updateCaretInfo(document.getElementById('input-pre'))  
});



function updateCaretInfo(input) {

  function isAcceptableNode(node, side) {
    if (node === input) { return true }

    const childProperty = side === 'start' ? 'firstChild' : 'lastChild'
    while (node && node.parentNode && node.parentNode[childProperty] === node) {
      if (node.parentNode === input) {
        return true
      }

      node = node.parentNode
    }

    return false
  }

  function isAcceptableOffset(offset, node, side) {
    if (side === 'start') {
      return offset === 0
    }

    if (node.nodeType === Node.TEXT_NODE) {
      return offset >= node.textContent.replace(/\n$/, '').length
    }
    else {
      return offset >= node.childNodes.length - 1
    }
  }

  function isAcceptableSelection(selection, side) {
    return selection &&
      selection.isCollapsed &&
      isAcceptableNode(selection.anchorNode, side) &&
      isAcceptableOffset(selection.anchorOffset, selection.anchorNode, side)
  }


  const selection = document.getSelection()
  const isAtStart = isAcceptableSelection(selection, 'start')
  const isAtEnd = isAcceptableSelection(selection, 'end')

  document.getElementById('start-' + input.id).innerText = isAtStart ? 'YES' : 'no'
  document.getElementById('end-' + input.id).innerText = isAtEnd ? 'YES' : 'no'
}
body {
  padding: 10px;
}

[id^="input-"] {
  border:        1px solid black;
  display:       inline-block;
  margin-bottom: 10px;
  padding:       5px;
}
<div contenteditable id="input-normal">Move the caret inside here!</div>
(<code>white-space: normal</code>)

<p>
  Caret at start: <span id="start-input-normal">no</span><br>
  Caret at end: <span id="end-input-normal">no</span>
</p>

<hr>

<div contenteditable id="input-pre" style="white-space: pre-wrap">Move the caret inside here!</div>
(<code>white-space: pre-wrap</code>)

<p>
  Caret at start: <span id="start-input-pre">no</span><br>
  Caret at end: <span id="end-input-pre">no</span>
</p>
...