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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3622818/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-25 01:46:01  来源:igfitidea点击:

IE's document.selection.createRange doesn't include leading or trailing blank lines

javascriptinternet-explorerdom

提问by Brad Robinson

I'm trying to extract the exact selection and cursor location from a textarea. As usual, what's easy in most browsers is not in IE.

我正在尝试从文本区域中提取确切的选择和光标位置。像往常一样,在大多数浏览器中容易的东西在 IE 中并不容易。

I'm using this:

我正在使用这个:

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;

Which works 99% of the time. The problem is that TextRange.textdoesn't return leading or trailing new line characters. So when the cursor is a couple of blank lines after a paragraph it yields a position at the end of the preceeding paragraph - rather than the actual cursor position.

这在 99% 的情况下都有效。问题是TextRange.text不返回前导或尾随换行符。因此,当光标在一个段落之后是几个空行时,它会在前一个段落的末尾产生一个位置 - 而不是实际的光标位置。

eg:

例如:

the quick brown fox|    <- above code thinks the cursor is here

|    <- when really it's here

The only fix I can think of is to temporarily insert a character before and after the selection, grab the actual selection and then remove those temp characters again. It's a hack but in a quick experiment looks like it will work.

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

But first I'd like to be sure there's not an easier way.

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

回答by Tim Down

I'm adding another answer since my previous one is already getting somewhat epic.

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

This is what I consider the best version yet: it takes bobince's approach (mentioned in the comments to my first answer) and fixes the two things I didn't like about it, which were first that it relies on TextRanges that stray outside the textarea (thus harming performance), and second the dirtiness of having to pick a giant number for the number of characters to move the range boundary.

这是我认为最好的版本:它采用了 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);

回答by Tim Down

N.B. Please refer to my other answerfor the best solution I can offer. I'm leaving this here for background.

注意请参阅我的其他答案以获取我可以提供的最佳解决方案。我把这个留在这里作为背景。

I've come across this problem and written the following that works in all cases. In IE it does use the method you suggested of temporarily inserting a character at the selection boundary, and then uses document.execCommand("undo")to remove the inserted character and prevent the insertion from remaining on the undo stack. I'm pretty sure there's no easier way. Happily, IE 9 will support the selectionStartand selectionEndproperties.

我遇到了这个问题并编写了以下适用于所有情况的内容。在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);

UPDATE

更新

Based on bobince's suggested approach in the comments, I've created the following, which seems to work well. Some notes:

根据 bobince 在评论中建议的方法,我创建了以下内容,这似乎运行良好。一些注意事项:

  1. bobince's approach is simpler and shorter.
  2. My approach is intrusive: it makes changes to the input's value before reverting those changes, although there is no visible effect of this.
  3. My approach has the advantage of keeping all operations within the input. bobince's approach relies on creating ranges that span from the start of the body to the current selection.
  4. A consequence of 3. is that the performance of bobince's varies with the position of the input within the document whereas mine does not. My simple tests suggest that when the input is close to the start of the document, bobince's approach is significantly faster. When the input is after a significant chunk of HTML, my approach is faster.
  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);

回答by Brad Robinson

The move by negative bazillion seems to work perfectly.

负数以百万计的举动似乎完美无缺。

Here's what I ended up with:

这是我的结果:

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

Thanks bobince - how can I vote up your answer when it's just a comment :(

谢谢 bobince - 当它只是一个评论时,我怎么能投票给你的答案:(

回答by Farrukh Aziz

A jquery plugin to get selection index start and end in text area. The above javascript codes didnt work for IE7 and IE8 and gave very inconsistent results, so I have written this small jquery plugin. Allows to temporarily save start and end index of the selection and hightlight the selection at a later time.

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

A working example and brief version is here: http://jsfiddle.net/hYuzk/3/

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

A more details version with comments etc. is here: http://jsfiddle.net/hYuzk/4/

带有评论等的更详细版本在这里: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}); 
            } 
            } 
        });