IE в document.selection.createRange не содержит начальных или конечных пустых строк
Я пытаюсь извлечь точное выделение и местоположение курсора из текстовой области. Как обычно, то, что легко в большинстве браузеров, не в IE.
Я использую это:
var sel=document.selection.createRange();
var temp=sel.duplicate();
temp.moveToElementText(textarea);
temp.setEndPoint("EndToEnd", sel);
selectionEnd = temp.text.length;
selectionStart = selectionEnd - sel.text.length;
Который работает 99% времени. Проблема в том, что TextRange.text
не возвращает начальные или конечные символы новой строки. Таким образом, когда курсор представляет собой пару пустых строк после абзаца, он возвращает позицию в конце предыдущего абзаца, а не фактическую позицию курсора.
например:
the quick brown fox| <- above code thinks the cursor is here
| <- when really it's here
Единственное исправление, о котором я могу подумать, - это временно вставить символ до и после выделения, захватить фактическое выделение и затем снова удалить эти временные символы. Это взлом, но в быстром эксперименте похоже, что он будет работать.
Но сначала я хотел бы убедиться, что нет более легкого пути.
4 ответа
Я добавляю другой ответ, так как мой предыдущий уже становится несколько эпическим.
Это то, что я считаю лучшей версией: он использует подход bobince (упомянутый в комментариях к моему первому ответу) и исправляет две вещи, которые мне не нравились в этом, во-первых, он полагается на TextRanges, которые отклоняются от текстовой области. (таким образом, нанося ущерб производительности), а во-вторых, грязность необходимости выбирать гигантское число для количества символов, чтобы переместить границу диапазона.
function getSelection(el) {
var start = 0, end = 0, normalizedValue, range,
textInputRange, len, endRange;
if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") {
start = el.selectionStart;
end = el.selectionEnd;
} else {
range = document.selection.createRange();
if (range && range.parentElement() == el) {
len = el.value.length;
normalizedValue = el.value.replace(/\r\n/g, "\n");
// Create a working TextRange that lives only in the input
textInputRange = el.createTextRange();
textInputRange.moveToBookmark(range.getBookmark());
// Check if the start and end of the selection are at the very end
// of the input, since moveStart/moveEnd doesn't return what we want
// in those cases
endRange = el.createTextRange();
endRange.collapse(false);
if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
start = end = len;
} else {
start = -textInputRange.moveStart("character", -len);
start += normalizedValue.slice(0, start).split("\n").length - 1;
if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
end = len;
} else {
end = -textInputRange.moveEnd("character", -len);
end += normalizedValue.slice(0, end).split("\n").length - 1;
}
}
}
}
return {
start: start,
end: end
};
}
var el = document.getElementById("your_textarea");
var sel = getSelection(el);
alert(sel.start + ", " + sel.end);
NB Пожалуйста, обратитесь к моему другому ответу для лучшего решения, которое я могу предложить. Я оставляю это здесь для фона.
Я сталкивался с этой проблемой и написал следующее, которое работает во всех случаях. В IE он использует предложенный вами метод временной вставки символа на границе выделения, а затем использует document.execCommand("undo")
удалить вставленный символ и не дать вставке остаться в стеке отмены. Я уверен, что нет более легкого пути. К счастью, IE 9 будет поддерживать selectionStart
а также selectionEnd
свойства.
function getSelectionBoundary(el, isStart) {
var property = isStart ? "selectionStart" : "selectionEnd";
var originalValue, textInputRange, precedingRange, pos, bookmark;
if (typeof el[property] == "number") {
return el[property];
} else if (document.selection && document.selection.createRange) {
el.focus();
var range = document.selection.createRange();
if (range) {
range.collapse(!!isStart);
originalValue = el.value;
textInputRange = el.createTextRange();
precedingRange = textInputRange.duplicate();
pos = 0;
if (originalValue.indexOf("\r\n") > -1) {
// Trickier case where input value contains line breaks
// Insert a character in the text input range and use that as
// a marker
range.text = " ";
bookmark = range.getBookmark();
textInputRange.moveToBookmark(bookmark);
precedingRange.setEndPoint("EndToStart", textInputRange);
pos = precedingRange.text.length - 1;
// Executing an undo command to delete the character inserted
// prevents this method adding to the undo stack. This trick
// came from a user called Trenda on MSDN:
// http://msdn.microsoft.com/en-us/library/ms534676%28VS.85%29.aspx
document.execCommand("undo");
} else {
// Easier case where input value contains no line breaks
bookmark = range.getBookmark();
textInputRange.moveToBookmark(bookmark);
precedingRange.setEndPoint("EndToStart", textInputRange);
pos = precedingRange.text.length;
}
return pos;
}
}
return 0;
}
var el = document.getElementById("your_textarea");
var startPos = getSelectionBoundary(el, true);
var endPos = getSelectionBoundary(el, false);
alert(startPos + ", " + endPos);
ОБНОВИТЬ
Основываясь на предложенном Бобинсом подходе в комментариях, я создал следующее, что, кажется, работает хорошо. Некоторые заметки:
- Подход Бобинса проще и короче.
- Мой подход навязчив: он вносит изменения в значение ввода перед возвратом этих изменений, хотя видимого эффекта от этого нет.
- Мой подход имеет то преимущество, что все операции выполняются на входе. Подход bobince основывается на создании диапазонов, которые охватывают от начала тела до текущего выбора.
- Следствием 3. является то, что производительность bobince's меняется в зависимости от позиции ввода в документе, а моя - нет. Мои простые тесты показывают, что, когда ввод близко к началу документа, подход Бобинса значительно быстрее. Когда ввод идет после значительного фрагмента HTML, мой подход быстрее.
function getSelection(el) {
var start = 0, end = 0, normalizedValue, textInputRange, elStart;
var range = document.selection.createRange();
var bigNum = -1e8;
if (range && range.parentElement() == el) {
normalizedValue = el.value.replace(/\r\n/g, "\n");
start = -range.moveStart("character", bigNum);
end = -range.moveEnd("character", bigNum);
textInputRange = el.createTextRange();
range.moveToBookmark(textInputRange.getBookmark());
elStart = range.moveStart("character", bigNum);
// Adjust the position to be relative to the start of the input
start += elStart;
end += elStart;
// Correct for line breaks so that offsets are relative to the
// actual value of the input
start += normalizedValue.slice(0, start).split("\n").length - 1;
end += normalizedValue.slice(0, end).split("\n").length - 1;
}
return {
start: start,
end: end
};
}
var el = document.getElementById("your_textarea");
var sel = getSelection(el);
alert(sel.start + ", " + sel.end);
Кажется, что движение отрицательным базиллионом работает идеально.
Вот чем я закончил:
var sel=document.selection.createRange();
var temp=sel.duplicate();
temp.moveToElementText(textarea);
var basepos=-temp.moveStart('character', -10000000);
this.m_selectionStart = -sel.moveStart('character', -10000000)-basepos;
this.m_selectionEnd = -sel.moveEnd('character', -10000000)-basepos;
this.m_text=textarea.value.replace(/\r\n/gm,"\n");
Спасибо, Бобинс, - как я могу проголосовать за твой ответ, когда это всего лишь комментарий:(
Плагин jquery для получения начала и конца индекса выделения в текстовой области. Приведенные выше коды javascript не работали для IE7 и IE8 и дали очень противоречивые результаты, поэтому я написал этот небольшой плагин jquery. Позволяет временно сохранить начальный и конечный индексы выбора и выделить его позднее.
Рабочий пример и краткая версия находятся здесь: http://jsfiddle.net/hYuzk/3/
Более подробная версия с комментариями и т. Д. Находится здесь: http://jsfiddle.net/hYuzk/4/
// Cross browser plugins to set or get selection/caret position in textarea, input fields etc for IE7,IE8,IE9, FF, Chrome, Safari etc
$.fn.extend({
// Gets or sets a selection or caret position in textarea, input field etc.
// Usage Example: select text from index 2 to 5 --> $('#myTextArea').caretSelection({start: 2, end: 5});
// get selected text or caret position --> $('#myTextArea').caretSelection();
// if start and end positions are the same, caret position will be set instead o fmaking a selection
caretSelection : function(options)
{
if(options && !isNaN(options.start) && !isNaN(options.end))
{
this.setCaretSelection(options);
}
else
{
return this.getCaretSelection();
}
},
setCaretSelection : function(options)
{
var inp = this[0];
if(inp.createTextRange)
{
var selRange = inp.createTextRange();
selRange.collapse(true);
selRange.moveStart('character', options.start);
selRange.moveEnd('character',options.end - options.start);
selRange.select();
}
else if(inp.setSelectionRange)
{
inp.focus();
inp.setSelectionRange(options.start, options.end);
}
},
getCaretSelection: function()
{
var inp = this[0], start = 0, end = 0;
if(!isNaN(inp.selectionStart))
{
start = inp.selectionStart;
end = inp.selectionEnd;
}
else if( inp.createTextRange )
{
var inpTxtLen = inp.value.length, jqueryTxtLen = this.val().length;
var inpRange = inp.createTextRange(), collapsedRange = inp.createTextRange();
inpRange.moveToBookmark(document.selection.createRange().getBookmark());
collapsedRange.collapse(false);
start = inpRange.compareEndPoints('StartToEnd', collapsedRange) > -1 ? jqueryTxtLen : inpRange.moveStart('character', -inpTxtLen);
end = inpRange.compareEndPoints('EndToEnd', collapsedRange) > -1 ? jqueryTxtLen : inpRange.moveEnd('character', -inpTxtLen);
}
return {start: Math.abs(start), end: Math.abs(end)};
},
// Usage: $('#txtArea').replaceCaretSelection({start: startIndex, end: endIndex, text: 'text to replace with', insPos: 'before|after|select'})
// Options start: start index of the text to be replaced
// end: end index of the text to be replaced
// text: text to replace the selection with
// insPos: indicates whether to place the caret 'before' or 'after' the replacement text, 'select' will select the replacement text
replaceCaretSelection: function(options)
{
var pos = this.caretSelection();
this.val( this.val().substring(0,pos.start) + options.text + this.val().substring(pos.end) );
if(options.insPos == 'before')
{
this.caretSelection({start: pos.start, end: pos.start});
}
else if( options.insPos == 'after' )
{
this.caretSelection({start: pos.start + options.text.length, end: pos.start + options.text.length});
}
else if( options.insPos == 'select' )
{
this.caretSelection({start: pos.start, end: pos.start + options.text.length});
}
}
});