访问 offsetParent 时是不是有针对 IE 6/7“未指定错误”错误的解决方法

Posted

技术标签:

【中文标题】访问 offsetParent 时是不是有针对 IE 6/7“未指定错误”错误的解决方法【英文标题】:Is there a workaround for IE 6/7 "Unspecified Error" bug when accessing offsetParent访问 offsetParent 时是否有针对 IE 6/7“未指定错误”错误的解决方法 【发布时间】:2010-09-27 03:41:35 【问题描述】:

我在一个简单的 ASP.NET 概念验证应用程序中使用 jQuery UI 的可拖放库。此页面使用 ASP.NET AJAX UpdatePanel 进行部分页面更新。该页面允许用户将项目放入垃圾箱 div,这将调用从数据库中删除记录的回发,然后重新绑定项目所在的列表(和其他控件)。所有这些元素(可拖动项和垃圾桶 div)都在 ASP.NET UpdatePanel 中。

这里是拖放初始化脚本:

    function initDragging()
    
        $(".person").draggable(helper:'clone');
        $("#trashcan").droppable(
            accept: '.person',
            tolerance: 'pointer',
            hoverClass: 'trashcan-hover',
            activeClass: 'trashcan-active',
            drop: onTrashCanned
        );
    

    $(document).ready(function()
        initDragging();

        var prm = Sys.WebForms.PageRequestManager.getInstance();
        prm.add_endRequest(function()
        
            initDragging();
        );
    );

    function onTrashCanned(e,ui)
    
        var id = $('input[id$=hidID]', ui.draggable).val();
        if (id != undefined)
        
            $('#hidTrashcanID').val(id);
            __doPostBack('btnTrashcan','');
        

    

当页面回发时,部分更新了 UpdatePanel 的内容,我重新绑定了可拖动对象和可放置对象。然后当我用光标抓取一个可拖动对象时,我得到一个“htmlfile:未指定的错误”。例外。我可以在 jQuery 库中解决这个问题,方法是将 elem.offsetParent 替换为我编写的对这个函数的调用:

function IESafeOffsetParent(elem)

    try
    
        return elem.offsetParent;
    
    catch(e)
            
        return document.body;
    

我还必须避免调用 elem.getBoundingClientRect(),因为它会引发相同的错误。对于那些感兴趣的人,我只需要在Dimensions Plugin 中的jQuery.fn.offset 函数中进行这些更改。

我的问题是:

虽然这可行,但是否有更好的方法(更简洁;更好的性能;无需修改 jQuery 库)来解决这个问题? 如果没有,当我将来更新 jQuery 库时,管理保持更改同步的最佳方法是什么?例如,我可以在从 jQuery 网站下载的文件中内联之外的地方扩展库。

更新:

@some 它不可公开访问,但我会看看 SO 是否会让我将相关代码发布到这个答案中。只需创建一个 ASP.NET Web 应用程序(将其命名为 DragAndDrop)并创建这些文件。不要忘记将 Complex.aspx 设置为您的起始页。您还需要下载jQuery UI drag and drop plug in 和jQuery core

