物理学的 H5 应用:模拟惯性滑动

如题所述

第1个回答  2022-06-24

在移动端 H5 中,时间选择器( date-picker )、省市区选择器( area-picker )等组件经常会使用这样的交互效果:

这个 gif 是在【微信钱包 - 账单】中录制的 ios 原生时间选择器。可见, 当用户手指在选择器上先是滑动再从屏幕上移开,内容会继续保持一段时间的滚动效果,并且滚动的速度和持续的时间是与滑动手势的强烈程度成正比。 这种交互思路源于 ios 系统原生元素的滚动回弹( momentum-based scrolling ),来看 H5 的一个普通列表在 ios 上的滚动表现:

社区上大部分的移动端组件库的选择器组件都采取了这种交互方式,看看效果:

weui 的选择器实现了惯性滑动,但滑动动画结束得有点突兀,效果一般。

vant 的选择器压根没有做惯性滑动,当手指从屏幕上移开后,选择器的滑动会立刻停止。可见这样的交互体验是比较差的。

接下来我会从设计层面剖析和模拟惯性滑动的交互效果。

不难想象,惯性滑动非常贴合现实生活中的一些场景,如汽车刹车等。除此之外,与物理力学中的滑块模型也十分相似,由此我会参考滑块模型来剖析惯性滑动的全过程。

惯性 来源于物理学中的惯性定律(即 牛顿第一定律 ):一切物体在没有受到力的作用的时候,运动状态不会发生改变,物体所拥有的这种性质就被称为惯性。我们不妨把惯性滑动模拟成滑动滑块然后释放的过程(以下讨论中用户滑动的目标皆模拟成 滑块 ),主要划分为两个阶段:

描述滑块的惯性滑动,首先需要求出滑动的距离。在上述二阶段中,滑块受摩擦力 作 匀减速直线运动 。假设滑动距离为 ,初速度为 ,末速度为 。根据位移公式

加速度公式

可以算出惯性滑动距离

由于匀减速运动的加速度为负,不妨设一个加速度常量 ,使其满足 ,那么

这里 为正数。也就是说,我们只需要求出初始速度即可。

关注第一个阶段,假设用户滑动滑块的距离为 ,滑动的持续时间是 ,那么二阶段的初速度 可以根据位移公式求得

综上,求惯性滑动的距离我们需要记录用户滑动滑块的 距离 持续时间 ,并设置一个合理的 加速度常量

注意,这里的距离和持续时间并不是用户滑动滑块的总距离和时长,而是触发惯性滑动范围内的距离和时长,详见【惯性滑动的启动条件】。

针对二阶段的匀减速直线运动,时间段 产生的位移差 ,其中 。也就是说时间越往后,同等时间间距下通过的位移越来越小,也就是动画的推进速度越来越慢。

这与 CSS3 transition-timing-function 中的 ease-out 速度曲线相吻合, ease-out (即 cubic-bezier(0, 0, .58, 1) )的贝塞尔曲线为

上图来自 在线绘制贝塞尔曲线网站 。图表中的纵坐标是指 动画推进的进程 ;横坐标是指 时间 ;原点坐标为 (0, 0) ,终点坐标为 (1, 1) ,假设动画持续时间为2秒, (1, 1) 坐标点则代表离动画开始2秒时动画执行完毕(100%)。根据图表可以得出,时间越往后动画进程的推进速度越慢,符合匀减速直线运动的特性。

然而这样的速度曲线过于线性平滑,减速效果不明显。我们基于 ios 滚动回弹的效果,调整贝塞尔曲线的参数为 cubic-bezier(.17, .89, .45, 1) 。

滑块滑动不是无边界的,我们来考虑这样的场景:当滑块向下滑动,其顶部正要接触容器上边界时速度还没有降到 ,此时如果让滑块瞬间停止运动,这样的交互效果是不理想的。

我们可以把上边界想象成一条与滑块紧密贴合的固定弹簧, 当滑块到达临界点而速度还没有降到 时,滑块会继续滑动并拉动弹簧使其往下形变,同时会受到弹簧的反拉力作减速运动(动能转化为内能);当滑块速度降为 ,此时弹簧的形变量最大,由于弹性特质弹簧会恢复原状(内能转化成动能),从而拉动滑块反向运动

回弹过程也可以分为两个阶段:

根据上述分析,回弹的第一阶段作加速度越来越大的变减速直线运动,设此阶段的初速度为 ,可以与 建立以下关系

那么回弹距离为

微积分都来了,简直没法算好吧…

我们可以根据运动模型来简化 的计算,由于该阶段的加速度大于 非回弹惯性滑动 的加速度,设 非回弹惯性滑动 的总距离为 ,那么

所以可以设置一个合理的常量 ,使其满足

整个触发回弹的惯性滑动模型包括三个运动阶段:

然而把 阶段a 和 阶段b 描绘成 CSS 动画是有一定复杂度和风险的:

出于简化的考虑,可以将 阶段a、b 合并为一个运动阶段:

对于合并后的 阶段a 末段,由于反向加速度越来越大,因此滑块减速的效率会比 非回弹惯性滑动 同期更大,对应的贝塞尔曲线末段也会更陡,参数调整为 cubic-bezier(.25, .46, .45, .94) 。

在 阶段b 中,滑块先变加速后变减速,尝试 ease-in-out 的动画曲线:

可以看出,由于 阶段b 初始的 ease-in 曲线使 阶段a、b 的衔接段稍有停留,效果体验一般。所以我们选择只描绘变减速运动这一段,调整贝塞尔曲线为 cubic-bezier(.165, .84, .44, 1) 。

一次惯性滑动可能会出现两种情况:

惯性滑动的启动需要有足够的动量。我们可以简单地认为,当用户滑动的距离足够大(大于 15px )和持续时间足够短(小于 300ms )时,即可产生惯性滑动。也就是说,最后一次 touchmove 事件触发的时间和 touchend 事件触发的时间间隔小于 300ms ,且两者产生的距离差大于 15px 时认为启动惯性滑动。

当惯性滑动未结束(包括处于回弹过程),用户再次触碰滑块时会暂停滑块的运动。原理上是通过 getComputedStyle 和 getPropertyValue 方法获取当前的 transform: matrix() 矩阵值,抽离出水平 y 轴偏移量后重新调整 translate 的位置。

demo 基于 vuejs 实现,预览地址: https://codepen.io/JunreyCen/pen/arRYem

相似回答
大家正在搜