代码之家  ›  专栏  ›  技术社区  ›  Brad Robinson

伊恩的document.selection.createRange文件不包括前导或尾随空行

  •  7
  • Brad Robinson  · 技术社区  · 15 年前

    我用这个:

    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 回复  |  直到 15 年前
        1
  •  12
  •   Tim Down    15 年前

    我认为这是迄今为止最好的版本:它采用了bobince的方法(在我的第一个答案的评论中提到)并修复了我不喜欢的两个问题,第一个问题是它依赖于偏离textarea的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);
    
        2
  •  1
  •   Brad Robinson    15 年前

    负巴兹利昂的举措似乎效果很好。

    我的结论是:

    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-当你的答案只是一个评论时,我怎么能投你的赞成票:(

        3
  •  1
  •   Farrukh Aziz    13 年前

    一个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}); 
                } 
                } 
            }); 
    
        4
  •  1
  •   Community CDub    8 年前

    注意:请参考我的 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);
    

    更新

    根据bobince在评论中建议的方法,我创建了以下内容,看起来效果不错。注意事项:

    1. bobince的方法更简单、更简短。
    2. 我的方法是侵入式的:它在恢复这些更改之前对输入的值进行更改,尽管没有明显的效果。
    3. 结果是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);