Вопрос по – TextRange offsetLeft и offsetTop не работает в стандартном режиме Internet Explorer 8 (IE8)

9

Кажется, я обнаружил проблему с Internet Explorer 8 при поиске положения диапазона текста - например, выделенного в данный момент текста. Я не нашел ни одного сообщения об этой проблеме ни в StackOverflow, ни где-либо еще.

TextRange.offsetLeft и TextRange.offsetTop сообщают о левом и верхнем углу диапазона, и во всех случаях, которые я видел в IE8, они примерно верны,except in the case when the range is within an IFrame, Когда диапазон находится в пределах IFrame, значения для offsetLeft и offsetTop сдвигаются на отрицательную величину относительно положения IFrame в его родительском элементе. (См. Пример ниже)

Эта проблема появляется только когда:

The browser is IE8 The page is in standards mode

Эта проблема делаетnot появляются, когда:

The browser is IE7 or IE10 The page is in quirks mode

Мои вопросы:

Can others confirm this issue or am I crazy? Is this a known issue? Are there any sane solution or work arounds? (A sane solution would be one where the JS in the frame doesn't need to know anything about its parent window)

Благодарю.

Пример проблемы: (см. Разницу в IE8 по сравнению с IE9)

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
        <title>IE8 IFrame Text Range Position Test Page</title>
        <style type="text/css">
            body {
                font-family: Tahoma;
            }
            #target {
                background-color: #CCC;
                position: absolute;
                left: 50px;
                top: 50px;
            }

            #bullsEye {
                position: absolute;
                background-color: red;
                width: 5px;
                height: 5px;
            }

            iframe {
                margin: 10px 75px;
            }
        </style>
        <script type="text/javascript" charset="utf-8">
            function target() {
                var range = document.body.createTextRange();
                range.moveToElementText(document.getElementById('target'));
                range.select();
                var bullsEye = document.createElement('div');
                bullsEye.id = 'bullsEye';
                bullsEye.style.left = range.offsetLeft + 'px';
                bullsEye.style.top = range.offsetTop + 'px';
                document.body.appendChild(bullsEye);
                document.getElementById('output').innerHTML = 'range.offsetLeft = ' + range.offsetLeft + ', range.offsetTop = ' + range.offsetTop;
            }
        </script>
    </head>
    <body>
        <div id="target">Target</div>
        <input type="button" value="Hit Target" onclick="target();"> <span id="output"></span>
        <br><br><br><br><br>
        <script>
            if (window.parent == window) document.write('<iframe src="?tfr" height="150" width="500"></iframe>');
        </script>
    </body>
</html>

Ваш Ответ

3   ответа
3
Я все еще считаю, что это ошибка в IE8, но есть обходной путь В стандартном режиме использованияboundingLeft а такжеboundingRight. Но в причудливом режиме используйтеoffsetLeft а такжеoffsetRight
3

ог воспроизвести проблему, поэтому могу подтвердить проблему, но не могу сказать, известна ли она или нет.
Я нашел рабочее решение (по крайней мере, в IE 8; я не могу сказать, работает ли оно также в IE 7 или IE 9, поскольку у меня нет тестовой среды для этих версий) на этот ответ.
Вот моя модифицированная тестовая страница:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
        <title>IE8 IFrame Text Range Position Test Page</title>
        <style type="text/css">
            body {
                font-family: Tahoma;
            }
            #target {
                background-color: #CCC;
                position: absolute;
                left: 50px;
                top: 50px;
            }

            #bullsEye {
                position: absolute;
                background-color: red;
                width: 5px;
                height: 5px;
            }

            iframe {
                margin: 10px 75px;
            }
        </style>
        <script type="text/javascript" charset="utf-8">
            function getSelectionTopLeft() {     
                var x = 0, y = 0;     // Use standards-based method only if Range has getBoundingClientRect     
                if (window.getSelection && document.createRange && 
                    typeof document.createRange().getBoundingClientRect != "undefined") {         
                    var sel = window.getSelection();         
                    if (sel.rangeCount > 0) {             
                        var rect = sel.getRangeAt(0).getBoundingClientRect();             
                        x = rect.left;             
                        y = rect.top;         
                    }     
                } else if (document.selection && document.selection.type != "Control") {         // All versions of IE         
                    var textRange = document.selection.createRange();         
                    x = textRange.boundingLeft;         
                    y = textRange.boundingTop;     
                }     
                return { x: x, y: y }; 
            }                   

            function target() {
                var range = document.body.createTextRange();
                range.moveToElementText(document.getElementById('target'));
                range.select();
                var bullsEye = document.createElement('div');
                bullsEye.id = 'bullsEye';
                bullsEye.style.left = range.offsetLeft + 'px';
                bullsEye.style.top = range.offsetTop + 'px';
                document.body.appendChild(bullsEye);
                //document.getElementById('output').innerHTML = 'range.offsetLeft = ' + range.offsetLeft + ', range.offsetTop = ' + range.offsetTop;
                var tl = getSelectionTopLeft();
                document.getElementById('output').innerHTML = tl.x + ',' + tl.y;
            }
        </script>
    </head>
    <body>
        <div id="target">Target</div>
        <input type="button" value="Hit Target" onclick="target();"> <span id="output"></span>
        <br><br><br><br><br>
        <script>
            if (window.parent == window) document.write('<iframe src="?tfr" height="150" width="500"></iframe>');
        </script>
    </body>
