CSS 中动画出场的小巧系统

Avatar of Neale Van Fleet
Neale Van Fleet

DigitalOcean 为您的旅程各个阶段提供云产品。立即开始使用 200 美元的免费信用额度!

我喜欢那些让网站不仅仅是一个静态文档的小细节。如果网页内容在页面加载时不会只是“出现”,而是弹出、滑入、淡入或旋转到位呢?说这样的动作总是很有用可能有点牵强,不过在某些情况下,它们可以吸引人们对特定元素的注意,强调哪些元素彼此不同,甚至表明状态发生了变化。所以,它们也不是完全无用。

因此,我创建了一组 CSS 实用程序,用于在元素进入视图时对其进行动画处理。是的,这是纯 CSS。它不仅有各种动画和变体,还支持交错这些动画,几乎就像创建场景一样。

你知道,像这样的东西

这其实只是这个的更花哨版本

我们将首先回顾一下我用来创建动画的基础,然后深入了解我添加的细微之处、如何交错动画,然后是如何将它们应用于 HTML 元素,最后我们也将看看如何在尊重用户减少运动偏好的情况下完成所有这些。

基础

核心思想是在页面加载时应用于我们要动画处理的任何内容,添加一个简单的 CSS @keyframes 动画。让我们使其在半秒钟内从 opacity: 0 淡入到 opacity: 1

.animate {
  animation-duration: 0.5s;
  animation-name: animate-fade;
  animation-delay: 0.5s;
  animation-fill-mode: backwards;
}

@keyframes animate-fade {
  0% { opacity: 0; }
  100% { opacity: 1; }
}

另外请注意,这里还有一个 animation-delay,为 0.5 秒,以便让网站的其他部分先加载。animation-fill-mode: backwards 是为了确保我们的初始动画状态在页面加载时处于活动状态。没有它,我们的动画元素会在我们希望它出现之前弹出视图。

如果我们很懒,我们可以直接这样做。但是,CSS-Tricks 的读者当然不会懒,所以让我们看看如何使用系统来使这种事情变得更好。

更花哨的动画

使用多种动画而不是一两种动画更有趣。我们甚至不需要创建一堆新的 @keyframes 来制作更多动画。创建新的类很简单,我们只需要更改动画使用的帧,而将所有时间都保持相同。

CSS 动画几乎有无限种。 (查看 animate.style 以了解大量的集合。) CSS 滤镜,如 blur()brightness()saturate() 以及当然还有 CSS 变换 也可以用来创建更多变化。

但现在,让我们从一个新的动画类开始,该类使用 CSS 变换使元素“弹出”到位。

.animate.pop {
  animation-duration: 0.5s;
  animation-name: animate-pop;
  animation-timing-function: cubic-bezier(.26, .53, .74, 1.48);
}

@keyframes animate-pop {
  0% {
    opacity: 0;
    transform: scale(0.5, 0.5);
  }

  100% {
    opacity: 1;
    transform: scale(1, 1);
  }
}

我添加了一个小的 cubic-bezier() 时间曲线,这得益于 Lea Verou 不可或缺的 cubic-bezier.com,用于实现弹性反弹。

添加延迟

我们可以做得更好!例如,我们可以对元素进行动画处理,使其在不同的时间进入。这会创建一个交错效果,在无需大量代码的情况下,就可以实现看起来很复杂的运动。

这个使用 CSS 滤镜、CSS 变换并在大约 1/10 秒内交错的三个页面元素的动画效果非常棒

我们所做的只是为每个元素创建了一个新的类,该类用于间隔元素开始动画的时间,使用相差约 1/10 秒的 animation-delay 值。

.delay-1 { animation-delay: 0.6s; }  
.delay-2 { animation-delay: 0.7s; }
.delay-3 { animation-delay: 0.8s; }

其他所有内容都完全相同。请记住,我们的基本延迟是 0.5s,因此这些辅助类从此开始计数。

尊重辅助功能偏好

让我们做个好网民,并为启用了 减少运动偏好 设置的用户移除动画

