2010-09-01 3 views
7

Я пытаюсь извлечь точный выбор и расположение курсора из текстового поля. Как обычно, то, что легко в большинстве браузеров, не в IE.IE document.selection.createRange не включает ведущие или завершающие пустые строки

Я использую это:

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 

Единственное исправление я могу думать о том, чтобы временно вставить символ до и после выбора, захватить фактический выбор, а затем удалить эти временные символы снова. Это взломать, но в быстром эксперименте похоже, что это сработает.

Но сначала я хотел бы быть уверенным, что нет более простого способа.

ответ

12

Я добавляю еще один ответ, так как мой предыдущий будет уже получился несколько эпическим.

Это то, что я считаю лучшей версией: он принимает подход bobince (упомянутый в комментариях к моему первому) и фиксирует две вещи, которые мне не нравились, которые были первыми, что он полагается на TextRanges, которые отклоняются вне textarea (тем самым нанося вред производительности), а во-вторых, нечеткость необходимости выбирать гигантское число для количества символов для перемещения границы диапазона.

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

Ницца. Как идея использования длины текста вместо действительно большого числа на moveStart/moveEnd. –

+0

А, я не видел этого. Результат - 20-30 мс, отличная работа! –

+1

@ Энди: Я написал плагин jQuery, который включает в себя это. Еще не задокументирован и не связан с проектом, связанным с тенью, но работает: http://code.google.com/p/rangy/downloads/detail?name=textinputs_jquery-src.js –

1

N.B. Пожалуйста, обратитесь к моему other answer за лучшее решение, которое я могу предложить. Я оставляю это здесь для фона.

Я столкнулся с этой проблемой и написал следующее, что работает во всех случаях. В 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); 

UPDATE

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

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

Это кажется очень грязный обходной путь! Есть ли какая-то причина предпочесть использовать метод 'range.moveStart ('character', -10000000)' 'определения границ выбора в текстовом поле? У этого все еще есть '\ r \ n' для исправления, но это относительно просто в сравнении. – bobince

+0

bobince: erm. Я не могу вспомнить проблему, которую, как я думал, существовал с вашим предложением, и теперь ее не найти. Я вернусь к вам. –

+0

bobince: hmm. Похоже, ты прав. Я был уверен, что есть какая-то проблема с пустыми линиями с вашим подходом, но, похоже, нет. –

1

Движение отрицательным bazillion, кажется, работает отлично.

Вот что я закончил с:

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

Спасибо bobince - как я могу проголосовать свой ответ, когда это просто комментарий :(

+0

Я изучил это немного дальше. См. Мой ответ на мои выводы. Два момента о том, что у вас есть: это вызовет ошибку, если вы используете его на входе вместо текстового поля, а также позиции, которые он возвращает, относительно части текста, которая не является фактическим значением на входе: I думаю, что позиция выбора концептуально является смещением внутри значения ввода, которое содержит '\ r \ n' для каждого разрыва строки, а не' \ n'. –

+0

Да, поэтому функция, указанная в http://stackoverflow.com/questions/1738808#1739088, возвращает фактические строки, скорректированные для '\ r \ n', а не индексы в значение; Я думаю, что то же самое произойдет здесь с 'm_text'. – bobince

+0

Да. Однако ваш пример не возвращает позиции. –

1

Плагин 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}); 
      } 
      } 
     }); 
+0

Является ли «вышеуказанные коды javascript» ссылкой на принятый ответ? Если это так, комментарий к самому ответу будет полезен, чтобы я мог улучшить ответ, если это необходимо. Не могли бы вы привести конкретный пример его непоследовательных результатов? –

Смежные вопросы