复杂的.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Complex.aspx.cs" Inherits="DragAndDrop.Complex" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Untitled Page</title>
    <script src="jquery-1.2.6.min.js" type="text/javascript"></script>
    <script src="jquery-ui-personalized-1.5.3.min.js" type="text/javascript"></script>
    <script type="text/javascript">
        function initDragging()
        
            $(".person").draggable(helper:'clone');
            $("#trashcan").droppable(
                accept: '.person',
                tolerance: 'pointer',
                hoverClass: 'trashcan-hover',
                activeClass: 'trashcan-active',
                drop: onTrashCanned
            );
        

        $(document).ready(function()
            initDragging();

            var prm = Sys.WebForms.PageRequestManager.getInstance();
            prm.add_endRequest(function()
            
                initDragging();
            );
        );

        function onTrashCanned(e,ui)
        
            var id = $('input[id$=hidID]', ui.draggable).val();
            if (id != undefined)
            
                $('#hidTrashcanID').val(id);
                __doPostBack('btnTrashcan','');
            

        
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server">
    </asp:ScriptManager>
    <div>
        <asp:UpdatePanel ID="updContent" runat="server" UpdateMode="Always">
    <ContentTemplate>
        <asp:LinkButton ID="btnTrashcan" Text="trashcan" runat="server" CommandName="trashcan" 
            onclick="btnTrashcan_Click" style="display:none;"></asp:LinkButton>
        <input type="hidden" id="hidTrashcanID" runat="server" />
        <asp:Button ID="Button1" runat="server" Text="Save" onclick="Button1_Click" />
        <table>
            <tr>
                <td style="width: 300px;">
                    <asp:DataList ID="lstAllPeople" runat="server" DataSourceID="odsAllPeople"
                        DataKeyField="ID">
                        <ItemTemplate>
                            <div class="person">
                                <asp:HiddenField ID="hidID" runat="server" Value='<%# Eval("ID") %>' />
                                Name:
                                <asp:Label ID="lblName" runat="server" Text='<%# Eval("Name") %>' />
                                <br />
                                <br />
                            </div>
                        </ItemTemplate>
                    </asp:DataList>
                    <asp:ObjectDataSource ID="odsAllPeople" runat="server" SelectMethod="SelectAllPeople" 
                        TypeName="DragAndDrop.Complex+DataAccess" 
                        onselecting="odsAllPeople_Selecting">
                        <SelectParameters>
                            <asp:Parameter Name="filter" Type="Object" />
                        </SelectParameters>
                    </asp:ObjectDataSource>
                </td>
                <td style="width: 300px;vertical-align:top;">
                    <div id="trashcan">
                        drop here to delete
                    </div>
                    <asp:DataList ID="lstPeopleToDelete" runat="server" 
                        DataSourceID="odsPeopleToDelete">
                        <ItemTemplate>
                            ID:
                            <asp:Label ID="IDLabel" runat="server" Text='<%# Eval("ID") %>' />
                            <br />
                            Name:
                            <asp:Label ID="NameLabel" runat="server" Text='<%# Eval("Name") %>' />
                            <br />
                            <br />
                        </ItemTemplate>
                    </asp:DataList>
                    <asp:ObjectDataSource ID="odsPeopleToDelete" runat="server" 
                        onselecting="odsPeopleToDelete_Selecting" SelectMethod="GetDeleteList" 
                        TypeName="DragAndDrop.Complex+DataAccess">
                        <SelectParameters>
                            <asp:Parameter Name="list" Type="Object" />
                        </SelectParameters>
                    </asp:ObjectDataSource>
                </td>
            </tr>
        </table>    
    </ContentTemplate>
    </asp:UpdatePanel>
    </div>
    </form>
</body>
</html>

复杂的.aspx.cs

namespace DragAndDrop

    public partial class Complex : System.Web.UI.Page
    
        protected void Page_Load(object sender, EventArgs e)
        
        

        protected List<int> DeleteList
        
            get
            
                if (ViewState["dl"] == null)
                
                    List<int> dl = new List<int>();
                    ViewState["dl"] = dl;

                    return dl;
                
                else
                
                    return (List<int>)ViewState["dl"];
                
            
        

        public class DataAccess
        
            public IEnumerable<Person> SelectAllPeople(IEnumerable<int> filter)
            
                return Database.SelectAll().Where(p => !filter.Contains(p.ID));
            

            public IEnumerable<Person> GetDeleteList(IEnumerable<int> list)
            
                return Database.SelectAll().Where(p => list.Contains(p.ID));
            
        

        protected void odsAllPeople_Selecting(object sender, ObjectDataSourceSelectingEventArgs e)
        
            e.InputParameters["filter"] = this.DeleteList;
        

        protected void odsPeopleToDelete_Selecting(object sender, ObjectDataSourceSelectingEventArgs e)
        
            e.InputParameters["list"] = this.DeleteList;
        

        protected void Button1_Click(object sender, EventArgs e)
        
            foreach (int id in DeleteList)
            
                Database.DeletePerson(id);
            

            DeleteList.Clear();
            lstAllPeople.DataBind();
            lstPeopleToDelete.DataBind();
        

        protected void btnTrashcan_Click(object sender, EventArgs e)
        
            int id = int.Parse(hidTrashcanID.Value);
            DeleteList.Add(id);
            lstAllPeople.DataBind();
            lstPeopleToDelete.DataBind();
        
    

数据库.cs

