Contenteditable DIV - как определить, находится ли курсор в начале или конце содержимого
У меня есть contenteditable div, который содержит типичный редактор wysiwyg html (жирный шрифт, привязки, списки).
Мне нужно определить, находится ли текущий курсор, onKeyDown, в начале и в конце div. Причина этого заключается в том, что на основе позиции курсора и нажатой клавиши я могу захотеть объединить этот div с предыдущим div в обратном пространстве или создать новый следующий 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)
}
Я собираюсь столкнуться с любыми нечетными проблемами с чем-то вроде этого?
Ответы
Ответ 1
Вот как я решил это решить. Предлагаемый мною выше раствор работал, но иногда были способом много случаев краев, так что я в конечном итоге, учитывая, сколько текста был до или после курсора, и если это 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 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;
}
Еще не совсем уверен, что есть какие-либо другие случаи края, так как я не совсем уверен, как unit test это, так как он требует взаимодействия с мышью. - Там, вероятно, библиотеку, чтобы иметь дело с этим где-то
Ответ 2
Я бы использовал подобный подход к вашему, за исключением использования метода toString()
объектов Range
, а не cloneContents()
, чтобы избежать ненужного клонирования. Кроме того, в IE < 9 (который не поддерживает диапазоны), вы можете использовать аналогичный подход с свойством text
TextRange
.
Обратите внимание, что это будет иметь проблемы, когда в содержимом есть ведущие и/или завершающие разрывы строк, потому что метод toString()
диапазона работает так же, как свойство textContent
для node и рассматривает только текстовые узлы, поэтому не учитывайте разрывы строк, подразумеваемые <br>
или элементами блока. Также CSS не учитывается: например, текст внутри элементов, скрытых через display: none
, включен.
Вот пример:
Live demo: 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 };
}
Ответ 3
Я понял этот довольно последовательный и короткий метод:
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()
- однако, как ни странно, расширение работает даже в конце текста.
-
Если вы проверяете, инициирует ли пользователь перемещение каретки (например, в обработчике событий клавиатуры или мыши), вы должны обрабатывать случаи, когда проверка заставляет карету перемещаться неожиданным образом.
Ответ 4
У меня была та же проблема сегодня без чистого решения, поэтому я разработал следующий подход. Он использует только Selection
- без Range
или специфических для поставщика функций. Он также учитывает переводы строк в начале и конце содержимого.
Это работает в текущих Chrome, Firefox, Safari и Opera. Microsoft Edge снова является выбросом, поскольку само выделение текста частично нарушается в contenteditable
div
когда в начале или в конце содержимого появляются новые строки. К сожалению, я еще не нашел решение этой проблемы.
Стоит также отметить, что логика отличается не только между браузерами, но также и между режимами 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>
Ответ 5
Для проверки, находится ли курсор/каретка в конце ввода:
this.$('input').addEventListener('keydown', (e) => {
if (e.target.selectionEnd == e.target.value.length) {
// DO SOMETHING
}
})