与滚动条绑定的动画

模仿钉钉官网动画

发表于 2023-05-13 13:29:49
更新于 2024-04-18 13:33:37
CSSJavascript

钉钉官网的效果

钉钉官网有个与滚动条绑定的页面动画,还是非常有创意的,但是我最早遇到类似动画效果的是苹果官网。

让我们动手来实践一下类似的效果,做完后将会得到一个类似的效果。

滚动了,页面怎么保持位置

不随滚动条移动的布局就是固定布局。但是 sticky 的布局方式,会使得我们实现这个效果更加容易。

我们将外层定一个比较高的高度,使得滚动条可以滚动,内层使用 sticky 布局,使得视口内,我们一直看到的是这一屏,这样我们才能在后续在这一屏上做动画。

sticky 的好处就是,在进入视口后,它会自动有一个固定布局的效果,父容器离开视口后,会自动实效,无需 JS 的参与,所以非常方便。

与滚动位置绑定动画

剩下的就是如何执行动画了,这一步就非常自由了,比如钉钉是在 DOM 元素上应用动画,我的例子使用了 Lottie 来播放动画。

我的需求时,在第二屏完全进入视口后,开始播放动画第一帧,在第三屏进入视口前,就是动画的最后一帧。

这样我们就可以得到一个滚动位置的开始位置和结束位置。

开始位置是第二屏的整体元素的 offsetTop

结束位置是在开始位置的基础上加上第二屏的整体高度,再减去一个屏幕的大小,即 scroller.offsetTop + scroller.clientHeight - document.documentElement.clientHeight

由于我用的 Lottie 动画是 121 帧的,所以我需要计算一下滚动多少距离后播放一帧 (121 / (end - start) * (curr - start)) >> 0,我使用了位运算来取整。

这样,就能实现我截图中的动画效果了,整体来说并不难,但是却可以得到一个不错的效果,感觉更考验设计师吧。

代码

html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + TS</title>
  </head>
  <body>
    <div class="screen">Screen 1</div>
    <div class="ani-screen">
      <div class="screen">
        <lottie-player
          mode="normal"
          src="https://assets2.lottiefiles.com/packages/lf20_riAqnQrYxZ.json"
          style="width: 320px"
        >
        </lottie-player>
      </div>
    </div>
    <div class="screen">Screen 3</div>
    <script src="https://unpkg.com/@lottiefiles/lottie-player@1.5.7/dist/lottie-player.js"></script>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>
css
body {
  margin: 0;
}

.screen {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 60px;
}

.ani-screen {
  height: 3000px;
}

.ani-screen > .screen {
  position: sticky;
  top: 0;
}
typescript
import './style.css'

const scroller = document.querySelector('.ani-screen')!
const player = document.querySelector("lottie-player") as any;

document.addEventListener('scroll', function () {
  let curr = window.scrollY
  let start = scroller.offsetTop
  let end = scroller.offsetTop + scroller.clientHeight - document.documentElement.clientHeight

  if (curr >= start && curr <= end) {
    player.seek(
      (121 / (end - start) * (curr - start)) >> 0
    );
  }
});

可用的三方库

如果不想手写这个效果的逻辑,我看到了有现成的三方库可以选用:lax.js