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);

ОБНОВИТЬ

Основываясь на предложенном Бобинсом подходе в комментариях, я создал следующее, что, кажется, работает хорошо. Некоторые заметки:

  1. Подход Бобинса проще и короче.
  2. Мой подход навязчив: он вносит изменения в значение ввода перед возвратом этих изменений, хотя видимого эффекта от этого нет.
  3. Мой подход имеет то преимущество, что все операции выполняются на входе. Подход bobince основывается на создании диапазонов, которые охватывают от начала тела до текущего выбора.
  4. Следствием 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}); 
            } 
            } 
        }); 
Другие вопросы по тегам