
使用 linear() 函数在 CSS 动画中创建自定义缓动效果
动画不仅仅是将事物从一个地方移动到另一个地方。如何移动(或以某种方式更改)对于传达目的感同样重要。在动画世界中,对象在一段时间内从一种状态过渡到另一种状态的方式称为缓动。
在这篇文章中,我们将比较 CSS 中的不同缓动函数。CSS 中的linear()
函数是一个新的缓动函数,它使我们能够更好地控制动画的制作。我们将探讨linear()
函数的工作原理,并查看一些可以使用的实际示例。
CSS 中的缓动
正确的缓动类型对于使动画看起来自然和逼真至关重要。想象一下在一个地板上滚动一个球。它不会一直以相同的速度移动:它可能会迅速加速到最大速度,然后逐渐减速并停止(称为三次贝塞尔缓动)。将其与在整个动画时间线上以相同速度移动的球体进行比较(称为线性缓动)。感觉不太自然,是吗?
控制 CSS 动画中的缓动
在 CSS 中,我们使用animation-timing-function
属性将缓动应用于元素的动画。
.box {
animation-name: move;
animation-duration: 2000ms;
animation-timing-function: ease-in;
}
让我们回顾一些已有的 CSS 缓动技术。如果您已经熟悉这些内容,可以安全地跳过本节,并继续了解有关使用linear()
函数应用自定义缓动的部分。
缓动关键字
在 CSS 中制作动画时,我们可以从许多关键字中进行选择:ease-in
、ease-out
、ease-in-out
、ease
(默认值)和linear
。它们也可以通过简写animation
属性应用。
.box {
animation: move 2000ms ease-in;
}
Ease-in(缓入)
动画开始缓慢,然后加速,最终达到最大速度。例如,汽车起步。
Ease-out(缓出)
动画开始快速,然后在一段时间内减速——有点像球在木板上滚动。这里的区别是ease-out
没有显示初始加速。
Ease-in-out(缓入缓出)
动画同时使用上述两个原理:开始时逐渐加速,然后向结尾减速。
Ease(缓动)
这是默认值。与ease-in-out
类似,既有加速也有减速,但它们并不均匀。在这种情况下,动画有时可能看起来比ease-in-out
更自然。
以下是两个关键字的效果并排比较
Linear(线性)
在整个动画过程中速度没有变化。尽管通常不太逼真,但线性缓动在 UI 动画中很有用,例如滚动的一排徽标或图像。
缓动函数
缓动关键字对于简单的 UI 元素(例如,应用于按钮的悬停状态)很有用,但它们并不总是足以满足更复杂的动画或我们需要更好地控制速度的情况。CSS 为我们提供了一些用于制作自定义缓动的函数:steps()
和cubic-bezier()
。
Steps(分步)
steps()
函数将输入时间划分为指定数量的间隔——因此我们的动画将从一个值“跳转”到下一个值。我们可以使用许多可能的值指定步数和跳转位置(可选)。本文的目的不是深入探讨steps()
缓动,但我建议阅读有关分步缓动函数的内容,以了解不同值的影响。
.box {
animation-timing-function: steps(3, jump-none);
}
Cubic-bezier(三次贝塞尔)
cubic-bezier()
函数使我们能够更好地控制 CSS 动画中的缓动。该函数采用四个值,它们对应于三次贝塞尔曲线的两个控制点。
我们可以使用cubic-bezier()
创建更逼真的效果,例如使元素在静止前稍微超出其最终位置。
.ball {
animation-timing-function: cubic-bezier(0.57, 0.4, 0.55, 1.17);
}
如果您了解其背后的数学原理,则可以非常有创意地定义自己的自定义曲线,例如 Temani Afif 在这篇文章使用 cubic-bezier() 的高级 CSS 动画中所做的那样!
幸运的是,我们不需要成为数学天才就能使用cubic-bezier()
制作令人愉悦的缓动:Lea Verou 的三次贝塞尔工具允许我们调整和可视化自定义缓动曲线并导出结果。
三次贝塞尔缓动函数的局限性
尽管cubic-bezier()
用途广泛,但它也有其局限性,因为我们只能控制三次贝塞尔曲线的两个点。
假设我们希望元素从左向右滑动,然后以递减的距离和速度弹跳几次,然后停止。如果我们查看easings.net,我们可以看到一系列可能适合我们不同场景需求的缓动曲线(或者可以调整为适合这些需求)。不幸的是,不可能使用cubic-bezier()
重新创建所有这些曲线。
使用关键帧应用自定义缓动
实现自定义缓动的一种方法是使用关键帧动画。我们可以为元素的确切位置定义许多关键帧,并整体应用线性缓动(因为我们的缓动实际上是由关键帧决定的)。
@keyframes easeOutElastic {
0% { transform: translateX(0%); }
16% { transform: translateX(-132.27%); }
28% { transform: translateX(-86.88%); }
44% { transform: translateX(-104.63%); }
59% { transform: translateX(-98.36%); }
73% { transform: translateX(-100.58%); }
88% { transform: translateX(-99.8%); }
100% { transform: translateX(-100%); }
}
注意:此关键帧示例取自easings.net的“easeOutElastic”示例。
这感觉有点笨拙,而且不容易调整以满足我们的需求。还有一个问题:我们的动画只能朝一个方向播放。如果我们想从右到左应用相同的动画,则需要创建另一组关键帧。
介绍线性时序函数
与其使用关键帧将缓动构建到动画中,不如使用新的 CSSlinear()
函数来创建完全自定义的缓动。
linear()
函数(不要与上面介绍的linear
关键字混淆)需要一个以逗号分隔的停止列表,其值范围从0
到1
。这些停止点沿时间线均匀分布。linear(0, 1)
的值等效于linear
关键字,其中动画在整个持续时间内的进度率没有变化。
传入三个值为0
、0.75
和1
的停止点意味着在时间段的 50% 处,动画将完成 75% 的进度。
.box {
animation-timing-function: linear(0, 0.75, 1);
}
将此缓动应用于translate
动画的结果是,元素在动画持续时间的前半部分看起来移动得比后半部分快。
或者,让我们尝试向缓动函数传递一个负值。
.box {
animation-timing-function: linear(0, -0.1, 0.75, 1);
}
我们看到动画元素在被推向终点之前会稍微反向移动一点。从0
到-0.1
所需的时间与从-0.1
到0.75
所需的时间相同。
我们还添加了一个额外的停止点,这意味着与前面的示例相比,到达每个停止点所需的时间减少了:在持续时间为 1 秒的动画中,每个停止点将花费 1/3 秒,而不是第一个示例中的 0.5 秒。
起始和结束停止点
我们的停止点列表不必从 0 到 1。我们可以从时间线上的稍后位置开始动画,并且动画将从该位置以相同的持续时间播放。在这里您可以看到第二个和第三个方块花费相同的时间来走第一个方块一半的距离,从时间线上的不同位置开始和结束。
.box {
animation: slide 3000ms linear(0, 1);
}
.box:nth-child(2) {
animation-timing-function: linear(0.5, 1);
}
.box:nth-child(3) {
animation-timing-function: linear(0, 0.5);
}
停止点长度
我们可以通过额外传入停止点长度来控制动画持续时间内的停止点位置。如果我们希望元素到达第二个停止点值的时间不是 33.33%(当有四个停止点时),而是持续时间的 20%,我们可以在linear()
函数中指定该值。
.box {
animation-timing-function: linear(0, -0.1 20%, 0.75, 1);
}
我们还可以给停止点一个可选的结束值。
.box {
animation-timing-function: linear(0, -0.1 20% 40%, 0.75, 1);
}
我们的动画将在持续时间的 20% 处到达第二个停止点值,暂停直到 40%,然后继续进行到结束。
缓动曲线现在如下所示
您可能会注意到,一旦我们添加了停止点长度,其余的停止点就会在剩余的持续时间内均匀分布。对于linear(0, -0.1 20%, 0.75, 1)
的值,停止点0.75
将不再出现在持续时间的 2/3 处,而是出现在 60% 处;这是因为持续时间的最后 80% 在最后三个停止点之间均匀分布。
使用线性缓动函数创建平滑曲线
我们的动画缓动仍然看起来,嗯,相当线性。还没有人可以说这是一个“逼真”的动画!在第一个示例中,元素快速移动到0.75
位置,然后突然切换到较慢的速度。如果我们想要创建更平滑的减速,我们需要添加更多停止点。
animation-timing-function: linear(0, -0.1 20%, 0.4, 0.63, 0.75, 0.84, 0.92, 0.97, 1);
现在元素的减速并不完全平滑,但对于快速动画来说,它可能足够平滑。如果我们的动画持续时间更长,则值的变化可能会更明显。
通常,我们使用的停止点越多,动画就越平滑,因为停止点之间的变化将难以察觉。
使用 JavaScript 重新创建流行的缓动效果
当然,手动创建所有这些停止点可能会有点麻烦!如果我们知道创建缓动曲线的函数,就可以使用 JavaScript 创建一个具有大量点的平滑曲线。
此示例中的 easeOutBounce
函数改编自 easings.net。我们可以将这些缓动效果设置为自定义属性,以便在代码中使用。
const easeOutBounce = (x) => {
const n1 = 7.5625;
const d1 = 2.75;
if (x < 1 / d1) {
return n1 * x * x;
} else if (x < 2 / d1) {
return n1 * (x -= 1.5 / d1) * x + 0.75;
} else if (x < 2.5 / d1) {
return n1 * (x -= 2.25 / d1) * x + 0.9375;
} else {
return n1 * (x -= 2.625 / d1) * x + 0.984375;
}
};
const createEase = (fn, points = 50) => {
const result = [...new Array(points)]
.map((d, i) => {
const x = i * (1 / points);
return fn(x);
})
.join(",");
return `linear(${result})`;
};
document.body.style.setProperty("--easeOutBounce", createEase(easeOutBounce));
.bounce {
animation-timing-function: var(--easeOutBounce);
}
将线性缓动函数与 SVG 路径一起使用
如果我们可以向 linear()
函数提供 SVG 路径值,那不是很好吗?虽然我们无法直接做到这一点,但有一个工具可以提供帮助。 Linear() 生成器 将 SVG 路径转换为为我们的 linear()
函数添加停止点,并允许我们预览结果。非常有用,对吧!
相同的工具还允许我们可视化 JavaScript 函数并将其输出为 CSS,因此我们可以从类似于上面函数的函数创建停止点,而无需发送任何客户端 JavaScript。该工具的预设中已经提供了“弹跳”函数,从而产生以下 CSS
:root {
--bounce-easing: linear(0, 0.004, 0.016, 0.035, 0.063, 0.098, 0.141 13.6%, 0.25, 0.391, 0.563, 0.765, 1, 0.891 40.9%, 0.848, 0.813, 0.785, 0.766, 0.754, 0.75, 0.754, 0.766, 0.785, 0.813, 0.848, 0.891 68.2%, 1 72.7%, 0.973, 0.953, 0.941, 0.938, 0.941, 0.953, 0.973, 1, 0.988, 0.984, 0.988, 1);
}
浏览器支持
linear()
函数目前在 Safari 中不受支持。但是,如果我们使用 animation-timing-function
作为独立属性,而不是使用简写,则回退到默认的 ease
关键字(或其他一些受良好支持的动画)非常简单。不支持 linear()
函数的浏览器将回退到声明的值,因为浏览器会忽略它们无法识别的任何属性/值对。
/* Using the shorthand, the animation will not be applied in browsers that do not support `linear()` */
.box {
animation: slide 3000ms linear(0, 0.75, 1);
}
/* This way, non-supporting browsers will fall back to `ease-out` */
.box {
animation: slide 3000ms ease-out;
animation-timing-function: linear(0, 0.75, 1);
}
或者,我们可以使用特性查询检测对 linear()
的支持,并提供替代方案。
/* Fallback animation. You could define an alternative animation with keyframes if you choose. */
.box {
animation: alternativeSlide 3000ms;
}
/* Browsers that support `linear()` will get these styles */
@supports (animation-timing-function: linear(0, 1)) {
.box {
animation: slide 3000ms linear(0, 0.75, 1);
}
}