之前做移动端项目的时候,同事推荐使用-webkit-overflow-scrolling:touch;
属性,当时只是知道在元素内容有滚动条的时候使用这个属性,可以使滚动比较流畅。
然后在MDN上查了一下:
-webkit-overflow-scrolling
属性是来控制元素在移动设备上是否有回弹的效果。
它有两个属性值:
auto
:使用普通滚动,当手指在屏幕上离开时,滚动立即停止touch
:使用具有回弹效果的滚动, 当手指从触摸屏上移开,内容会继续保持一段时间的滚动效果。继续滚动的速度和持续的时间和滚动手势的强烈程度成正比。同时也会创建一个新的堆栈上下文。
兼容性写法:
verflow:auto;/* winphone8和android4+ */
-webkit-overflow-scrolling: touch; /* ios5+ */
bug
当你给一个元素设置过position:absolute;
或者position:relative;
后再增加-webkit-overflow-scrolling: touch;
属性后,你会发现,滑动几次后可滚动区域会卡主,不能在滑动,这时给元素增加个z-index
值就可以了。
-webkit-overflow-scrolling: touch;
position:absolute;
z-index:1;
-webkit-overflow-scrolling:touch
在移动端存在滚动卡死的bug
前言
最近用ionic来开发微信公众号,看好的就是ionic有成熟的UI框架,不需要自己去定义UI控件,当然微信也提供了自家的UI控件,但是数量实在太少,不敢恭维。
因为一直用的是chrome调试开发的页面,测试的时候也没有发现什么问题,但是直到将代码放上服务器,然后通过iPhone手机的微信公众号来访问开发的网页,在滑动到顶部或者底部的时候,重复上滑或者重复下滑,会导致页面卡死,需要2,3秒后才能恢复正常。瞬间奔溃,这是个什么玩意儿。在百度上查了一下资料原来是-webkit-overflow-scrolling:touch引起的,有前端开发经验的人都知道,这个属性是给网页在iOS设备中呈现并滚动的时候,起到更加流畅的作用,有一个回弹的效果,跟原生iOS滑动的效果一样,没想到反而给iOS设备挖了这么大的一个坑,wtf…
分析
经网上查找一些资料描述是Safari
会对使用-webkit-overflow-scrolling
的网页,会创建一个UIScrollView
,给要显示的元素使用。具体可以参考这篇文章,文章的作者也是遇到这个问题,并且估计快被逼疯了…
通过chrome检查元素可以看到在网页编译并且跑起来之后,ionic
有生成了一个类名为“scroll-content
”的div
,这个div就是使用了-webkit-overflow-scrolling:touch
,仔细查看了资料包括上面那篇文章,说是可以通过给滚动的元素的内部加一个div
元素,然后设置这个内部的div的高度100%+1px
或100%+1%
可以解决,有些人就说是通过修改z-index
,还有些人说是不要让那个滚动的元素有relative
或者absolute
这样的css定位。由于是在ionic
的框架上进行开发的页面,查找了元素“scroll-content
”这个div
确实有绝对定位,刚开始很担心是ionic
框架的问题,难道这次要翔,但是上面的方法全部试了个遍,可以很负责任的告诉你,没有用,没有用,没有用,不论是ios设备的微信浏览器,safari浏览器,QQ浏览器,UC浏览器,都是会存在这个卡死的问题。
解决方案
想了好久,既然是-webkit-overflow-scrolling:touch
引起的,那么不用这个属性就行了嘛,但是不用这个属性,页面滑动起来真的是还不如让这个bug留着,那感觉就像你看了一部无声的,黑白的,画质贼差的电影。接着往下思考,既然是在顶部或者底部的时候会出现卡死,那么不如通过touchstart
和touchmove
事件来判断是否到达顶部或者底部,然后移除-webkit-overflow-scrolling:touch
,不满足的时候就重新加上-webkit-overflow-scrolling:touch
,这样就不会影响滑动,实际上也是有点效果了,可以滑动一点点,但是你疯狂的滑动,还是会出现卡死。
当然这种方案是不行的,所以最后还是通过判断是否到达底部或者顶部添加event.preventDefault()
;来解决
stackoverflow上的建议
但是这边还有一个需要注意的点就是,如果你的页面有刷新和加载分页,那么你就需要注意设置event.preventDefault()
的时机。
如果有下拉刷新,那么在
scrollTop
为0的时候,就不能设置event.preventDefault()
;如果有加载分页,那么你就需要直到滑动到最后一页的时候,才能设置event.preventDefault()
,否者你一加载更多,页面立马就卡死了。
下面是我代码:
preventFreezeAtTopOrBottomForIOS(isGetRefresher,contenClassName){
if(this.isIOS()){
let contentEle = document.getElementsByClassName(contenClassName)[0];
let lastY = 0; // Needed in order to determine direction of scroll.
contentEle.getElementsByClassName("scroll-content")[0].addEventListener('touchstart', function(event) {
lastY = event.touches[0].clientY;
});
//获取滚动元素
let scrollEle = contentEle.getElementsByClassName("scroll-content")[0];
//先移除监听事件
scrollEle.removeEventListener('touchmove', function (event) {
event.preventDefault();
}, false);
//再添加监听事件
scrollEle.addEventListener('touchmove', function(event) {
let top = event.touches[0].clientY;
// Determine scroll position and direction.
let scrollTop = scrollEle.scrollTop;
// console.log("--scrollTop--" + scrollTop);
let scrollHeight = scrollEle.scrollHeight;
// console.log("--scrollHeight--" + scrollHeight);
let clientHeight = scrollEle.clientHeight;
// console.log("--clientHeight--" + clientHeight);
let direction = (lastY - top) < 0 ? "up" : "down";
// FIX IT!
if (scrollTop == 0 && direction == "up") {
console.log("--到顶部--");
// Prevent scrolling up when already at top as this introduces a freeze.
if(!isGetRefresher){//没有头部刷新的,需要设置防止头部freeze
event.preventDefault();
}
} else if (scrollTop >= (scrollHeight - clientHeight) && direction == "down") {
// Prevent scrolling down when already at bottom as this also introduces a freeze.
event.preventDefault();
console.log("--到底部--");
}
lastY = top;
});
}
}
由于我是使用ionic
框架来开发的,所以我这边实际上滚动是“scroll-content”这个div,并且每一个页面都有一个“scroll-content”,所以你在获取对应的页面对应不同的“scroll-content”的时候,可以通过给ion-content设置class来获取其下面的元素“scroll-content”。
参数说明:
其中的contentClassName
就是各个页面的ion-content
对应的classname
,注意需自己给ion-content
设置唯一的classname
,isGetRefresher
就是判断是否有下拉刷新,是boolean
类型,至于加载更多,你不需要限制,只需要我上面说的,加载到最后再调用上面那段代码。反正就是内容高度一变化,你就得重新设置。
总结
感觉这种方法是比较好的解决方法了。如果你有其他更好的方法可以告诉我(估计你也没有😄),被这个bug折磨了很久,总算是消停了,当然还是希望官方可以解决这个bug。
移动端丨-webkit-overflow-scrolling:touch
属性导致页面卡住
起因
故事的起因是,在一个多列表的页面上,页面在iOS11,跟iOS10中会发生页面卡住,不能进行滚动。
然后就怀疑是自己的样式写的出了问题,就一直排查定位元素的样式,怎么都找不到问题所在。
但还是本着追根溯源的精神,定位到根元素的样式上有一个-webkit-overflow-scrolling: touch;
的样式属性;然后查了一下该属性:
-webkit-overflow-scrolling
属性控制元素在移动设备上是否使用滚动回弹效果.
auto
: 使用普通滚动, 当手指从触摸屏上移开,滚动会立即停止。touch
:使用具有回弹效果的滚动,当手指从触摸屏上移开,内容会继续保持一段时间的滚动效果。继续滚动的速度和持续的时间和滚动手势的强烈程度成正比。同时也会创建一个新的堆栈上下文。
在移动端上,在你用
overflow-y:scorll
属性的时候,你会发现滚动的效果很很生硬,很慢,这时候可以使用-webkit-overflow-scrolling:touch
这个属性,让滚动条产生滚动回弹的效果,就像ios原生的滚动条一样流畅。
问题
但是的但是,-webkit-overflow-scrolling:touch
这个属性真的是超级神坑,比如:
- 在safari上,使用了
-webkit-overflow-scrolling:touch
之后,页面偶尔会卡住不动。(中招) - 在safari上,点击其他区域,再在滚动区域滑动,滚动条无法滚动的
bug
。 - 通过动态添加内容撑开容器,结果根本不能滑动的
bug
。(中招) - 滚动中
scrollTop
属性不会变化 - 手势可穿过其他元素触发元素滚动
- 滚动时暂停其他
transition
解决方案:
方案一
<div id="app" style="-webkit-overflow-scrolling: touch;">
<div style="min-height:101%"></div>
</div>
方案二
<div id="app" style="-webkit-overflow-scrolling: touch;">
<div style="height:calc(100%+1px)"></div>
</div>
方法就是在webkit-overflow-scrolling:touch
属性的下一层子元素上,将height
加1%
或1px
。从而主动触发scrollbar
。
思考为什么会出现这个问题
Safari对于overflow-scrolling
用了原生控件来实现。对于有-webkit-overflow-scrolling
的网页,会创建一个UIScrollView
,提供子layer
给渲染模块使用。我们也就只能解决到这了。
总结
不得不感叹,这些神奇的黑魔法,看的奇奇怪怪但是真实 的解决了实际的问题,毕竟css本身也是就是黑魔法本黑了,更不要说移动端这个天坑了。
深入研究-webkit-overflow-scrolling:touch
及ios滚动
1. -webkit-overflow-scrolling:touch
是什么?
MDN上是这样定义的:
`-webkit-overflow-scrolling` 属性控制元素在移动设备上是否使用滚动回弹效果. `auto`: 使用普通滚动, 当手指从触摸屏上移开,滚动会立即停止。 `touch`: 使用具有回弹效果的滚动, 当手指从触摸屏上移开,内容会继续保持一段时间的滚动效果。继续滚动的速度和持续的时间和滚动手势的强烈程度成正比。同时也会创建一个新的堆栈上下文。
在移动端上,在你用overflow-y:scorll
属性的时候,你会发现滚动的效果很木,很慢,这时候可以使用-webkit-overflow-scrolling:touch
这个属性,让滚动条产生滚动回弹的效果,就像ios原生的滚动条一样流畅。
2. 解决safari布局抖动的例子
想实现一个布局为header、main、bottom
的布局,其中头部和底部通过fixed
固定,中间部分通过滚动条滑动。
如果目的是实现只要中间的内容超过屏幕高度时,中间内容会自动滚动的效果的话,main
部分加上上下的padding
,然后不需要自己添加任何滚动条属性,当超出高度时,body
会自动产生滚动条。这样我们的目的其实是实现了的。
但是在safari上,当超出高度,页面往下滑时,浏览器底部的工具栏会随着页面一起晃动(向下滚动时会拉起底部工具栏),造成了很不好的体验。所以我们想在中间的main
部分加一个独立的滚动条。
2.1 方案一
在main
上使用fixed
定位,加上overflow-y
属性。
.main {
position: fixed;
top: 50px;
bottom: 50px;
overflow-y: scroll;
}
不过不推荐这个fixed
方案,因为页面偶尔卡住不动,下面说到了这个问题。
2.2 方案二
中间的**main
不设定位,高度100%
,再padding
头部和尾部,**
其中头部和底部的定位设为absolute
会比设为fixed
体验更好(况且fix布局在移动端本来就有各种各样的问题,还是尽量避开:) )。
大致代码如下,仍是 overflow-y
和-webkit-overflow-scrolling
,重点在于中间部分依照文本流布局。
html, body {
height: 100%;
}
main {
padding: 50px 0;
height: 100%;
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
}
3. 探究-webkit-overflow-scrolling:touch
偶尔卡住或不能滑动的bug
-webkit-overflow-scrolling:touch
这个属性真的是各种坑,我研究这个属性已经大半年了,还没有发现能够在safari上完美使用无bug
的例子。
最常见的例子就是,
- 在safari上,使用了
-webkit-overflow-scrolling:touch
之后,页面偶尔会卡住不动。 - 在safari上,点击其他区域,再在滚动区域滑动,滚动条无法滚动的
bug
。 - 通过动态添加内容撑开容器,结果根本不能滑动的bug。
在网上也看到了一些人在问这个问题,不过不多,国外倒是讨论的更多一点,描述如下。
偶尔卡住的问题,解决方案网上众说纷纭,遇到了很多相同的说法,比如如果卡住不动的话,就加一个z-index
,就能解决该问题的说法。
在试了很多次之后,这种说法没有一次解决过这个问题。这个说法能够传播出来,可能是使用者当时在使用的时候遇到了-webkit-overflow-scrolling:touch
点透或者层级的问题。所以该方案不具有适用性。
所以这个东西真的让我很苦恼了很久,以致于那段时间所有的滚动条不是通过body
自己滚动,就是使用iScroll
这样的库,繁琐地让我几乎想要放弃移动web,拥抱hybrid,不过在stackoverflow
潜水了很久之后,总结了以下几种解决方案:
3.1 保证使用了该属性的元素上没有设置定位
如果出现偶尔卡住不动的情况,那么在使用该属性的元素上不设置定位或者手动设置定位为static
position: static
这样会解决部分因为定位(relative、fixed、absolute
)导致的页面偶尔不能滚动的bug
。
但是滑动到顶部继续手指往下滑,或者到底部继续往上滑,还是会触发卡住的问题(其实是整个页面上下回弹),说他算bug,其实就是ios8以上的特性,如果滚动区域大一点,用户不会觉得这是bug,如果小了,用户会不知道发生了什么而卡住了。
视频在这,有梯子的同学可以看一看https://www.youtube.com/watch?v=MkAVYbO_joo。
3.2 如果添加动态内容页面不能滚动,让子元素height+1
如果在-webkit-overflow-scrolling:touch
属性的元素上,想通过动态添加内容来撑开容器,触发滚动,是有bug 的,页面是会卡住不动的。
国内没有人讨论这个问题,国外倒是很多,例如下面的描述:
收集了很多资料,用了之后,下面的方法真正的解决了我的问题,真是直呼神奇,方案如下图:
图一:
图二:
方法就是在webkit-overflow-scrolling:touch
属性的下一层子元素上,将height
加1%
或1px
。从而主动触发scrollbar
。
main-inner {
min-height: calc(100% + 1px)
}
你也可以直接加伪元素上:
main:after {
min-height: calc(100% + 1px)
}
这个方案不得不说真的好用。。
当然还有其他方案,不过要写js或者jq
了,麻烦。
3.3 为什么会有卡住不动的这个bug
这个bug产生于ios8以上(不十分肯定,但在ios5~7上需要手动使用translateZ(0)
打开硬件加速)
Safari对于overflow-scrolling
用了原生控件来实现。对于有-webkit-overflow-scrolling
的网页,会创建一个UIScrollView
,提供子layer
给渲染模块使用。
我想说作为一个苦逼的前端只能解决到这了。
4. -webkit-overflow-scrolling:touch
的其他坑
除此之外,这个属性还有很多bug
,包括且不限于以下几种:
- 滚动中
scrollTop
属性不会变化 - 手势可穿过其他元素触发元素滚动
- 滚动时暂停其他
transition
最后的吐槽
想写这个文章很久了, 本来以为就我有这个问题,结果看到网站上也有很多人在用这个属性,我用safari试了下,都能触发不能滑动的问题,但是网上的文章又很少,不知道大家是不是就视而不见了。
所以目前来看,如果不想那么费心,直接上iScroll
或者better-scroll
吧,我觉得better-scroll
还是挺好用的。如果你喜欢偷懒,那么接着用-webkit-overflow-scrolling:touch
也没什么问题。
毕竟移动端的水太深了,你永远不知道下一个问题是发生在safari还是x5内核浏览器上。