CSS轮播图
分享一个实际业务中能够用得上的动画技巧。
巧用逐帧动画,配合补间动画实现一个无限循环的轮播效果,像是这样:
实现轮播图 - 图1
看到上述示意图,有同学不禁会发问,这不是个非常简单的位移动画么?
来简单分析分析,从表面上看,确实好像只有元素的 transform: translate() 在位移,但是注意,这里有两个难点:

  1. 这是个无限轮播的效果,动画需要支持任意多个元素的无限轮播切换
  2. 因为是轮播,所以,运行到最后一个的时候,需要动画切到第一个元素

到这里,可以暂停思考一下,如果有 20 个元素,需要进行类似的无限轮播播报,使用 CSS 实现,会怎么去做呢?

逐帧动画控制整体切换

首先,需要利用到逐帧动画效果,也被称为步骤缓动函数,利用的是 animation-timing-function 中,的 steps,语法如下:

  1. {
  2. /* Keyword values */
  3. animation-timing-function: step-start;
  4. animation-timing-function: step-end;
  5. /* Function values */
  6. animation-timing-function: steps(6, start)
  7. animation-timing-function: steps(4, end);
  8. }

还是以开头的例子,假设存在这样 HTML 结构:

  1. <div class="g-container">
  2. <ul>
  3. <li>Lorem ipsum 1111111</li>
  4. <li>Lorem ipsum 2222222</li>
  5. <li>Lorem ipsum 3333333</li>
  6. <li>Lorem ipsum 4444444</li>
  7. <li>Lorem ipsum 5555555</li>
  8. <li>Lorem ipsum 6666666</li>
  9. </ul>
  10. </div>

首先,实现这样一个简单的布局:
实现轮播图 - 图2
在这里,要实现轮播效果,并且是任意个数,可以借助 animation-timing-function: steps()

  1. :root {
  2. // 轮播的个数
  3. --s: 6;
  4. // 单个 li 容器的高度
  5. --h: 36;
  6. // 单次动画的时长
  7. --speed: 1.5s;
  8. }
  9. .g-container {
  10. width: 300px;
  11. height: calc(var(--h) * 1px);
  12. }
  13. ul {
  14. display: flex;
  15. flex-direction: column;
  16. animation: move calc(var(--speed) * var(--s)) steps(var(--s)) infinite;
  17. }
  18. ul li {
  19. width: 100%;
  20. }
  21. @keyframes move {
  22. 0% {
  23. transform: translate(0, 0);
  24. }
  25. 100% {
  26. transform: translate(0, calc(var(--s) * var(--h) * -1px));
  27. }
  28. }

别看到上述有几个 CSS 变量就慌了,其实很好理解:

  1. calc(var(--speed) * var(--s)):单次动画的耗时 * 轮播的个数,也就是总动画时长
  2. steps(var(--s)) 就是逐帧动画的帧数,这里也就是 steps(6),很好理解
  3. calc(var(--s) * var(--h) * -1px)) 单个 li 容器的高度 * 轮播的个数,其实就是 ul 的总体高度,用于设置逐帧动画的终点值

上述的效果,实际如下:
实现轮播图 - 图3
如果给容器添加上 overflow: hidden,就是这样的效果:
实现轮播图 - 图4
这样,就得到了整体的结构,至少,整个效果是循环的。
但是由于只是逐帧动画,所以只能看到切换,但是每一帧之间,没有过渡动画效果。所以,接下来,还得引入补间动画。

利用补间动画实现两组数据间的切换

需要利用补间动画,实现动态的切换效果。
这一步,其实也非常简单,要做的,就是将一组数据,利用 transform,从状态 A 位移到 状态 B。
单独拿出一个来演示的话,大致的代码如下:

  1. <div class="g-container">
  2. <ul style="--s: 6">
  3. <li>Lorem ipsum 1111111</li>
  4. <li>Lorem ipsum 2222222</li>
  5. <li>Lorem ipsum 3333333</li>
  6. <li>Lorem ipsum 4444444</li>
  7. <li>Lorem ipsum 5555555</li>
  8. <li>Lorem ipsum 6666666</li>
  9. </ul>
  10. </div>
  1. :root {
  2. --h: 36;
  3. --speed: 1.2s;
  4. }
  5. ul li {
  6. height: 36px;
  7. animation: liMove calc(var(--speed)) infinite;
  8. }
  9. @keyframes liMove {
  10. 0% {
  11. transform: translate(0, 0);
  12. }
  13. 80%,
  14. 100% {
  15. transform: translate(0, -36px);
  16. }
  17. }

非常简单的一个动画:
bgg1
基于上述效果,如果把一开始提到的 逐帧动画 和这里这个 补间动画 结合一下,ul 的整体移动,和 li 的 单个移动叠在在一起:

  1. :root {
  2. // 轮播的个数
  3. --s: 6;
  4. // 单个 li 容器的高度
  5. --h: 36;
  6. // 单次动画的时长
  7. --speed: 1.5s;
  8. }
  9. .g-container {
  10. width: 300px;
  11. height: calc(var(--h) * 1px);
  12. }
  13. ul {
  14. display: flex;
  15. flex-direction: column;
  16. animation: move calc(var(--speed) * var(--s)) steps(var(--s)) infinite;
  17. }
  18. ul li {
  19. width: 100%;
  20. animation: liMove calc(var(--speed)) infinite;
  21. }
  22. @keyframes move {
  23. 0% {
  24. transform: translate(0, 0);
  25. }
  26. 100% {
  27. transform: translate(0, calc(var(--s) * var(--h) * -1px));
  28. }
  29. }
  30. @keyframes liMove {
  31. 0% {
  32. transform: translate(0, 0);
  33. }
  34. 80%,
  35. 100% {
  36. transform: translate(0, calc(var(--h) * -1px));
  37. }
  38. }