namespace DragAndDrop

    public static class Database
    
        private static Dictionary<int, Person> _people = new Dictionary<int,Person>();
        static Database()
        
            Person[] people = new Person[]
            
                new Person("Chad")
                , new Person("Carrie")
                , new Person("Richard")
                , new Person("Ron")
            ;
            foreach (Person p in people)
            
                _people.Add(p.ID, p);
            
        

        public static IEnumerable<Person> SelectAll()
        
            return _people.Values;
        

        public static void DeletePerson(int id)
        
            if (_people.ContainsKey(id))
            
                _people.Remove(id);
            
        

        public static Person CreatePerson(string name)
        
            Person p = new Person(name);
            _people.Add(p.ID, p);

            return p;
        
    

    public class Person
    
        private static int _curID = 1;
        public int ID  get; set; 
        public string Name  get; set; 
        public Person()
        
            ID = _curID++;
        

        public Person(string name)
            : this()
        
            Name = name;
        
    

【问题讨论】:

您能否提供一个指向最低限度的测试页面的链接? 【参考方案1】:

@arilanto - 我在我的 jquery 脚本之后包含了这个脚本。就性能而言,这不是最好的解决方案,但它是一种快速简单的解决方法。

function IESafeOffsetParent(elem)

    try
    
       return elem.offsetParent;
    
    catch(e)
            
      return document.body;
    


// The Offset Method
// Originally By Brandon Aaron, part of the Dimension Plugin
// http://jquery.com/plugins/project/dimensions
jQuery.fn.offset = function() 
/// <summary>
///     Gets the current offset of the first matched element relative to the viewport.
/// </summary>
/// <returns type="Object">An object with two Integer properties, 'top' and 'left'.</returns>

var left = 0, top = 0, elem = this[0], results;

if ( elem ) with ( jQuery.browser ) 
    var parent     = elem.parentNode,
        offsetChild  = elem,
        offsetParent = IESafeOffsetParent(elem),
        doc       = elem.ownerDocument,
        safari2   = safari && parseInt(version) < 522 && !/adobeair/i.test(userAgent),
        css       = jQuery.curCSS,
        fixed       = css(elem, "position") == "fixed";

    // Use getBoundingClientRect if available
    if (false && elem.getBoundingClientRect) 
        var box = elem.getBoundingClientRect();

        // Add the document scroll offsets
        add(box.left + Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft),
            box.top  + Math.max(doc.documentElement.scrollTop,  doc.body.scrollTop));

        // IE adds the HTML element's border, by default it is medium which is 2px
        // IE 6 and 7 quirks mode the border width is overwritable by the following css html  border: 0; 
        // IE 7 standards mode, the border is always 2px
        // This border/offset is typically represented by the clientLeft and clientTop properties
        // However, in IE6 and 7 quirks mode the clientLeft and clientTop properties are not updated when overwriting it via CSS
        // Therefore this method will be off by 2px in IE while in quirksmode
        add( -doc.documentElement.clientLeft, -doc.documentElement.clientTop );

    // Otherwise loop through the offsetParents and parentNodes
     else 

        // Initial element offsets
        add( elem.offsetLeft, elem.offsetTop );

        // Get parent offsets
        while ( offsetParent ) 
            // Add offsetParent offsets
            add( offsetParent.offsetLeft, offsetParent.offsetTop );

            // Mozilla and Safari > 2 does not include the border on offset parents
            // However Mozilla adds the border for table or table cells
            if ( mozilla && !/^t(able|d|h)$/i.test(offsetParent.tagName) || safari && !safari2 )
                border( offsetParent );

            // Add the document scroll offsets if position is fixed on any offsetParent
            if ( !fixed && css(offsetParent, "position") == "fixed" )
                fixed = true;

            // Set offsetChild to previous offsetParent unless it is the body element
            offsetChild  = /^body$/i.test(offsetParent.tagName) ? offsetChild : offsetParent;
            // Get next offsetParent
            offsetParent = offsetParent.offsetParent;
        

        // Get parent scroll offsets
        while ( parent && parent.tagName && !/^body|html$/i.test(parent.tagName) ) 
            // Remove parent scroll UNLESS that parent is inline or a table to work around Opera inline/table scrollLeft/Top bug
            if ( !/^inline|table.*$/i.test(css(parent, "display")) )
                // Subtract parent scroll offsets
                add( -parent.scrollLeft, -parent.scrollTop );

            // Mozilla does not add the border for a parent that has overflow != visible
            if ( mozilla && css(parent, "overflow") != "visible" )
                border( parent );

            // Get next parent
            parent = parent.parentNode;
        

        // Safari <= 2 doubles body offsets with a fixed position element/offsetParent or absolutely positioned offsetChild
        // Mozilla doubles body offsets with a non-absolutely positioned offsetChild
        if ( (safari2 && (fixed || css(offsetChild, "position") == "absolute")) ||
            (mozilla && css(offsetChild, "position") != "absolute") )
                add( -doc.body.offsetLeft, -doc.body.offsetTop );

        // Add the document scroll offsets if position is fixed
        if ( fixed )
            add(Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft),
                Math.max(doc.documentElement.scrollTop,  doc.body.scrollTop));
    

    // Return an object with top and left properties
    results =  top: top, left: left ;


