问题
I seem to have found an issue with Internet Explorer 8, when finding the position of a Text Range - For instance the currently selected text. I have not found been able to find any reports of this issue on StackOverflow or elsewhere.
TextRange.offsetLeft and TextRange.offsetTop report the left and top corner of the range, and in all cases I have seen in IE8, they are roughly correct, except in the case when the range is within an IFrame. When the range is within an IFrame the values for offsetLeft and offsetTop are shifted by a negative amount relative to the position of the IFrame within it's parent. (See example below)
This issue appears only when:
- The browser is IE8
- The page is in standards mode
This issue does not appear when:
- The browser is IE7 or IE10
- The page is in quirks mode
My Questions:
- 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)
Thanks.
An example of the issue: (See the difference in IE8 vs. 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>
回答1:
- I still believe that this is a bug in IE8, but there is a workaround
- In standards mode use
boundingLeft
andboundingRight
. But in quirks mode useoffsetLeft
andoffsetRight
回答2:
I've tested your sample page in my IE 8 (on Windows XP) and I've been able to reproduce the problem, so I can confirm the issue, but I can't say if it is a known issue or not.
I've found a working solution (at least on IE 8; I can't say if it also works on IE 7 or IE 9 since I don't have a test environment for these versions) on this answer.
Here is my modified test page:
<!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>
Have also a look at the Rangy library:
A cross-browser JavaScript range and selection library. It provides a simple standards-based API for performing common DOM Range and Selection tasks in all major browsers, abstracting away the wildly different implementations of this functionality between Internet Explorer up to and including version 8 and DOM-compliant browsers.
回答3:
In the past when trying to get a consistent read across browsers for the selection position I've fallen back to inserting span elements where the selection is and reading from that element... it's a bit hacky but seems to be more reliable.
By approaching things this way you should completely circumvent that particular bug... I have no idea whether this would be classed as sane however ;) Although it has been tested and works across all these user agents:
- 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
(the following code relies on jQuery, 1.8+ would be best)
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);
update
Had a little more time to work on this last night, so here is my improved code worked into your example - the following should be fully cross-browser (at least within reason):
<!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>
来源:https://stackoverflow.com/questions/10591903/textrange-offsetleft-and-offsettop-broken-in-internet-explorer-8-standards-mode