就能得到这样一个效果:
实现轮播图 - 图6
神奇的化学反应产生了!基于 逐帧动画补间动画 的结合,几乎实现了一个轮播效果。
当然,有一点瑕疵,可以看到,最后一组数据,是从第六组数据 transform 移动向了一组空数据:
实现轮播图 - 图7

末尾填充头部第一组数据

实际开发过轮播的同学肯定知道,这里,其实也很好处理,只需要在末尾,补一组头部的第一个数据即可:
改造下 HTML:

  1. <div class="g-container">
  2. <ul>
  3. <li>Lorem ipsum 1111111</li>
  4. <li>Lorem ipsum 2222222</li>
  5. <li>Lorem ipsum 3333333</li>
  6. <li>Lorem ipsum 4444444</li>
  7. <li>Lorem ipsum 5555555</li>
  8. <li>Lorem ipsum 6666666</li>
  9. <!--末尾补一个首条数据-->
  10. <li>Lorem ipsum 1111111</li>
  11. </ul>
  12. </div>

这样,再看看效果:
实现轮播图 - 图8
Beautiful!如果还有所疑惑,给容器加上 overflow: hidden,实际效果如下,通过额外添加的最后一组数据,整个动画刚好完美的衔接上,一个完美的轮播效果:
实现轮播图 - 图9
完整的代码,可以戳这里:CodePen Demo — Vertical Infinity Loop
点击查看【codepen】

横向无限轮播

当然,实现了竖直方向的轮播,横向的效果也是一样的。
并且,可以通过在 HTML 结构中,通过 style 内填写 CSS 变量值,传入实际的 li 个数,以达到根据不同 li 个数适配不同动画:

  1. <div class="g-container">
  2. <ul style="--s: 6">
  3. <li>Lorem ipsum 1111111</li>
  4. <li>Lorem ipsum 2222222</li>
  5. <li>Lorem ipsum 3333333</li>
  6. <li>Lorem ipsum 4444444</li>
  7. <li>Lorem ipsum 5555555</li>
  8. <li>Lorem ipsum 6666666</li>
  9. <!--末尾补一个首尾数据-->
  10. <li>Lorem ipsum 1111111</li>
  11. </ul>
  12. </div>

整个动画的 CSS 代码基本是一致的,只需要改变两个动画的 transform 值,从竖直位移,改成水平位移即可:

  1. :root {
  2. --w: 300;
  3. --speed: 1.5s;
  4. }
  5. .g-container {
  6. width: calc(--w * 1px);
  7. overflow: hidden;
  8. }
  9. ul {
  10. display: flex;
  11. flex-wrap: nowrap;
  12. animation: move calc(var(--speed) * var(--s)) steps(var(--s)) infinite;
  13. }
  14. ul li {
  15. flex-shrink: 0;
  16. width: 100%;
  17. height: 100%;
  18. animation: liMove calc(var(--speed)) infinite;
  19. }
  20. @keyframes move {
  21. 0% {
  22. transform: translate(0, 0);
  23. }
  24. 100% {
  25. transform: translate(calc(var(--s) * var(--w) * -1px), 0);
  26. }
  27. }
  28. @keyframes liMove {
  29. 0% {
  30. transform: translate(0, 0);
  31. }
  32. 80%,
  33. 100% {
  34. transform: translate(calc(var(--w) * -1px), 0);
  35. }
  36. }

这样,就轻松的转化为了横向的效果:
实现轮播图 - 图10
完整的代码,可以戳这里:CodePen Demo — Horizontal Infinity Loop
点击查看【codepen】

轮播图也不在话下

上面的只是文字版的轮播,那如果是图片呢?
没问题,方法都是一样的。基于上述的代码,可以轻松地将它修改一下后得到图片版的轮播效果。
代码都是一样的,就不再列出来,直接看看效果:
实现轮播图 - 图11
完整的代码,可以戳这里:CodePen Demo — Horizontal Image Infinity Loop
点击查看【codepen】
掌握了这个技巧之后,可以将它运用在非常多只需要简化版的轮播效果之上。
再简单总结一下,非常有意思的技巧:

  1. 利用 逐帧动画,实现整体的轮播的循环效果
  2. 利用 补间动画,实现具体的 状态A*状态B 的动画效果
  3. 逐帧动画 配合 补间动画 构成整体轮播的效果
  4. 通过向 HTML 结构末尾补充一组头部数据,实现整体动画的衔接
  5. 通过 HTML 元素的 style 标签,利用 CSS 变量,填入实际的参与循环的 DOM 个数,可以实现 JavaScript 与 CSS 的打通