开发滚动翻屏交互总结
On this page
交互需求
为了提升首屏金刚位的承接效率,产品希望金刚位滑动到第二屏的时候有类似携程头部菜单左右滚动时高度拉伸展示金刚位列表的效果。
实现
因为整体是用rax开发的,翻了下rax官方组件库。有个官方组件scrollview,貌似可以基于这个组件开发实现。采用rax官方ScrollView组件,将内容拆分成两屏滚动。
功能1:滚动时变高,实现起来比较简单,左右滚动的时候动态改变scrollView的高度即可。这里可以采用scroll事件回调来处理。 功能2:停止滚动后,根据滚动位置判断最后滚动到左边还是右边。简单方法可以在用户手指离开,touchend回调事件中处理,调用scrollView提供的scrollTo方法,根据横向滚动距离判断是滚动到前一屏还是后一屏。在Android中测试还行,没有明显的问题,但是在ios中,正常操作没问题,快速滑动swipe手势下会存在抖动的问题。 肯定是我使用的姿势有问题,问题处理的简单了。查看了下scrollTo方法,是基于JS模拟的动画滚动。
/**
* Scroll to some position method
* @param scrollerRef the scroll container ref
* @param x offset x
* @param y offset y
* @param animated does it need animated
* @param duration animate duration
*/
function scrollTo(scrollerRef, x, y, animated, duration) {
const scrollView = scrollerRef.current;
const scrollLeft = scrollView.scrollLeft;
const scrollTop = scrollView.scrollTop;
if (animated) {
const timer = new Timer({
duration,
easing: 'easeOutSine',
onRun: e => {
if (scrollerRef && scrollerRef.current) {
if (x >= 0) {
scrollerRef.current.scrollLeft =
scrollLeft + e.percent * (x - scrollLeft);
}
if (y >= 0) {
scrollerRef.current.scrollTop =
scrollTop + e.percent * (y - scrollTop);
}
}
}
});
timer.run();
} else {
if (x >= 0) {
scrollerRef.current.scrollLeft = x;
}
if (y >= 0) {
scrollerRef.current.scrollTop = y;
}
}
}
再测下快速滚动,手指离开屏幕后其实滚动还没结束,这时候如果立马执行scrollTo是有问题的。所以问题关键点是如何准确判断快速滚动时动画停止状态,然后在执行scrollTo动画,如果再卡顿,那就是JS动画的问题,可以再优化。这里我做了简单的处理,加了个定时器,判断onscroll事件没有触发的时候,说明滚动停止了。
let timer1 = null;
timer1 && clearTimeout(timer1);
timer1 = setTimeout(() => {
// 开启对齐动画
}, 100);
果然可以了,没有抖动的问题了。但是快速滚动要等停止才会触发对齐动画。快速滑动swipe手势有个减速过程,这个的过程会体现在整个操作流程上,感觉差那么点意思,接着研究下有没有css动画可以来优化这个操作体验。
scroll相关的属性
overscroll-behavior mdn:https://developer.mozilla.org/en-US/docs/Web/CSS/overscroll-behavior 这里有个scroll chaining的概念,比如滚动容器里面还有个滚动容器,当里面的滚动容器滚到边界后,外面的滚动容器会接着滚动。我们这里不希望这种行为,所以这里需要设置成contain或者none,仅限于在滚动容器元素上使用。
-webkit-overflow-scrolling mdn:https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-overflow-scrolling 设置滚动元素是否开启惯性滚动,不建议使用,apple Safari文档写的是iOS 5.0之后支持的属性。仅限于在滚动容器元素上使用。
scroll-behavior MDN:https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior 为滚动容器指定默认的滚动行为,仅限于在滚动容器元素上使用,不影响用户的滚动操作,指定滚动过程的补间动画,比如#锚点定位的滚动,目前浏览器支持smooth平滑滚动。ios 14.5测了没有效果,看文档写的ios16支持,没有测。这个属性实测浏览器和手机上都有坑。 问题1:Mac chrome108版本模拟器下,设置后,调用scrollView的scrollTo方法会失效,改变scrollView的scrollLeft属性没有反应,Android手机上测试正常。
scroll-margin MDN:https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-margin 表述起来有点麻烦,可以看下原文,主要是设定滚动时捕捉的滚动区域边距,增加的边距内容也可以捕捉展示。
scroll-snap-type MDN:https://developer.mozilla.org/zh-CN/docs/Web/CSS/scroll-snap-type 设定滚动容器滚动的捕捉点,作用于滚动容器。支持设置在滚动容器滚动停止后,滚动容器会对其到滚动捕捉点,配合scroll-snap-align使用。具体看文档解释。遇到救星,这个正好能符合我们的产品要求。
scroll-snap-align MDN:https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-align 设定滚动容器捕捉的滚动元素的对齐位置,作用域滚动元素上。可以看下官方示例。
scroll-snap-stop MDN:https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-stop 设定滚动容器是否可以跳过可能捕捉的捕捉点。不能跳过时,像快速滑动swipe就会停到下一个捕捉点。 优化 最后基于scroll-snap-type属性来做滚动对齐优化,查了下兼容性还不错,不支持的时候用前面的备用方案。
只修改css就可以,示意代码:
.scroll-container {
scroll-snap-type: x mandatory;
-webkit-scroll-snap-type: x mandatory;
.scroll-element {
scroll-snap-align: center;
scroll-snap-stop: always;
}
}
测试效果能实现,但是引入另外一个问题,swipe手势回弹效果有点夸张,左右回弹太厉害。
这块官方文档没有提到,我猜可能是跟哪个样式有关系,冲突了导致的。经过大量尝试,测试出来几个坑。 坑1:scroll-snap-type 和scroll-behavior 一起使用,在Android 12下测试会有bug,滚动容器滚动到最后捕捉对齐之后,再次滚动会出现手势失效的问题。比如水平滚动,滚动到最右边后,再向右滑动一次,然后第一次向左滑动,没反应,第二次滑动手势才有反应。左边界同样。 坑2:滚动容器设置了scroll-snap-type之后,不能修改滚动容器及其一级父元素的宽高,否则会导致swiper手势产生空白回弹,如上图所示。这个是相当的坑,推研了好久才想起了可能是这个原因,可能是浏览器在swiper手势后计算捕捉对齐的时候,需要用到容器宽高来计算。
最终方案
检查是否支持scroll-snap-type,支持则采用css动画对齐方案,不支持则采用scrollTo方案。宽高动画做到最外层父容器上,确保不会产生边界弹性,修复掉坑的问题,最终效果还不错的,Android和IOS都比较丝滑。