</html>

Посмотрите также на Ранги библиотека:

Кросс-браузерный диапазон JavaScript и библиотека выбора. Он предоставляет простой, основанный на стандартах API для выполнения общих задач DOM Range и Selection во всех основных браузерах, абстрагируя от дико отличающихся реализаций этой функциональности между Internet Explorer до версии 8 включительно и совместимыми с DOM браузерами.

Я считаю, что getBoundingClientRect возвращает координаты относительно окна, поэтому, если ваш документ в два раза больше окна просмотра окна, то координаты выбора не будут правильными daedelus_j
3

когда я пытался получить согласованное чтение в браузерах позиции выбора, я прибегал к вставке элементов span, где есть выбор, и чтению из этого элемента ... это немного странно, но кажется более надежным.

Подходя к таким вещам, вы должны полностью обойти эту конкретную ошибку ... Я понятия не имею, будет ли это классифицироваться как нормальное;) Хотя это было проверено и работает на всех этих пользовательских агентах:

Mac OSX FF15 / FF16Mac OSX Safari 5.1.7Mac OSX Chrome 22Mac OSX Opera 12 Win XP FF 3.6 Win XP Safari 3.1 Win XP IE7 / IE8 Win 7 IE9 Win 7 FF15 Win 7 Chrome 22

(следующий код опирается на jQuery, лучше подойдет 1.8+)

Jsfiddle

http: //jsfiddle.net/vCWha

@ C

#__span__    {
  display: inline !important;
  *display: inline-block !important; /* IE7 works better with inline-block */
}

/* you can obviously ignore these, they are just used to show accuracy */
.crosshair-v {
  height: 0;
  width: 20px;
  border-bottom: 1px solid red;
  position: absolute;
  margin-left: -10px;
}

.crosshair-h {
  height: 20px;
  width: 0;
  border-right: 1px solid red;
  position: absolute;
  margin-top: -9px;
}

Javascript

(function($){
  $(function(){
    var span = $('<span id="__span__" />').get(0),
        crv = $('<div class="crosshair-v" />'),
        crh = $('<div class="crosshair-h" />');
    $('body').append(crv).append(crh);
    var getSelectionTopLeft = function(){
      var s,e,a,p,o,r,t;
      try{
        /// IE9+, FF, Chrome, Safari, Opera
        if ( window.getSelection ){
          s = window.getSelection();
          r = s.getRangeAt(0);
          a = r.startContainer;
          p = a.parentNode;
          if ( a.nodeType == 3 ){
            t = a.splitText(r.startOffset);
            p.insertBefore(span, t);
          }
          else if ( a.nodeType == 1 ){
            p.insertBefore(span, a);
          }
          o = $(span).position();
        }
        /// IE8-
        else if ( (s = document.selection) && (s.type != 'Control') ) {
          r = s.createRange();
          r.move('character',0);
          $('#__span__').remove();
          r.pasteHTML(span.outerHTML);
          o = $('#__span__').position();
        }
        /// quick fallback for certain older browsers for 
        /// whom $().position() fails.
        if ( o && o.left === 0 && o.left === o.top ) {
          e = span;
          while( e.offsetParent ){
            o.left += e.offsetLeft;
            o.top += e.offsetTop;
            e = e.offsetParent;
          }
        }
      }catch(ex){}
      return o;
    }
    $(document).mouseup(function(e){
      /// execute our function to calculate the selection position
      var o = getSelectionTopLeft();
      if ( o ){
        /// update the crosshair
        crv.css(o);
        crh.css(o);
      }
    });
  });
})(typeof jQuery != 'undefined' && jQuery);
Обновит

