我喜欢那些让网站不仅仅是一个静态文档的小细节。如果网页内容在页面加载时不会只是“出现”,而是弹出、滑入、淡入或旋转到位呢?说这样的动作总是很有用可能有点牵强,不过在某些情况下,它们可以吸引人们对特定元素的注意,强调哪些元素彼此不同,甚至表明状态发生了变化。所以,它们也不是完全无用。
因此,我创建了一组 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;
}
}
“其他所有内容都完全相同。请记住,我们的基本延迟是 0.5s,因此这些辅助类从此开始计数。”
也许我漏掉了什么,但我没有看到你的示例中是这种情况。0.6s、0.7s 和 0.8s 正在覆盖之前定义的 0.5s。根据你解释的方式,我理解为延迟应该是 0.5s + 0.6s、0.5s + 0.7s、0.5s + 0.8s。
很确定这是想表达
“默认延迟是0.5秒,所以我们只需要创建从那里开始计数的辅助函数”
太棒了
我喜欢你对延迟的分类方式,那个动画库和缓动曲线计算器对我来说是新的。
也许有点作弊,但我真的很喜欢 Animista(我更希望人们搜索而不是链接,但为了清晰起见,这里提供链接,而不是广告)。
你可以可视化并调整每个部分,甚至渐变,然后将 CSS 复制粘贴到任何地方。它使用类似的命名约定。
所以它并不能为你做所有事情,但它是我用过的最好的动画编辑器。
不错。我之前创建了一个类似的(但没有那么广泛)的小工具。
有一件事我一直没有做到,但这可能值得为你项目进行探索,那就是如何利用 CSS 变量使延迟类变得动态,从而不依赖于编号类系统(例如,不需要考虑“.delay-18”的可能性)。
例如,在产品页面上循环遍历生成的项目,你可能会有 20 个产品正在加载,每个项目都会将其索引值传递给 CSS,然后 CSS 会计算出延迟其动画的时间。
你也可以使用类似的 nth-child 方法,让 CSS 确定元素在元素顺序中的位置。
祝你好运!
如果它是生成的数据,你可以在父元素中将元素总数作为 CSS 变量放置,然后使用它来动态调整延迟。
我喜欢这个想法,但我还没有找到理想的实现方式。我欢迎大家提出想法。
我使用 Pug 生成一个包含 n 个子元素的容器(子元素的数量作为容器上的自定义属性设置,每个子元素也具有索引作为自定义属性)
这会生成以下 HTML
然后,对于样式,我通常将动画持续时间 t 设为 SCSS 变量(如果不需要动态,最好不要使用 CSS 变量,因为它会影响性能),并将基本延迟设为 t/n,然后将基本延迟乘以项目索引 i 来计算实际的项目延迟。虽然基本延迟可以是任何其他值。
在这种情况下,我还会做的一件事是放弃所有这些 100% 的关键帧,它们完全没有必要。在没有动画的情况下,这些是属性值,浏览器知道如果它们没有明确写出来,就会动画到这些值。
在 animate-drop 的情况下,opacity: 1 只有在 95% 的关键帧中是必要的。我还会使用一个单值进行统一缩放,因为看到像 scale(1.1, 1.1) 这样的东西非常令人困惑——我看了五遍才弄清楚这两个值之间的区别。如果我在 scale() 中看到两个值,我希望它们不同。
回复有点晚了,但我们在 animxyz.com 上也做了同样的事情,我们接受了创建像这样动画的所有样板代码,并让你使用一些类似实用类的属性和 CSS 变量轻松地组合它们。
使用索引 CSS 变量,我们可以处理任意数量的元素,并进行交错延迟,甚至在需要时进行反向延迟。
FWIW,我将它用于一个生成的图片库。
我没有创建一大堆 delay-X 类,我只是在元素上设置了延迟作为样式属性。
在循环之前,将计数器设置为基本延迟(0.5),然后每次迭代增加 0.1。只要你按照你想要显示的顺序生成它们,这个方法就很好用。
Ana——感谢你的评论。我将在有机会的时候更新代码。
Jill——很高兴看到它被用于某些东西。谢谢!
这些精美的动画会对网页核心指标产生负面影响吗?
需要进行一些调查。有可能。
不错
这正是我在 2013 年的 max WordPress 主题中创建 CSS 动画的方式。当元素进入页面的实际“视图”时,添加 load 类。
很酷!我认为它可以从一些包含某些重复值的变量以及一些恒定的参数中受益。
例如,cubic-bezier() 本身可以用 vars 提供,就像可组合的 hsl() 一样,但要达到更高的抽象级别,甚至可以保存一个只存储“–timing-function-type: cubic-bezier();”的变量来交换计时。有点过度,但仍然。
最后,动画延迟实用程序应该有一个增量值,它应用 calc() 以获得更大的灵活性,但我不知道从性能角度来说这是否重要。但组件级别的更新和调整维护将得到极大改善,就像设计系统一样;它们首先服务于 DX,而不是 UX。
这是最终用户受益的结果,而设置它的过程是“我们”需要受益的。
最后,prefers reduce motion 并不意味着“完全没有运动”,所以如果你认为某些 UI 消息需要它们来传达消息和意义,你对此的看法是什么?
也许我钻得太深了,但我喜欢像这样的彻底谦虚和硬核的系统。我用它来作为我自己的 DS 的参考,并及时应用了一些反馈。
感谢分享!
感谢你的评论。
我之前没有想到在 cubic-bezier 中使用变量,但这可能非常聪明。你可以将第三个值视为减速(有点像),将第四个值视为它会超调多少。
至于使用 calc 来实现延迟,我最初确实有一个这样的变体!很棒,但我为了简化文章而把它删掉了。这是一个很棒的功能,可以添加回来,很可能作为一个乘数变量添加到延迟类中。我可能会在某个时候将其添加到我的生产版本中。
关于 reduced motion 这一点非常正确。我曾考虑过在打开 reduced motion 时,将所有动画都转换为带有零延迟的淡入淡出。这可能比完全没有动画更合适。
我喜欢将
calc()
与放置在元素中的自定义属性一起使用来分配淡入顺序