最近做了一些移动端的交互 Demo,需要在用户一边滑动容器时,一边做一些 UI 上的变化。传统的做法是依靠 scroll
事件,很简单:
const handleScroll = () => {
const { scrollLeft, scrollWidth } = element
}
const handleScroll = () => {
const { scrollLeft, scrollWidth } = element
}
但会遇到以下几个问题:
scroll
事件的触发频率没有非常高,无法做顺畅的 UI 变化;scrollend
事件,但 Safari 无兼容性);scroll-snap
相关的属性。从我用过原生 CSS 的 scroll-snap
的经验来看,这个属性虽然很方便,但是和最舒服的体验总是不一样。实际上,模拟一个移动端滚动,无非是以 transform
属性为核心,通过弹簧动画来模拟。这类似于我在 转盘交互动画:以关键参数,细化我们的感受
这篇文章中所做的事。
motion/react
提供了一个 dragConstraints
属性,可以用来限制拖拽的范围。但是我的需求是需要在滚动的过程中实时变换 dragConstraints
的值,而这是 motion/react
做不到的。至于是什么需求,暂时还不能说。
motion/react
的做法是直接将超出的部分乘以 0.35,以达到橡皮筋的效果。这种简单的线性做法实际上并不是 iOS 的做法。下面的公式才是 iOS 的做法:
x = (1.0 - 1.0 / ((x * c) / d + 1.0)) * d
x = (1.0 - 1.0 / ((x * c) / d + 1.0)) * d
其中:
const applyRubberBand = (x: number, edge: number, dimension: number) => {
const c = 0.55
return (
(1.0 - 1.0 / ((Math.abs(x - edge) * c) / dimension + 1.0)) *
dimension *
Math.sign(x - edge) +
edge
)
}
const applyRubberBand = (x: number, edge: number, dimension: number) => {
const c = 0.55
return (
(1.0 - 1.0 / ((Math.abs(x - edge) * c) / dimension + 1.0)) *
dimension *
Math.sign(x - edge) +
edge
)
}
这个公式有意思的地方就在于:
transform
已经是 web 端的最优解了,但性能一定不如 iOS 原生。虽然差别没有那么大,但只要横向对比着体验,还是能感受到差别。