@media screen and (prefers-reduced-motion: reduce) {
  .animate { animation: none !important; }
}

这样,动画永远不会加载,元素会像正常一样进入视图。不过,这里值得提醒的是,“减少”运动并不一定意味着“移除”运动.

将动画应用于 HTML 元素

到目前为止,我们已经看到了基本动画以及稍微更花哨的动画,我们能够使用交错的动画延迟(包含在新的类中)来使它变得更加花哨。我们还看到了如何在同时尊重用户运动偏好的情况下做到这一点。

尽管有一些实时演示展示了这些概念,但我们还没有真正讲解如何将我们的工作应用于 HTML。酷的是,我们可以将其应用于几乎任何元素,无论是 div、span、article、header、section、table、form… 你懂的。

我们将这样做。我们希望将我们的动画系统用于三个 HTML 元素,每个元素获得三个类。我们可以将所有动画代码硬编码到元素本身,但将其拆分会给我们一个可以重复使用的动画系统。

  • .animate: 这是包含我们核心动画声明和时间的基本类。
  • 动画类型: 我们将使用之前使用的“弹出”动画,但也可以使用淡入的动画。这个类在技术上是可选的,但它是应用不同动作的好方法。
  • .delay-<number>: 如前所述,我们可以创建不同的类,用于交错每个元素上的动画开始时间,从而产生一种整洁的效果。这个类也是可选的。

因此,我们的动画元素现在可能看起来像这样

<h2 class="animate pop">One!</h2>
<h2 class="animate pop delay-1">Two!</h2>
<h2 class="animate pop delay-2">Three!</h2>

让我们数一数它们!

结论

看看吧:我们从一组看似简单的 @keyframes 开始,并将其变成一个完整的系统,用于为进入视图的元素应用有趣的动画。

这当然非常有趣。但我最大的收获是,我们所看到的示例形成了一个完整的系统,可以用来创建基线、不同类型的动画、交错延迟以及尊重用户运动偏好的方法。我认为,这些都是灵活且易于使用的系统的全部要素。它用很少的东西就给了我们很多,而且没有一堆多余的垃圾。

我们所涵盖的内容确实可以成为一个完整的动画库。但是,当然,我没有止步于此。我将我所有的动画 CSS 文件都提供给你,其中包含更多类型的动画,包括 15 个用于交错事物的不同延迟类。我一直在自己的项目中使用这些,但它仍然是一个早期草案,我很乐意收到关于它的反馈。请尽情享受,并在评论中告诉我你的想法!

/* ==========================================================================
Animation System by Neale Van Fleet from Rogue Amoeba
========================================================================== */
.animate {
  animation-duration: 0.75s;
  animation-delay: 0.5s;
  animation-name: animate-fade;
  animation-timing-function: cubic-bezier(.26, .53, .74, 1.48);
  animation-fill-mode: backwards;
}

/* Fade In */
.animate.fade {
  animation-name: animate-fade;
  animation-timing-function: ease;
}

@keyframes animate-fade {
  0% { opacity: 0; }
  100% { opacity: 1; }
}

/* Pop In */
.animate.pop { animation-name: animate-pop; }

@keyframes animate-pop {
  0% {
    opacity: 0;
    transform: scale(0.5, 0.5);
  }
  100% {
    opacity: 1;
    transform: scale(1, 1);
  }
}

/* Blur In */
.animate.blur {
  animation-name: animate-blur;
  animation-timing-function: ease;
}

@keyframes animate-blur {
  0% {
    opacity: 0;
    filter: blur(15px);
  }
  100% {
    opacity: 1;
    filter: blur(0px);
  }
}

/* Glow In */
.animate.glow {
  animation-name: animate-glow;
  animation-timing-function: ease;
}

@keyframes animate-glow {
  0% {
    opacity: 0;
    filter: brightness(3) saturate(3);
    transform: scale(0.8, 0.8);
  }
  100% {
    opacity: 1;
    filter: brightness(1) saturate(1);
    transform: scale(1, 1);
  }
}

/* Grow In */
.animate.grow { animation-name: animate-grow; }

