IE 的 document.selection.createRange 不包括前导或尾随空行

Posted

技术标签:

【中文标题】IE 的 document.selection.createRange 不包括前导或尾随空行【英文标题】:IE's document.selection.createRange doesn't include leading or trailing blank lines 【发布时间】:2011-04-07 01:46:20 【问题描述】:

我正在尝试从文本区域中提取确切的选择和光标位置。像往常一样,在大多数浏览器中容易做的事情不在 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

我能想到的唯一解决方法是在选择前后临时插入一个字符,获取实际选择,然后再次删除这些临时字符。这是一个 hack,但在一个快速的实验中看起来它会起作用。

但首先我想确定没有更简单的方法。

【问题讨论】:

【参考方案1】:

我正在添加另一个答案,因为我之前的答案已经变得有些史诗了。

这是我认为最好的版本:它采用 bobince 的方法(在我的第一个答案的 cmets 中提到)并修复了我不喜欢它的两件事,首先它依赖于流浪的 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);

【讨论】:

不错。就像在 moveStart/moveEnd 上使用文本长度而不是非常大的数字的想法一样。 啊,我没看到这个。现在结果是 20-30 毫秒,干得好! @Andy:我编写了一个包含这个的 jQuery 插件。尚未记录并与一个相关的项目混为一谈,但正在工作:code.google.com/p/rangy/downloads/… 啊,所以你是 rangy 的创造者。我使用你的答案的项目是一个仅限 IE 的项目,我没有使用 jQuery,但我肯定会在某个时候为你的插件找到用处,我敢肯定。 包含此内容的 jQuery 插件现已移至此处:code.google.com/p/rangyinputs【参考方案2】:

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

【讨论】:

我对此进行了更深入的研究。请参阅我的答案以了解我的结论。关于你所拥有的两点:如果你在输入而不是 textarea 上使用它会引发错误,而且它返回的位置是相对于一段不是输入中实际值的文本:我认为选择位置在概念上是输入值内的偏移量,其中包含每个换行符的\r\n,而不是\n 是的,这就是***.com/questions/1738808#1739088 中提到的函数返回实际字符​​串的原因,已针对\r\n 进行了更正,而不是值的索引;我想m_text 也会发生同样的情况。 是的。您的示例不会返回位置。 我发布了一个新的答案,通过消除将 TextRange 的开头移动到文档正文的开头来改进这一点。【参考方案3】:

一个 jquery 插件,用于在文本区域中获取选择索引的开始和结束。上面的 javascript 代码对 IE7 和 IE8 不起作用,并且给出的结果非常不一致,所以我编写了这个小的 jquery 插件。允许暂时保存选择的开始和结束索引,并在以后突出显示选择。

这里有一个工作示例和简要版本:http://jsfiddle.net/hYuzk/3/

这里有一个带有 cmets 等的更详细的版本: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); 
             
             
        ); 

【讨论】:

“上述 javascript 代码”是对已接受答案的引用吗?如果是这样,对答案本身的评论会有所帮助,以便我可以在必要时改进答案。你能举一个具体的例子说明它的结果不一致吗?【参考方案4】:

注意请参考我的other answer,了解我能提供的最佳解决方案。我把这个留在这里作为背景。

我遇到了这个问题,并编写了以下适用于所有情况的内容。在 IE 中,它确实使用了您建议的在选择边界处临时插入字符的方法,然后使用 document.execCommand("undo") 删除插入的字符并防止插入保留在撤消堆栈上。我很确定没有更简单的方法。令人高兴的是,IE 9 将支持 selectionStartselectionEnd 属性。

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 在 cmets 中建议的方法,我创建了以下方法,似乎效果很好。一些注意事项:

    bobince 的方法更简单、更短。 我的方法是侵入性的:它在还原这些更改之前对输入值进行更改,尽管这样做没有明显的效果。 我的方法具有将所有操作保留在输入中的优点。 bobince 的方法依赖于创建从主体开始到当前选择的范围。 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);

【讨论】:

这似乎是一个非常混乱的解决方法!是否有任何理由更喜欢它而不是仅使用range.moveStart('character', -10000000) 方法来确定文本区域中的选择边界?那还有\r\n 需要修复,但相比之下这相对简单。 bobince:嗯。我不记得我认为您的建议存在的问题,现在找不到。我会回复你的。 bobince:嗯。看起来你是对的。我确定您的方法存在空行问题,但似乎没有。 bobince:我对此进行了更深入的研究。我的结论现在在我的答案中,但要回答您最初的问题,我可以看到支持我的方法的一个原因是我的方法独立于文档中输入的位置,而您的不是。如果您在大型 HTML 文档末尾的输入中使用您的方法,则会影响性能。 有趣的是,即使moveStart 方法实际上并没有遍历这些字符中的任何一个,文档末尾也存在可测量的性能差异?这非常奇怪。 (哦,IE...)

以上是关于IE 的 document.selection.createRange 不包括前导或尾随空行的主要内容,如果未能解决你的问题,请参考以下文章

让IE6 IE7 IE8 IE9 IE10 IE11支持Bootstrap的解决方法

让IE6 IE7 IE8 IE9 IE10 IE11支持Bootstrap的解决方法

转载------让IE6 IE7 IE8 IE9 IE10 IE11支持Bootstrap的解决方法

ie11兼容模式ie8显示错误

让IE6 IE7 IE8 IE9 IE10 IE11支持Bootstrap的解决方法

一行代码解决各种IE兼容问题,IE6,IE7,IE8,IE9,IE10