使用 MS AJAX 持久化子 DIV 的滚动位置






4.72/5 (12投票s)
一篇关于使用 MS AJAX 构建控件来持久化子 DIV 滚动位置的文章。
引言
虽然 Microsoft AJAX 中的 ScriptManager
和 UpdatePanel
在部分回发操作期间能很好地持久化页面的滚动位置,但您可能会惊讶地发现,对于 UpdatePanel
中包含的可滚动子 DIV
,情况并非如此。
本文中介绍的 PersistentScrollPosition
控件旨在解决此问题,它使用客户端行为和使用 Microsoft AJAX 实现的 ASP.NET 服务器控件。
背景
虽然我肯定没有打算回顾 UpdatePanel
和 PageRequestManager
的内部结构,或者实现任何客户端组件(Sys.Component
、Sys.UI,Behavior
或 Sys.UI.Control
),但快速了解可以帮助您理解和解决这个特殊问题。此控件需要记住两个关键点
- 客户端组件在部分回发生命周期中会被销毁和重新创建,因此您不能使用控件的实例来存储任何需要在此过程中保留的数据。
UpdatePanel
的 HTML 输出在部分回发期间(假设它被触发)通过innerHTML
属性完全替换,这就是滚动位置问题首先存在的原因。
使用代码
对于那些只想获得解决方案的人来说,使用代码非常简单。该控件有一个需要设置的属性,名为 ControlToPersist
。这是一个 string
,它接受服务器端容器 DIV 的 ID(它必须具有 runat="server"
)。
<asp:UpdatePanel runat="server" ID="UpdatePanel" UpdateMode="always">
<ContentTemplate>
<asp:Button runat="server" ID="btnPostBack" Text="Post Back" OnClick="btnPostBack_Click" />
<br />
<div style="width:590px;height:400px;overflow-y:scroll;overflow-x:hidden;"
runat="server" id="persistMe">
<p>
Lorem ipsum dolor sit amet, consectetuer adipiscing elit...</p>
</div>
<mbc:PersistentScrollPosition runat="server" ID="psf1" ControlToPersist="persistMe" />
</ContentTemplate>
</asp:UpdatePanel>
构建控件
该控件由两部分组成,这两部分在很大程度上都是非常“千篇一律”的。在服务器端,我们继承自 Control
,并实现 IScriptControl
和 INamingContainer
,并在初始化期间创建一个 HiddenField
,以便在部分回发之间存储我们的滚动位置。
public class PersistentScrollPosition : Control, IScriptControl, INamingContainer
{
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
// Create hidden control for storage
storage = new HiddenField();
storage.ID = "storage";
Controls.Add(storage);
}
}
在创建客户端初始化的脚本描述符时,我们传递可滚动的 DIV
的 ClientID
、控件的 ElementID
,并使用 ScriptComponentDescriptor
类的 AddElementProprety
方法传入对 HiddenField
的 DOM 元素的引用。
public IEnumerable<scriptdescriptor /> GetScriptDescriptors()
{
ScriptComponentDescriptor scd =
new ScriptBehaviorDescriptor("Mbccs.WebControls.PersistentScrollPosition",
Control.ClientID);
scd.AddElementProperty("storage", storage.ClientID);
yield return scd;
}
在客户端,控件继承自 Sys.UI.Behavior
基类。在控件初始化时,它挂钩到两个事件:DIV
的 scroll
DOM 事件和 Sys.WebForms.PageRequestManager
类的 EndRequest
事件。EndRequest
事件是恢复滚动状态的时间,但我稍后会详细介绍。
initialize : function() {
Mbccs.WebControls.PersistentScrollPosition.callBaseMethod(this, 'initialize');
this._scrollDelegate = Function.createDelegate(this, this._onScroll);
this._endRequestDelegate = Function.createDelegate(this, this._onEndRequest);
var prm = Sys.WebForms.PageRequestManager.getInstance();
prm.add_endRequest(this._endRequestDelegate);
$addHandler(this.get_element(), 'scroll', this._scrollDelegate);
}
当 DIV
滚动时,x,y 滚动位置被序列化并存储在之前创建的 HiddenField
服务器端控件中。
_onScroll : function(e) {
this._storage.value =
Sys.Serialization.JavaScriptSerializer.serialize(this._getScrollPosition());
},
_getScrollPosition : function() {
var el = this.get_element();
if (el) {
return {
x: el.scrollLeft || 0,
y: el.scrollTop || 0
};
}
}
为了防止 null 值四处漂浮,如果 scrollLeft
或 scrollTop
属性为 null,则 x,y 坐标会被强制转换为 0。
当“异步回发完成并且控件已返回到浏览器”时,会触发 EndRequest
事件,因此这是恢复滚动状态的最佳时机。
_onEndRequest : function(sender, args) {
var o = null;
if(this._storage.value !== '')
o = Sys.Serialization.JavaScriptSerializer.deserialize(this._storage.value);
if (o) {
var el = this.get_element();
el.scrollLeft = o.x;
el.scrollTop = o.y;
this._storage.value = '';
}
}
dispose
方法通常不是任何特别感兴趣的地方,但值得注意的是,在对基类调用 dispose
之前,您需要取消挂钩 DIV
的 scroll
事件。
dispose : function() {
$removeHandler(this.get_element(), 'scroll', this._scrollDelegate);
Mbccs.WebControls.PersistentScrollPosition.callBaseMethod(this, 'dispose');
var prm = Sys.WebForms.PageRequestManager.getInstance();
prm.remove_endRequest(this._endRequestDelegate);
delete this._endRequestDelegate;
delete this._scrollDelegate;
}