@keyframes animate-grow {
  0% {
    opacity: 0;
    transform: scale(1, 0);
    visibility: hidden;
  }
  100% {
    opacity: 1;
    transform: scale(1, 1);
  }
}

/* Splat In */
.animate.splat { animation-name: animate-splat; }

@keyframes animate-splat {
  0% {
    opacity: 0;
    transform: scale(0, 0) rotate(20deg) translate(0, -30px);
    }
  70% {
    opacity: 1;
    transform: scale(1.1, 1.1) rotate(15deg);
  }
  85% {
    opacity: 1;
    transform: scale(1.1, 1.1) rotate(15deg) translate(0, -10px);
  }

  100% {
    opacity: 1;
    transform: scale(1, 1) rotate(0) translate(0, 0);
  }
}

/* Roll In */
.animate.roll { animation-name: animate-roll; }

@keyframes animate-roll {
  0% {
    opacity: 0;
    transform: scale(0, 0) rotate(360deg);
  }
  100% {
    opacity: 1;
    transform: scale(1, 1) rotate(0deg);
  }
}

/* Flip In */
.animate.flip {
  animation-name: animate-flip;
  transform-style: preserve-3d;
  perspective: 1000px;
}

@keyframes animate-flip {
  0% {
    opacity: 0;
    transform: rotateX(-120deg) scale(0.9, 0.9);
  }
  100% {
    opacity: 1;
    transform: rotateX(0deg) scale(1, 1);
  }
}

/* Spin In */
.animate.spin {
  animation-name: animate-spin;
  transform-style: preserve-3d;
  perspective: 1000px;
}

@keyframes animate-spin {
  0% {
    opacity: 0;
    transform: rotateY(-120deg) scale(0.9, .9);
  }
  100% {
    opacity: 1;
    transform: rotateY(0deg) scale(1, 1);
  }
}

/* Slide In */
.animate.slide { animation-name: animate-slide; }

@keyframes animate-slide {
  0% {
    opacity: 0;
    transform: translate(0, 20px);
  }
  100% {
    opacity: 1;
    transform: translate(0, 0);
  }
}

/* Drop In */
.animate.drop { 
  animation-name: animate-drop; 
  animation-timing-function: cubic-bezier(.77, .14, .91, 1.25);
}

@keyframes animate-drop {
0% {
  opacity: 0;
  transform: translate(0,-300px) scale(0.9, 1.1);
}
95% {
  opacity: 1;
  transform: translate(0, 0) scale(0.9, 1.1);
}
96% {
  opacity: 1;
  transform: translate(10px, 0) scale(1.2, 0.9);
}
97% {
  opacity: 1;
  transform: translate(-10px, 0) scale(1.2, 0.9);
}
98% {
  opacity: 1;
  transform: translate(5px, 0) scale(1.1, 0.9);
}
99% {
  opacity: 1;
  transform: translate(-5px, 0) scale(1.1, 0.9);
}
100% {
  opacity: 1;
  transform: translate(0, 0) scale(1, 1);
  }
}

/* Animation Delays */
.delay-1 {
  animation-delay: 0.6s;
}
.delay-2 {
  animation-delay: 0.7s;
}
.delay-3 {
  animation-delay: 0.8s;
}
.delay-4 {
  animation-delay: 0.9s;
}
.delay-5 {
  animation-delay: 1s;
}
.delay-6 {
  animation-delay: 1.1s;
}
.delay-7 {
  animation-delay: 1.2s;
}
.delay-8 {
  animation-delay: 1.3s;
}
.delay-9 {
  animation-delay: 1.4s;
}
.delay-10 {
  animation-delay: 1.5s;
}
.delay-11 {
  animation-delay: 1.6s;
}
.delay-12 {
  animation-delay: 1.7s;
}
.delay-13 {
  animation-delay: 1.8s;
}
.delay-14 {
  animation-delay: 1.9s;
}
.delay-15 {
  animation-delay: 2s;
}

@media screen and (prefers-reduced-motion: reduce) {
  .animate {
    animation: none !important;
  }
}