У меня было немного больше времени на работу прошлой ночью, так что вот мой улучшенный код, работающий с вашим примером - следующее должно быть полностью кросс-браузерным (по крайней мере, в пределах разумного):

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
  <title>IE8 IFrame Text Range Position Test Page</title>
  <style type="text/css">
    body {
        font-family: Tahoma;
    }

    #__span__    {
      display: inline !important;
      display: inline-block !important;
      min-height: 1em;
    }

    #target {
        background-color: #CCC;
        position: absolute;
        left: 50px;
        top: 50px;
    }

    #bullsEye {
        position: absolute;
        background-color: red;
        width: 5px;
        height: 5px;
    }

    iframe {
        margin: 10px 75px;
    }
  </style>
  <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
  <script type="text/javascript" charset="utf-8">
    (function($){
        var bullsEye = $('<div id="bullsEye" />'), span = $('<span id="__span__"></span>').get(0);

        /// var is missed here on purpose to make the function globally accessible
        target = function() {                
          moveSelectionToElement( document.getElementById('target') );
          bullsEye
            .css( getSelectionTopLeft() )
            .appendTo('body');
        }

        /// because selectNodeContents seems to select outside the node we 
        /// need our own rangeToNodeContents that only highlights text nodes
        /// this is a side effect of having code inserted ranges & selections.
        var rangeToNodeContents = function(r, node){
          var i, l, tns = [];
          if ( node.nodeType == 1 && node.childNodes && (l = node.childNodes.length) ){
            for ( i=0;i<l;i++ ){
              if ( node.childNodes[i] && node.childNodes[i].nodeType == 3 ) {
                tns.push(node.childNodes[i]);
              }
              if ( tns.length > 1 ) {
                r.setStart(tns[0],0);
                r.setEnd(tns[tns.length-1],tns[tns.length-1].nodeValue.length);
              }
              else {
                r.selectNodeContents(node);
              }
            }
          }
          else {
            r.selectNodeContents(node);
          }
        }

        /// cross browser selection creator
        var moveSelectionToElement = function(elm) {
          var s,w,r,d; w = window; d = document;
          if (w.getSelection && d.createRange) {
            s = w.getSelection();
            r = d.createRange();
            rangeToNodeContents( r, elm );
            s.removeAllRanges();
            s.addRange(r);
          } else if (d.selection && d.body && d.body.createTextRange) {
            r = elm.createTextRange();
            r.select();
          }
        }

        /// cross browser getSelectionTopLeft
        var getSelectionTopLeft = function(){
          var s,e,a,p,o,r,t; o = {left:0,top:0};
          try{
            if ( window.getSelection ){
              s = window.getSelection();
              r = s.getRangeAt(0);
              a = r.startContainer;
              p = a.parentNode;
              if ( a.nodeType == 3 ){
                t = a.splitText(r.startOffset);
                p.insertBefore(span, t);
              }
              else if ( a.nodeType == 1 ){
                  p.insertBefore(span, a);
              }
              o = $(span).offset();
            }
            else if ( (s = document.selection) && (s.type != 'Control') ) {
              r = s.createRange();
              r.move('character',0);
              $('#__span__').remove();
              r.pasteHTML(span.outerHTML);
              o = $('#__span__').offset();
            }
            if ( o && o.left === 0 && o.left === o.top ) {
              e = span;
              while( e.offsetParent ){
                o.left += e.offsetLeft;
                o.top += e.offsetTop;
                e = e.offsetParent;
              }
            }
          }catch(ex){}
          return o;
        }

    })(typeof jQuery != 'undefined' && jQuery);
  </script>
</head>
<body>
  <div id="target">Target <b>abc</b> test</div>
  <input type="button" value="Hit Target" onmouseup="target();"> <span id="output"></span>
  <br><br><br><br><br>
  <script>
    if (window.parent == window){
      document.write('<iframe src="?tfr" height="150" width="500"></iframe>');
    }
  </script>
</body>
</html>

Похожие вопросы