1.应用场景:
相信大家都曾经遇到过要将屏幕的某一部分滚到到用户视窗里,例如 聊天信息的自动滚屏 等,这个有不少解决方案:
1.聊天面板的scrolltop=scrollheight
2.消息最后加一个element,然后element.scrollIntoView
但是如果想要 任一容器中间的一个元素,滚到该容器的视窗显示部分 , 应该应用不广泛。
2.Extjs API:
extjs的核心 Element 包含了这一方法。
scrollIntoView ( [Mixed container
] , [Boolean hscroll
]
Scrolls this element into view within the passed container.
Scrolls this element into view within the passed container.
Parameters:
container
: Mixed (optional) The container element to scroll (defaults to document.body). Should be a string (id), dom node, or Ext.Element.hscroll
: Boolean (optional) False to disable horizontal scroll (defaults to true)
Returns:
Ext.Element
this
这个函数的效果就是将父区域内的子区域显示到父区域的视窗部分。
3.Extjs 实现详解:
子区域在父区域内大概分为三种关系:
1.子区域在父区域视窗底部,且子区域没有父区域视窗高,
2.子区域比父区域视窗高
3.子区域顶部在父区域视窗顶部以上,且子区域没有父区域视窗高
对于这三种情况,要分别处理,其中 二和三最终的处理方式一样的。
那么现在就可以分析extjs处理滚动高度部分的代码了:
scrollIntoView : function(container, hscroll){
var c = Ext.getDom(container) || Ext.getBody().dom;
var el = this.dom;
//子区域渲染后在屏幕上和父区域视窗的左边距和上边距,
//为负时,表示子区域有上部分在父区域视窗上面,如第三种情况
var o = this.getOffsetsTo(c),
//分别计算子区域相对父区域结点的坐标(不是父区域视窗)
l = o[0] + c.scrollLeft,
t = o[1] + c.scrollTop,
b = t+el.offsetHeight,
r = l+el.offsetWidth;
var ch = c.clientHeight;
var ct = parseInt(c.scrollTop, 10);
var cl = parseInt(c.scrollLeft, 10);
var cb = ct + ch;
var cr = cl + c.clientWidth;
//二三种情况,如果子区域比父区域视窗还高或者,
//区域有上部分在父区域视窗上面,就把子区域顶部和父区域视窗顶部对齐
//注意:子区域比父区域视窗还高,优先显示子区域顶部部分内容,比较合理。
if(el.offsetHeight > ch || t < ct){
c.scrollTop = t;
}else
//第一种情况,如果子区域在父区域视窗下面,或者有下部分在父区域视窗下面,
//且子区域没有父区域视窗高,就把子区域底部和父区域视窗底部对齐
if(b > cb){
c.scrollTop = b-ch;
}
}
宽度调整同理。
4.总结:
关键为初始根据scrollTop以及offsetTo得到元素和容器内容上边界的固定差值,之后只需赋值 scrollTop,在容器内容条上滑动 容器显示区域 到一定位置使得元素和容器的上(下)边对其即可。
PS1: scrollIntoView 原生解释
将一个元素滚动到视窗,如果参数为true则元素会被滚动到视窗的顶部,否则他会被滚动到视窗底部,默认为true。
当该方法被调用时,作用元素必须引起调用者的注意。
在读屏器中,可使得当前的播放位置重置到作用元素的开头。
在可视化的用户代理中,如果参数为false,用户代理必须滚动窗体使得元素的顶部和底部在当前视窗都可见,并且底部和视窗的底部对齐。如果当前视窗不能完全显示元素,或者元素参数被忽略则默认为true,那么用户代理应该将元素的顶部和视窗的顶部排列,如果当前页面可以完全显示在视窗中,那么用户代理不应该进行任何滚动。可视化用户代理应该同样进一步进行水平滚动使得该元素能够引起用户注意。
非可视化用户代理可以忽略调用参数,或者以合适的媒体特性来区别对待。
模拟原生 scrollView 的 kissy 实现 :
/**
* Makes elem visible in the container
* @refer http://www.w3.org/TR/2009/WD-html5-20090423/editing.html#scrollIntoView
* http://www.sencha.com/deploy/dev/docs/source/Element.scroll-more.html#scrollIntoView
* http://yiminghe.iteye.com/blog/390732
*/
scrollIntoView: function(elem, container, top, hscroll) {
if (!(elem = S.get(elem)) || !elem[OWNER_DOCUMENT]) return;
container = S.get(container);
hscroll = hscroll === undefined ? true : !!hscroll;
top = top === undefined ? true : !!top;
// default current window,use native for scrollIntoView(elem, top)
if (!container || container === win) {
// 注意:
// 1. Opera 不支持 top 参数
// 2. 当 container 已经在视窗中时,也会重新定位
return elem.scrollIntoView(top);
}
//document 归一化到 window
if (container.nodeType && container.nodeType == 9) {
container = getWin(container);
}
//support iframe's win
var notWin = !('scrollTo' in container && container[DOCUMENT]),
elemOffset = DOM.offset(elem),
cl = notWin ? container.scrollLeft : DOM.scrollLeft(container),
ct = notWin ? container.scrollTop : DOM.scrollTop(container),
//import! viewport should has
containerOffset = notWin ? DOM.offset(container) : {left:cl,top:ct},
diff = {
left:elemOffset.left - containerOffset.left ,
top:elemOffset.top - containerOffset.top
},
eh = elem.offsetHeight,
ew = elem.offsetWidth,
//left
l = diff.left + cl,
//top
t = diff.top + ct,
b = t + elem.offsetHeight,
r = l + elem.offsetWidth,
ch = notWin ? container.clientHeight : DOM.viewportHeight(container),
cw = notWin ? container.clientWidth : DOM.viewportWidth(container),
//container视窗下doc高度下限
cb = ct + ch,
//container视窗下doc右边限
cr = cl + cw;
//used if container is window
var wl = 0,wt = 0;
if (eh > ch || top) {
if (notWin)
container.scrollTop = t;
else
wt = t;
} else {
if (notWin)
container.scrollTop = t - (ch - eh);
else
wt = t - (ch - eh);
}
if (ew > cw || l < cl) {
if (notWin)
container.scrollLeft = l;
else
wl = l;
} else if (r > cr) {
if (notWin)
container.scrollLeft = l;
else
wl = l;
}
if (!notWin && (wl || wt)) {
container.scrollTo(wl, wt);
}
}
});
PS2:原生使用注意
使用原生的 element.scrollIntoView() 时,会同时使 element 所在容器也会进行 scrollIntoView 操作,特别当 elment 为处于 iframe 中的一个元素,则当调用 element.scrollIntoView() ,不但使得 iframe 内的窗口滚动到 element 所在处,同时也会使主窗口滚动到 iframe 处,某些场景这也许不是我们所期望的(编辑器工具栏操作编辑区iframe内元素),这时模拟实现的scrollIntoView就有用武之地了,调用:
new Node(element).scrollIntoView(iframe.contentWindow);
使得只滚动 iframe 内的窗口,而保持主窗口不变。