function border(elem) 
    /// <summary>
    ///     This method is internal.
    /// </summary>
    /// <private />
    add( jQuery.curCSS(elem, "borderLeftWidth", true), jQuery.curCSS(elem, "borderTopWidth", true) );


function add(l, t) 
    /// <summary>
    ///     This method is internal.
    /// </summary>
    /// <private />
    left += parseInt(l, 10) || 0;
    top += parseInt(t, 10) || 0;


return results;
;

【讨论】:

我在行中收到错误“safari undefined” - “safari2 = safari && parseInt(version) 【参考方案2】:

如果您想为 jQuery 版本 1.4.2 修复缩小/压缩的 .js 文件,请替换:

var d=b.getBoundingClientRect(),

var d = null; 
try  d = b.getBoundingClientRect(); 
catch(e)  d =  top : b.offsetTop, left : b.offsetLeft  ; 

(注意现在右大括号后面没有逗号)

【讨论】:

【参考方案3】:

我在拖放时尝试了以下解决方法来解决 getBoundingClientRect() 未指定错误,并且效果很好。

在 jquery.1.4.2.js 中(即基本 jquery 文件,错误被准确抛出的地方

替换js文件中的elem.getBoundingClientRect()函数调用

//抛出未指定错误的行

var box = elem.getBoundingClientRect(),

有了这个..

var box = null; try box = elem.getBoundingClientRect(); catch(e) box = top : elem.offsetTop, left : elem.offsetLeft ;

这解决了问题,即使在通过更新面板回帖后,拖放也能正常工作

问候

拉古

【讨论】:

【参考方案4】:

我的版本是:

    添加功能:

    function getOffsetSum(elem) 
        var top = 0, left = 0
        while (elem) 
            top = top + parseInt(elem.offsetTop)
            left = left + parseInt(elem.offsetLeft)
            try 
                elem = elem.offsetParent
            
            catch (e) 
                return  top: top, left: left 
            
        
        return  top: top, left: left 
    ;
    

    替换

    var box  = this[0].getBoundingClientRect()
    

    var box = getOffsetSum(this[0])
    

PS:jquery-1.3.2.

【讨论】:

【参考方案5】:

@Raghu

感谢这段代码!我在 IE 中遇到了同样的问题,这解决了它。

var box = null; 
try  
    box = elem.getBoundingClientRect(); 
 catch(e)  
    box =  
        top : elem.offsetTop, 
        left : elem.offsetLeft 
    ; 

【讨论】:

【参考方案6】:

这不仅仅是一个 jQuery 错误。我在 IE8 上使用 ExtJS 4.0.2a 遇到了它。如果元素在 DOM 中被替换,IE 几乎总是会偶然发现element.getBoundingClientRect()。您的 try/catch hack 几乎是解决此问题的唯一方法。我想实际的解决方案是最终放弃对 IE

相关 ExtJS v4.0.2a 源代码(第 11861-11869 行):

...
if(el != bd)
    hasAbsolute = fly(el).isStyle("position", "absolute");

    if (el.getBoundingClientRect) 
        b = el.getBoundingClientRect();
        scroll = fly(document).getScroll();
        ret = [Math.round(b.left + scroll.left), Math.round(b.top + scroll.top)];
     else 
...

使用 try/catch 修复:

...
if(el != bd)
    hasAbsolute = fly(el).isStyle("position", "absolute");

    if (el.getBoundingClientRect) 
        try 
            b = el.getBoundingClientRect();
            scroll = fly(document).getScroll();
            ret = [Math.round(b.left + scroll.left), Math.round(b.top + scroll.top)];
         catch(e) 
            ret = [0,0];
        
     else 
...

【讨论】:

以上是关于访问 offsetParent 时是不是有针对 IE 6/7“未指定错误”错误的解决方法的主要内容,如果未能解决你的问题,请参考以下文章

JS-offsetParent定位父节点

parentNode和offsetParent的区别,仅仅只是定位相对不一样么

JavaScript问题——在浏览器中每一个元素都有一个offsetParent属性,这个属性是什么?

一台电脑的所有浏览器针对个别网站无法运行javascript

html中的offsetTop的使用

[转] offsetParent 到底是哪一个?