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

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

TextRange.offsetLeft и TextRange.offsetTop сообщают о левом и верхнем углах диапазона, и во всех случаях, которые я видел в IE8, они примерно правильны, за исключением случая, когда диапазон находится внутри IFrame . Когда диапазон находится внутри IFrame, значения offsetLeft и offsetTop сдвигаются на отрицательную величину относительно положения IFrame в его родительском элементе. (см. пример ниже)

Эта проблема возникает только в том случае, если:

  • Браузер IE8
  • Страница находится в стандартном режиме

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

  • Браузер IE7 или IE10
  • Страница находится в причудливом режиме

Мои вопросы:

  • Могут ли другие подтвердить эту проблему или я сумасшедший?
  • Это известная проблема?
  • Есть ли разумное решение или обходные пути? (Разумным решением было бы то, где JS в кадре не нужно ничего знать о своем родительском окне)

Спасибо.

Пример проблемы: (см. разницу в 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>

person theycallmethesloth    schedule 14.05.2012    source источник


Ответы (3)


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

Я проверил ваш образец страницы в своем IE 8 (в Windows XP) и смог воспроизвести проблему, поэтому я могу подтвердить наличие проблемы, но не могу сказать, известная это проблема или нет.< br> Я нашел работающее решение (по крайней мере, для 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>

Также обратите внимание на библиотеку Rangy:

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

person Community    schedule 10.10.2012
comment
я считаю, что getBoundingClientRect возвращает координаты относительно окна, поэтому, если ваш документ в два раза больше размера области просмотра окна, тогда координаты выбора будут неправильными - person daedelus_j; 20.05.2014

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

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

  1. Mac OSX FF15/FF16
  2. MacOS Safari 5.1.7
  3. Mac OS X Chrome 22
  4. Mac OS X Опера 12
  5. Вин ХР ФФ 3.6
  6. Выиграй ХР Сафари 3.1
  7. WinXP IE7/IE8
  8. Вин 7 IE9
  9. Вин 7 ФФ15
  10. Вин 7 Хром 22

(следующий код основан на jQuery, лучше всего 1.8+)

jsfiddle

http://jsfiddle.net/vCWha/

CSS

#__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>
person Pebbl    schedule 06.10.2012