iOS safari浏览器上overflow: scroll元素无法滚动bug深究

13

前情提要

在之前我写过一篇文章:iOS safari浏览器上overflow: scroll元素无法滑动bug解决方法整理,这篇文章写的是,当iOS safari浏览器上出现大于父容器的svg元素,想给父容器加上overflow: scroll实现内部滚动效果而失败的总结。但当时并没有意识到这个问题的实际原理,只是知道了safari给scroll元素加入了原生的scrollView块

bug原理

最近在做一个项目的时候,在safari上遇到了一个其他的bug,却让我意识到了这个问题的终极原因。

项目bug是这样的:我在用Nuxt做一个展示站点,需要使用锚链接在页面刚进入的时候跳转到某个位置。这里我本来使用的是router api提供的scrollBehavior方法,但这个方法在Nuxt上有局限性。我就把实现方式改为:进入页面后,动态计算不同锚点位置的scroll top再设置父元素的scroll位置。

在其他浏览器上都是ok的,但在safari上就出了问题:在页面刚进入时无法正确获取到元素的scroll top,小很多,只有页面加载完成之后才可以。

究其原因,是因为我在页面上放了很多张图片让其自行占位,而在页面刚加载时,其他浏览器会预先获取到图片的大小而给其一个占位,无论图片是否加载完成页面总高度固定的。而safari就不一样,图片没加载成功时高度是0

图片没加载成功时高度是0。哇长见识了。

这时回想到之前在safari上的那个scroll bug,在查阅相关资料后就可以得出结论了:

safari浏览器在渲染页面元素的时候,会预先走webkit浏览器的渲染流程:

  1. 构建DOM tree
  2. 构建CSS rule tree
  3. 根据DOM和CSS tree来构建render tree
  4. 根据render tree计算页面的layout
  5. render页面

注意在第三步和第四步的时候,safari浏览器在构建render tree的时候,会预先找到相应的overflow: scroll元素,在计算页面layout的时候,会计算父元素的高度与子元素的高度,若子元素高于父元素,则在render页面时为其建立一个原生的scrollView。

这个scrollView有什么用的?其实就是为了给其一个弹弹乐的效果(但确实用户体验不错)。

当子元素是某个媒体格式时,比如img、object(svg)等,safari在加载完成之前是不会在计算在layout之内的,也就是高度为0,则子元素的高度就一定小于父元素的高度,safari不会给父元素一个原生的scrollView。

解决方法

反其道而行之。当出现这种问题的时候,给子元素一个包裹元素,包裹元素设置一个min-height大于父元素的高度,让父元素有scrollView。当子元素加载完成时,将包裹元素撑开,父元素便可以自由滚动了。


如果觉得我的文章对你有用,请随意赞赏

你可能感兴趣的

lzhong · 2018年09月29日

我碰上了一个问题 就是父级设置absolute后 占满屏幕 然后overflow-y:auto让子级滚动 然后滚动过程中偶尔会突然卡住 就像被设置了overflow:hidden一样 无法滚动 手松开 等个一两秒 又正常了
有必要说明一下 我那是一个表单页面 没有图片 看了题主的两篇文章 都尝试过 没有解决问题

回复

0

你说的问题也是 safari scrollView 的锅。

现在想这么个问题,现在同时有几个scrollView嵌套,这几个scrollView都不在其顶部,你开始往下滑动,正常情况:屏幕肯定先从内部开始滚动,按照由内向外的顺序依次触发,最后所有scrollView都滑到顶部。

但safari不正常:你在内层上滑动到顶部时,直接触发内层弹弹乐,在触发弹弹乐期间,scrollView的激活是不会改变的。也就是你往下拉,屏幕不会再往上走,就算外层不到顶也一样。当你松手,动画结束,这时恢复到不触发任何scrollView状态,你再下拉会重新触发一个最内层的可滑动scrollView,你才能继续拉屏幕。

所以回到你的问题,safari会首先给整个页面加一个scrollView,就是浏览器的正常滚动。你的全屏父元素也会加一个scrollView,也就是说现在有两个scrollView。

外层scrollView的子元素就是全屏父元素,高度是100%,也就是如果你的父元素没有scroll的话,页面上拉下拉都会直接弹弹乐。

根据上面safari的行为分析,你在全屏父元素的scrollView在顶部的时候继续下拉屏幕,safari会判断内部的scrollView无法滑动,直接触发更外层的scrollView,也就是整个页面的scrollView。但外层也是没法滑动的,所以直接触发弹弹乐。但在触发弹弹乐期间,当前激活的scrollView是不会变的,所以再往上拉屏幕下滑也是触发的外层,据上面分析,外层上拉下拉都会直接弹弹乐,你页面就不会滑动了。之后松手,弹弹乐动画结束再上拉,就会重新触发内部的滑动了。

文学功底较差,啰嗦一堆不知道你能看明白不。。

Kinice 作者 · 2018年09月30日
0

哈哈 能明白 我早上又认真研究了一下 确实是这样 在顶部时 往上拉 虽然页面没反应 但其实还是触发它的滚动状态 自身已经到顶部了 那就只能让父级滚动 但是父级是固定的 于是乎就不能动了 底部也是同理
于是我就想了一个土办法 我监听它的滚动事件 在顶部时 只要你scrollTop小于等于0 我就让你等于1 这样就永远无法到达顶部 也就触发不了这个bug 底部同理

lzhong · 2018年09月30日
0

哈哈你这个方法有点hack的

Kinice 作者 · 2018年09月30日
lzhong · 2018年09月29日

不知道题主有没有碰上过,求教

回复

SupaFan · 3月27日

这个解决办法还是有些局限性 不同手机的高度不同 高的手机在有些场景可能不需要滚动条

回复

0

不过还好的是,这个只在iOS上有问题,需要适配的宽高比加起来就3种,所以到时候具体情况具体分析就好了。

Kinice 作者 · 3月28日
前端小白菜 · 4月25日

“一个min-height大于父元素的高度”的法子在ios10上貌似就失灵了

回复

载入中...