起因
最近几天在写一个滚动加载更多数据的插件(Scrollload),为局部滚动写demo时,遇到了很多局部滚动的坑,在这里分享一下这些坑的解决方案。以下的坑只针对ios。
约定
把产生滚动条的元素称之为视窗
全局滚动:滚动条在body或者body父级元素上。
局部滚动:滚动条在body里的子孙元素上。
局部滚动优势
每个局部滚动拥有自己的滚动条,这是全局滚动所不能取代的。最典型的是列子如局部滚动,全局滚动。不难发现全局滚动不同的tab之间共享一个滚动条,也就是说其中一个tab滚动了,另一个tab也会跟着滚动。
可以解决input fixed定位失效问题。
全局滚动有出界情况,出界就是滑到最顶端和最底端后继续滑。这样会出现一个很恶心的效果。局部滚动虽然也会有这个情况,但是能修复,全局滚动至少我不会修。
坑(一)
ios浏览器局部滚动默认没有弹性滚动的效果。解决方法是为body或者视窗加-webkit-overflow-scrolling: touch。其实加这两个地方都一样,虽然在文档中并没有说该属性有继承性的,不过我在safari下测试出来是有继承性的。该属性具体说明看这里。
坑(二)
问题
先看一下视频效果:没用ScrollFix
ios局部滚动的出界情况,当你的滚动条在最顶端的时候,你会发现此时你的列表不再滚动而是产生全局滚动了。其实这个确实应该是这样的。如果此时你的视窗占了比整个window还要大,就会一直在视窗里滚动,你还让不让用户看其他内容了。但视窗的滚动条在最顶端的时候的时候下滑又迅速上滑你会发现还是在做全局滚动。这个也应该是这样的,全局滚动还没停下来不可能做局部滚动吧。同理当你滚动条在最下面的时候也会出现这样的状况。但有时候,你就不会想以上的效果。
解决方案
其实解决方案很简单,既然知道了问题是滚动条在最顶端和最底端的时候才会出现的,那么你只要在touchstart的时候判断scrollTop是否为这两个值,如果是就加1或者减1。这里有一个别人已经实现的库ScrollFix。视频效果:用了ScrollFix。贴一下核心代码
var ScrollFix = function(elem) {
var startY, startTopScroll;
elem.addEventListener('touchstart', function(event){
startY = event.touches[0].pageY;
startTopScroll = elem.scrollTop;
//当滚动条在最顶部的时候
if(startTopScroll <= 0)
elem.scrollTop = 1;
//当滚动条在最底部的时候
if(startTopScroll + elem.offsetHeight >= elem.scrollHeight)
elem.scrollTop = elem.scrollHeight - elem.offsetHeight - 1;
}, false);
};
但是这个库一定要谨慎用。因为他监听了touchstart事件,这个事件会使滚动滞后(在这里并不明显),passive event listeners。当然你不能用文章里的解决方法,否则如果你快速滑动,由于touchstart事件的监听函数还没执行到就已经开始滚动了所以可能还是会发生上面的情况,ScrollFix这个库就无效了。
坑(三)
还是先看一下视频效果: 有问题的视频
这个坑就是你的内容不满视窗一个屏幕的时候,你向上滑动你会发现整个视窗都动了,也就成了全局滚动。这个现象是正常的,内容都不满一屏当然不需要滚动啊,甚至连滚动条都没产生。
解决方案
理由也说了,内容不足一个屏幕产生的现象,那么让内容时刻保持在一屏之上不就可以了。这里我写了一个库,LocalScrollFix。贴一下核心代码
update() {
//当内容超过一屏的时候isArrived为true
if (this.isArrived) {
return
}
//每次调用update方法时候都去更新fixDom的paddingTop值
const fixDomPaddingTop = this.computerFixDomPaddingTop()
if (fixDomPaddingTop >= 0) {
//只有当计算后的值大于0才要更新
this.fixDom.style.paddingTop = `${fixDomPaddingTop + 2}px`
} else {
//当计算后的值小于0的时候,也就是原来的内容就超过一屏幕了。arrived方法中会把fixDom移除。
this.arrived()
}
}
//计算fixDom所需要的paddingTop值
computerFixDomPaddingTop() {
//fixDom指的是append到最底部的dom。win指的是视窗
const {fixDom, win} = this
//一开始想内容的高度+paddingTop==数创的高度。因为直接求内容的高度其实并不简单。
//所以稍微变通了以下,让窗口的top值减去fixDom的top值其实就是fixDom的paddingTop值
//在把视窗的borderBottomWidth和视窗的paddingBottom考虑进去
const fixDomTop = fixDom.getBoundingClientRect().top
const winBottom = win.getBoundingClientRect().bottom
const {paddingBottom: winPaddingBottom, borderBottomWidth: winBorderBottomWidth}= window.getComputedStyle(win, null)
return winBottom - parseFloat(winPaddingBottom) - parseFloat(winBorderBottomWidth) - fixDomTop
}
大概就是在视窗内append一个元素,当你调用update方法的时候就去更新这个元素的paddingTop值来使视窗的内容超过一屏。
最佳实践
ios下可以用局部滚动替代全局滚动,安卓用全局滚动(老的安卓手机在局部滚动上貌似有严重的性能问题)。可能很多人会担心这样比较麻烦,其实仔细思考一下并没有增加多少代码。可以参考最佳实践,源码。
顺便说一下,如果你使用Scrollload来作为滚动到底部加载的插件,那么坑二就只需要把配置useScrollFix设置为true,坑三只需要把配置useLocalScrollFix设置为true。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。