book
归档: 前端 
flag
mode_edit

在虚拟页面里寻找真实感动画的人一定有问题

浏览器自带

如何在锚点之间平滑滚动?在之前可能要依赖各种奇怪的库,而现在只需要使用

window.scrollTo({top: 0, behavior: 'smooth'})

看起来很棒。

然而这样操作有一个大问题,不同浏览器对它的实现是不一样的,在某些很长的页面上,这个动画时间甚至可以达到 3 秒,令人怀疑人生。因此可能还是手写比较好。

手写动画

如何手写呢?我首先想到的是开一个时间间隔是 160 s 的定时器,然后每次滚动一帧。但是很不幸,JS作为一门屑语言是单线程的,定时器完全没有实时性可言。好在浏览器为我们提供了 requestAnimationFrame ,让我们可以以稳定的时间间隔进行动画的绘制。

既然手写动画了,不如来调一个好看的动画曲线。

动画曲线

我首先想到的是据称很强大的 iOS 动画,在网上一顿搜索之后找到了 一个隐藏的动画曲线 这篇文章。虽然它是键盘弹出的动画,但是个人感觉似乎也很适合用作锚点之间平滑滚动的动画。放到文章的最后,作者得出结论:

原来这是一个 SpringAnimation,从这个角度看,它已经不是一个动画曲线了。 常见的 SpringAnimation 都是在终点值附近波动几下,所以很难看出来它是一个 SpringAnimation。在阻尼震动中,在终点值附近波动的是欠阻尼状态,即日常所说的弹簧效果。当阻尼系数大于某个值时,震动不会在终点值附近波动,而是缓慢靠近终点值,这种情况叫过阻尼。而我们的动画曲线就是处于过阻尼的状态。

再往下看下去,发现 iOS 已经提供了基于物理的弹簧曲线。参考价值已经不大了。

看起来,需要我们手动模拟基于物理的弹簧了。

弹簧模型

在这个振动模型中,可以调整的参数有:

  • 与弹簧连接的物体的质量 $m$
  • 弹簧的劲度系数 $k$
  • 物体的阻力系数 $\mathit{stiffness}$
  • 初始时,弹簧偏离平衡位置的距离。我将其固定为常数 $1$

我们将初始状态设定为,弹簧受到压缩,当我们松开它时,弹簧会把物体“弹射”出去。

根据物理知识,我们有 $$ \begin{align} F_k &= x\cdot k\
F_f &= -\mathit{stiffness}\cdot v\
ma &= kx-\mathit{stiffness}\cdot v \end{align} $$ 即, $$ m\ddot x =kx-\mathit{stiffness}\cdot \dot v $$ 转化成了我们并不熟悉的二阶微分方程。

我当然不会去手解这个方程的,微分方程全忘掉了。sagemath,启动。

t = var('t')
y = function('y')(x)

k = 500        # 劲度系数 F_k = k * x
stiffness = 100 # 阻力系数 F_f = stiffness * v
m = 3           # 质量


res = desolve(diff(y, x, 2) == (-k*(y - 1) - stiffness*diff(y, x))/m, y, ics=[0, 0, 0])

通过不断的手调参数,我们可以得到这样的结果。 $$ f(x)=\newcommand{\Bold}[1]{\mathbf{#1}}0.250707500000000 \, {\left(\sqrt{10} - 2\right)} e^{\left(-\frac{10}{3} \, x {\left(\sqrt{10} + 5\right)}\right)} - 0.250707500000000 \, {\left(\sqrt{10} + 2\right)} e^{\left(\frac{10}{3} \, x {\left(\sqrt{10} - 5\right)}\right)} + 1.00283000000000 $$ 它的图像如下:

它在 $1$ 处的取值是 $1.00000099891136\approx 1$。横轴是动画进度的百分比,纵轴是物体的位移百分比。

非常有趣的是,通过调整不同的参数,我们能得到很多有意思的曲线。

$k = 500,\mathit{stiffness}=50,m=3$

$k=1000,\mathit{stiffness}=20,m=1.5$

最后,动画的效果可以在 OI Wiki ng 上看到。

navigate_before navigate_next