TextRange offsetLeft и offsetTop не работает в стандартном режиме Internet Explorer 8 (IE8)
Кажется, я обнаружил проблему с Internet Explorer 8 при поиске положения диапазона текста - например, выделенного в данный момент текста. Я не нашел ни одного сообщения об этой проблеме в Stackru или где-либо еще.
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>
3 ответа
Я протестировал ваш пример страницы в своем IE 8 (в Windows XP) и смог воспроизвести проблему, поэтому я могу подтвердить проблему, но я не могу сказать, является ли это известной проблемой или нет.
Я нашел рабочее решение (по крайней мере, в 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 браузерами.
- Я до сих пор считаю, что это ошибка в IE8, но есть обходной путь
- В стандартном режиме использования
boundingLeft
а такжеboundingRight
, Но в странном режиме использоватьoffsetLeft
а такжеoffsetRight
В прошлом, когда я пытался получить согласованное чтение в браузерах позиции выбора, я возвращался к вставке элементов span, где есть выбор, и чтению из этого элемента... это немного странно, но кажется более надежным.
Подходя к таким вещам, вы должны полностью обойти эту конкретную ошибку... Я понятия не имею, будет ли это классифицироваться как нормальное;) Хотя это было проверено и работает для всех этих пользовательских агентов:
- Mac OSX FF15 / FF16
- Mac OSX Safari 5.1.7
- Mac OSX Chrome 22
- Mac 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
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>