想象一下:你加入一个新项目,深入研究代码库,在最初的几个小时内,你发现了一些令人沮丧的熟悉的东西。您会发现相同基本动画的多个 @keyframes 定义分散在样式表中。三种不同的淡入效果,两种或三种幻灯片变化,一些缩放动画,以及至少两种不同的旋转动画,因为,好吧,为什么不呢? @关键帧脉冲{ 从{ 规模:1; } 到{ 规模:1.1; } }
@keyframes 更大的脉冲 { 0%, 20%, 100% { 规模:1; } 10%, 40% { 规模:1.2; } }
如果这种情况听起来很熟悉,那么您并不孤单。根据我在各个项目中的经验,我能提供的最一致的快速胜利之一是整合和标准化关键帧。它已经成为一种非常可靠的模式,我现在期待将这种清理作为我在任何新代码库上的首要任务之一。 混乱背后的逻辑 仔细想想,这种冗余是完全有道理的。我们在日常工作中都使用相同的基本动画:淡入淡出、幻灯片、缩放、旋转和其他常见效果。这些动画非常简单,并且很容易快速创建 @keyframes 定义来完成工作。 如果没有集中式动画系统,开发人员自然会从头开始编写这些关键帧,而不会意识到代码库中的其他地方已经存在类似的动画。在基于组件的架构(现在我们大多数人都这样做)中工作时,这种情况尤其常见,因为团队经常在应用程序的不同部分并行工作。 结果呢?动画混乱。 小问题 关键帧重复最明显的问题是浪费开发时间和不必要的代码膨胀。多个关键帧定义意味着当需求发生变化时需要更新多个位置。需要调整淡入淡出动画的时间吗?您需要寻找代码库中的每个实例。想要标准化缓动函数吗?祝你好运找到所有的变化。维护点的增加使得即使是简单的动画更新也成为一项耗时的任务。 更大的问题 这种关键帧重复造成了一个潜伏在表面之下的更加阴险的问题:全局范围陷阱。即使使用基于组件的架构,CSS 关键帧也始终在全局范围内定义。这意味着所有关键帧都适用于所有组件。总是。是的,您的动画不一定使用您在组件中定义的关键帧。它使用与加载到全局范围中的名称完全相同的最后一个关键帧。 只要所有关键帧都相同,这似乎是一个小问题。但是,当您想要为特定用例自定义动画时,您就会遇到麻烦,或者更糟糕的是,您将成为导致这些问题的人。 要么你的动画无法工作,因为另一个组件在你的组件之后加载,覆盖了你的关键帧,要么你的组件最后加载并意外地更改了使用该关键帧名称的每个其他组件的动画行为,而你甚至可能没有意识到这一点。 这是一个演示该问题的简单示例: . 组件一 { /* 组件样式 */ 动画:脉冲 1s 缓入缓出无限交替; }
/* 这个@keyframes 定义不起作用 */ @关键帧脉冲{ 从{ 规模:1; } 到{ 规模:1.1; } }
/* 后面的代码... */
. 组件二 { /* 组件样式 */ 动画:脉冲 1s 缓入缓出无限; }
/* 该关键帧将应用于两个组件 */ @关键帧脉冲{ 0%, 20%, 100% { 规模:1; } 10%, 40% { 规模:1.2; } }
两个组件使用相同的动画名称,但第二个 @keyframes 定义会覆盖第一个。现在,组件一和组件二都将使用第二个关键帧,无论哪个组件定义了哪个关键帧。 请参阅 Amit Sheen 的笔关键帧令牌 - 演示 1 [forked]。 最糟糕的部分?这通常在本地开发中完美运行,但在生产中当构建过程更改样式表的加载顺序时会神秘地中断。您最终会得到表现不同的动画,具体取决于加载的组件以及加载的顺序。 解决方案:统一关键帧 这种混乱的答案出人意料地简单:预定义的动态关键帧存储在共享样式表中。我们不是让每个组件定义自己的动画,而是创建有据可查、易于使用的集中式关键帧使用、可维护并根据您的项目的特定需求进行定制。 将其视为关键帧标记。正如我们使用标记来表示颜色和间距,并且我们中的许多人已经使用标记来表示动画属性(例如持续时间和缓动函数),为什么不也使用标记来表示关键帧呢? 这种方法可以自然地与您正在使用的任何当前设计令牌工作流程集成,同时解决小问题(代码重复)和更大的问题(全局范围冲突)。 这个想法很简单:为我们所有常见的动画创建单一的事实来源。这个共享样式表包含精心制作的关键帧,涵盖了我们项目实际使用的动画模式。不再猜测我们的代码库中是否已经存在淡入淡出动画。不再意外覆盖其他组件的动画。 但关键是:这些不仅仅是静态的复制粘贴动画。它们被设计为动态的,并且可以通过 CSS 自定义属性进行自定义,使我们能够保持一致性,同时仍然可以灵活地使动画适应特定的用例,例如,如果您需要在一个地方稍微更大的“脉冲”动画。 构建第一个关键帧令牌 我们应该解决的第一个容易实现的目标之一是“淡入”动画。在我最近的一个项目中,我发现了十多个单独的淡入定义,是的,它们都只是将不透明度从 0 动画到 1。 因此,让我们创建一个新的样式表,将其命名为 kf-tokens.css,将其导入到我们的项目中,并在其中放置带有适当注释的关键帧。 /* 关键帧-tokens.css */
/* * Fade In - 淡入淡出动画 * 用法:动画:kf-fade-in 0.3s ease-out; */ @keyframes kf-淡入 { 从{ 不透明度:0; } 到{ 不透明度:1; } }
这个单一的 @keyframes 声明取代了我们代码库中所有分散的淡入动画。干净、简单且全球适用。现在我们已经定义了这个令牌,我们可以在整个项目的任何组件中使用它: .模态{ 动画:kf-淡入 0.3 秒缓出; }
.工具提示{ 动画:kf-fade-in 0.2s ease-in-out; }
.通知{ 动画:kf-淡入 0.5 秒缓出; }
请参阅 Amit Sheen 的笔关键帧令牌 - 演示 2 [forked]。 注意:我们在所有 @keyframes 名称中使用 kf- 前缀。此前缀用作命名空间,可防止与项目中现有动画发生命名冲突,并立即清楚地表明这些关键帧来自我们的关键帧标记文件。 制作动态幻灯片 kf 淡入关键帧效果很好,因为它很简单,而且几乎没有空间搞乱事情。然而,在其他动画中,我们需要更加动态,在这里我们可以利用 CSS 自定义属性的巨大威力。与分散的静态动画相比,这就是关键帧标记真正发挥作用的地方。 让我们看一个常见的场景:“滑入”动画。但从哪里滑进去呢?距右侧 100 像素?左边50%?应该从屏幕顶部进入吗?或者是从底部漂进去?可能性有很多,但我们可以构建一个适应所有场景的灵活令牌,而不是为每个方向和每个变化创建单独的关键帧: /* * Slide In - 定向滑动动画 * 使用--kf-slide-from控制方向 * 默认:从左侧滑入 (-100%) * 用法: * 动画:kf-slide-in 0.3s ease-out; * --kf-slide-from:-100px 0; // 从左滑动 * --kf-slide-from: 100px 0; // 从右滑动 * --kf-slide-from: 0 -50px; // 从顶部滑动 */
@keyframes kf-slide-in { 从{ 翻译:var(--kf-slide-from,-100% 0); } 到{ 翻译:0 0; } }
现在,我们只需更改 --kf-slide-from 自定义属性即可将这个单一 @keyframes 标记用于任何滑动方向: .侧边栏{ 动画:kf-slide-in 0.3s 缓出; /* 使用默认值:从左侧滑动 */ }
.通知{ 动画:kf-slide-in 0.4s 缓出; --kf-slide-from: 0 -50px; /* 从顶部滑动 */ }
.模态{ 动画: kf-淡入0.5秒, kf-滑入 0.5s 三次贝塞尔曲线(0.34, 1.56, 0.64, 1); --kf-slide-from: 50px 50px; /* 从右下角滑动 */ }
这种方法为我们提供了令人难以置信的灵活性,同时保持一致性。一个关键帧声明,无限可能。 请参阅 Amit Sheen 的笔关键帧令牌 - 演示 3 [forked]。 如果我们想让我们的动画更加灵活,也允许“滑出”效果,我们可以只需添加 --kf-slide-to 自定义属性,类似于我们将在下一节中看到的内容。 双向缩放关键帧 跨项目重复的另一个常见动画是“缩放”效果。无论是 Toast 消息的微妙放大、模态的戏剧性放大,还是标题的温和缩小效果,缩放动画无处不在。 让我们构建一组灵活的 kf-zoom 关键帧,而不是为每个比例值创建单独的关键帧:
/* * 缩放动画 * 使用 --kf-zoom-from 和 --kf-zoom-to 控制比例值 * 默认:从 80% 缩放到 100%(0.8 到 1) * 用法: * 动画:kf-zoom 0.2s 缓出; * --kf-zoom-from: 0.5; --kf-缩放至:1; // 从 50% 缩放到 100% * --kf-zoom-from: 1; --kf-缩放至:0; // 从 100% 缩放到 0% * --kf-zoom-from: 1; --kf-缩放至:1.1; // 从 100% 缩放到 110% */
@keyframes kf-zoom { 从{ 比例尺:var(--kf-zoom-from,0.8); } 到{ 比例:var(--kf-zoom-to, 1); } }
通过一个定义,我们可以实现我们需要的任何缩放变化: .吐司{ 动画: kf-滑入0.2s, kf-zoom 0.4s 缓出; --kf-幻灯片-来自:0 100%; /* 从顶部滑动 */ /* 使用默认缩放:从 80% 缩放到 100% */ }
.模态{ 动画:kf-zoom 0.3s 三次贝塞尔曲线(0.34, 1.56, 0.64, 1); --kf-缩放-来自:0; /* 从 0% 到 100% 的戏剧性缩放 */ }
.标题{ 动画: kf-淡入 2 秒, kf-zoom 2s 缓入; --kf-zoom-来自:1.2; --kf-缩放至:0.8; /* 温和缩小 */ }
默认值 0.8 (80%) 非常适合大多数 UI 元素,例如 Toast 消息和卡片,同时仍然可以轻松针对特殊情况进行自定义。 请参阅 Amit Sheen 的笔关键帧令牌 - 演示 4 [forked]。 您可能已经注意到最近示例中的一些有趣的事情:我们一直在组合动画。使用 @keyframes 令牌的主要优势之一是它们被设计为彼此无缝集成。这种流畅的构图是有意为之,而非偶然。 稍后我们将更详细地讨论动画合成,包括它们可能出现问题的地方,但大多数组合都是简单且易于实现的。 注意:在写这篇文章时,也许是因为写它,我发现自己重新思考了入口动画的整个想法。随着 CSS 的最新进展,我们还需要它们吗?幸运的是,亚当·阿盖尔探讨了同样的问题,并在他的博客中精彩地表达了这些问题。这与此处所写的内容并不矛盾,但它确实提供了一种值得考虑的方法,特别是如果您的项目严重依赖入口动画。 连续动画 虽然入口动画(如“淡入淡出”、“滑动”和“缩放”)发生一次然后停止,但连续动画会无限循环以吸引注意力或指示正在进行的活动。我遇到的两个最常见的连续动画是“旋转”(用于加载指示器)和“脉冲”(用于突出显示重要元素)。 这些动画在创建关键帧标记时提出了独特的挑战。与通常从一种状态进入另一种状态的入口动画不同,连续动画的行为模式需要高度可定制。 旋转医生 每个项目似乎都使用多个旋转动画。有些顺时针旋转,有些逆时针旋转。有些会进行一次 360 度旋转,有些会进行多次旋转以获得更快的效果。让我们构建一个可以处理所有场景的灵活旋转,而不是为每个变体创建单独的关键帧:
/* * Spin - 旋转动画 * 使用--kf-spin-from和--kf-spin-to控制旋转范围 * 使用--kf-spin-turns控制旋转量 * 默认:从 0 度旋转到 360 度(1 个完整旋转) * 用法: * 动画:kf-spin 1s线性无限; * --kf-自旋转弯:2; // 2 次完整旋转 * --kf-spin-from: 0deg; --kf-旋转:180°; // 半旋转 * --kf-spin-from: 0deg; --kf-旋转至:-360°; // 逆时针方向 */
@keyframes kf-spin { 从{ 旋转:var(--kf-spin-from, 0deg); } 到{ 旋转: calc(var(--kf-spin-from, 0deg) + var(--kf-spin-to, 360deg) * var(--kf-spin-turns, 1)); } }
现在我们可以创建任何我们喜欢的旋转变化:
.loading-spinner { 动画:kf-spin 1s线性无限; /* 使用默认值:从 0deg 旋转到 360deg */ }
.fast-loader { 动画:kf-spin 1.2s 缓入无限交替; --kf-旋转匝数:3; /* 每个周期每个方向 3 次完整旋转*/ }
.steped-reverse { 动画:kf-spin 1.5s steps(8) 无限; --kf-旋转至:-360°; /* 逆时针 */ }
.微妙的摆动{ 动画:kf-spin 2s 缓入出无限交替; --kf-spin-from:-16deg; --kf-旋转至:32°; /* 摆动 36 度:-18 度和 +18 度之间 */ }
请参阅 Amit Sheen 的笔关键帧令牌 - 演示 5 [forked]。 这种方法的优点在于,相同的关键帧可用于加载旋转器、旋转图标、摆动效果,甚至复杂的多回合动画。 脉冲悖论 脉冲动画比较棘手,因为它们可以“脉冲”不同的属性。有些脉冲比例,其他脉冲不透明度,还有一些脉冲颜色属性,如亮度或饱和度。我们可以创建适用于任何 CSS 属性的关键帧,而不是为每个属性创建单独的关键帧。 以下是带有比例和不透明度选项的脉冲关键帧示例:
/* * 脉冲 - 脉冲动画 * 使用 --kf-pulse-scale-from 和 --kf-pulse-scale-to 控制比例范围 * 使用 --kf-pulse-opacity-from 和 --kf-pulse-opacity-to 控制不透明度范围 * 默认值:无脉冲(所有值均为 1) * 用法: * 动画:kf-pulse 2s 缓入缓出无限交替; * --kf-脉冲比例-来自:0.95; --kf-脉冲比例-至:1.05; // 缩放脉冲 * --kf-脉冲不透明度-来自:0.7; --kf-脉冲不透明度-to:1; // 不透明度脉冲 */
@keyframes kf-脉冲 { 从{ 比例:var(--kf-pulse-scale-from, 1); 不透明度:var(--kf-pulse-opacity-from, 1); } 到{ 比例:var(--kf-pulse-scale-to, 1); 不透明度:var(--kf-pulse-opacity-to, 1); } }
这将创建一个灵活的脉冲,可以为多个属性设置动画: .号召性用语{ 动画:kf-pulse 0.6s无限交替; --kf-脉冲不透明度-来自:0.5; /* 不透明度脉冲 */ }
.通知点{ 动画:kf-pulse 0.6s 缓入缓出无限交替; --kf-脉冲比例-来自:0.9; --kf-脉冲比例-至:1.1; /* 缩放脉冲 */ }
.文本突出显示{ 动画:kf-pulse 1.5s 无限缓出; --kf-脉冲比例-来自:0.8; --kf-脉冲不透明度-来自:0.2; /* 缩放和不透明度脉冲 */ }
请参阅 Amit Sheen 的笔关键帧令牌 - 演示 6 [forked]。 这个单一的 kf-pulse 关键帧可以处理从微妙的注意力吸引到引人注目的亮点的一切,同时易于定制。 高级缓动 使用关键帧令牌的好处之一是可以轻松地扩展我们的动画库并提供大多数开发人员不会费心从头开始编写的效果,例如弹性或弹跳。 下面是一个简单的“弹跳”关键帧标记示例,它使用 --kf-bounce-from 自定义属性来控制跳跃高度。 /* * Bounce - 弹跳入口动画 * 使用--kf-bounce-from控制跳跃高度 * 默认:从 100vh 跳转(屏幕外) * 用法: * 动画:kf-bounce 3s 缓入; * --kf-bounce-from: 200px; // 从200px高度跳转 */
@keyframes kf-bounce { 0% { 翻译:0 calc(var(--kf-bounce-from, 100vh) * -1); }
34% { 翻译:0 calc(var(--kf-bounce-from, 100vh) * -0.4); }
55% { 翻译:0 calc(var(--kf-bounce-from, 100vh) * -0.2); }
72% { 翻译:0 calc(var(--kf-bounce-from, 100vh) * -0.1); }
85% { 翻译:0 calc(var(--kf-bounce-from, 100vh) * -0.05); }
94% { 翻译:0 calc(var(--kf-bounce-from, 100vh) * -0.025); }
99% { 翻译:0 calc(var(--kf-bounce-from, 100vh) * -0.0125); }
22%、45%、64%、79%、90%、97%、100% { 翻译:0 0; 动画计时功能:缓出; } }
由于关键帧内的计算,像“弹性”这样的动画有点棘手。我们需要分别定义 --kf-elastic-from-X 和 --kf-elastic-from-Y (两者都是可选的),它们一起让我们从屏幕上的任何点创建一个弹性入口。
/* * Elastic In - 弹性入口动画 * 使用 --kf-elastic-from-X 和 --kf-elastic-from-Y 控制起始位置 * 默认:从顶部中心进入(0,-100vh) * 用法: * 动画:kf-elastic-in 2s 缓入缓出两者; * --kf-elastic-from-X:-50px; * --kf-elastic-from-Y: -200px; // 从(-50px,-200px)输入 */
@keyframes kf-elastic-in { 0% { 翻译: calc(var(--kf-elastic-from-X, -50vw) * 1) calc(var(--kf-elastic-from-Y, 0px) * 1); }
16% { 翻译: calc(var(--kf-elastic-from-X, -50vw) * -0.3227) calc(var(--kf-elastic-from-Y, 0px) * -0.3227); }
28% { 翻译: calc(var(--kf-elastic-from-X, -50vw) * 0.1312)计算(var(--kf-elastic-from-Y,0px)* 0.1312); }
44% { 翻译: calc(var(--kf-elastic-from-X, -50vw) * -0.0463) calc(var(--kf-elastic-from-Y, 0px) * -0.0463); }
59% { 翻译: calc(var(--kf-elastic-from-X, -50vw) * 0.0164) calc(var(--kf-elastic-from-Y, 0px) * 0.0164); }
73% { 翻译: calc(var(--kf-elastic-from-X, -50vw) * -0.0058) calc(var(--kf-elastic-from-Y, 0px) * -0.0058); }
88% { 翻译: calc(var(--kf-elastic-from-X, -50vw) * 0.0020) calc(var(--kf-elastic-from-Y, 0px) * 0.0020); }
100% { 翻译:0 0; } }
这种方法使我们可以轻松地在整个项目中重用和自定义高级关键帧,只需更改单个自定义属性即可。
.bounce-and-zoom { 动画: kf-bounce 3s 缓入, kf-zoom 3s 线性; --kf-缩放-来自:0; }
.bounce-and-slide { 动画合成:添加; /* 两个动画都使用翻译 */ 动画: kf-bounce 3s 缓入, kf-滑入 3 秒缓出; --kf-幻灯片-来自:-200px; }
.elastic-in { 动画:kf-elastic-in 2s 缓入缓出两者; }
请参阅 Amit Sheen 的笔关键帧令牌 - 演示 7 [forked]。 到目前为止,我们已经了解了如何以智能且高效的方式整合关键帧。当然,您可能想要调整一些内容以更好地满足您的项目需求,但我们已经介绍了几种常见动画和日常用例的示例。有了这些关键帧令牌,我们现在就拥有了强大的构建块,可以在整个项目中创建一致、可维护的动画。不再有重复的关键帧,不再有全局范围冲突。这是一种干净、便捷的方式来处理我们所有的动画需求。 但真正的问题是:我们如何将这些构建块组合在一起? 把它们放在一起 我们已经看到,组合基本关键帧标记很简单。我们不需要任何特殊的东西,只是定义第一个动画,定义第二个动画,根据需要设置变量,就是这样。 /* 淡入+滑入 */ .吐司{ 动画: kf-淡入0.4秒, kf-滑入 0.4s 三次贝塞尔曲线(0.34, 1.56, 0.64, 1); --kf-slide-from: 0 40px; }
/* 放大+淡入 */ .模态{ 动画: kf-淡入0.3秒, kf-zoom 0.3s 三次贝塞尔曲线(0.34, 1.56, 0.64, 1); --kf-zoom-from:0.7; --kf-缩放至:1; }
/* 滑入 + 脉冲 */ .通知{ 动画: kf-滑入0.5s, kf-pulse 1.2s 缓入缓出无限交替; --kf-slide-from:-100px 0; --kf-脉冲比例-来自:0.95; --kf-脉冲比例-至:1.05; }
这些组合效果很好,因为每个动画都针对不同的属性:不透明度、变换(平移/缩放)等。但有时会出现冲突,我们需要知道为什么以及如何处理它们。 当两个动画尝试对同一属性进行动画处理时(例如,都对比例进行动画处理或同时对不透明度进行动画处理),结果将不是您所期望的。默认情况下,只有一个动画实际应用于该属性,即动画列表中的最后一个。这是 CSS 如何处理同一属性上的多个动画的限制。 例如,这不会按预期工作,因为仅应用 kf 脉冲动画。 .坏组合{ 动画: kf-向前变焦0.5秒, kf-脉冲1.2s无限交替; --kf-zoom-from:0.5; --kf-缩放至:1.2; --kf-脉冲比例-来自:0.8; --kf-脉冲比例-至:1.1; }
动画添加 处理影响同一属性的多个动画的最简单、最直接的方法是使用animation-composition 属性。在上面的最后一个示例中,kf-pulse 动画取代了 kf-zoom 动画,因此我们不会看到初始缩放,也不会获得预期的 1.2 比例。 通过设置要添加的动画组合,我们告诉浏览器组合两个动画。这给了我们我们想要的结果。 . 组件二 { 动画合成:添加; }
请参阅 Amit Sheen 的笔关键帧令牌 - 演示 8 [forked]。 对于我们想要组合对同一属性的效果的大多数情况,此方法效果很好。当我们需要将动画与静态属性值结合起来时,它也很有用。 例如,如果我们有一个元素使用 translate 属性将其精确定位在我们想要的位置,然后我们想使用 kf-slide-in 关键帧对其进行动画处理,那么我们会在没有动画合成的情况下得到令人讨厌的可见跳跃。 请参阅 Amit Sheen 的笔关键帧标记 - 演示 9 [forked]。 通过添加动画合成设置,动画可以与现有的平滑结合变换,因此元素保持在原位并按预期进行动画处理。 动画交错 处理多个动画的另一种方法是“交错”它们——也就是说,在第一个动画完成后稍微开始第二个动画。这并不是一个适用于所有情况的解决方案,但是当我们有一个入口动画后跟一个连续动画时,它很有用。 /* 淡入 + 不透明度脉冲 */ .通知{ 动画: kf-淡入 2 秒缓出, kf-pulse 0.5s 2s 缓入缓出无限交替; --kf-脉冲不透明度-to:0.5; }
请参阅 Amit Sheen 的笔关键帧令牌 - 演示 10 [forked]。 订单事宜 我们使用的大部分动画都使用变换属性。在大多数情况下,这更加方便。它还具有性能优势,因为变换动画可以通过 GPU 加速。但如果我们使用变换,我们需要接受执行变换的顺序很重要。很多。 到目前为止,在我们的关键帧中,我们使用了单独的变换。根据规范,这些总是以固定的顺序应用:首先,元素进行平移,然后旋转,然后缩放。这是有道理的,也是我们大多数人所期望的。 但是,如果我们使用变换属性,则函数的编写顺序就是它们的应用顺序。在这种情况下,如果我们在 X 轴上移动某个物体 100 像素,然后将其旋转 45 度,这与先将其旋转 45 度然后移动 100 像素不同。 /* 粉色方块:先平移,再旋转 */ .example-one { 变换:translateX(100px) 旋转(45deg); }
/* 绿色方块:先旋转,再平移 */ .example-2 { 变换:旋转(45度)平移X(100像素); }
请参阅 Amit Sheen 的笔关键帧令牌 - 演示 11 [forked]。 但根据变换顺序,所有单独的变换(我们用于关键帧标记的所有内容)都发生在变换函数之前。这意味着您在变换属性中设置的任何内容都将在动画之后发生。但是,例如,如果您将平移与 kf-spin 关键帧一起设置,则平移将在动画之前发生。还困惑吗? 这会导致静态值可能导致同一动画产生不同结果的情况,如下例所示:
/* 两个旋转器的通用动画 */ .spinner { 动画:kf-spin 1s线性无限; }
/* 粉色旋转器:旋转前平移(单独变换)*/ .spinner-粉红色{ 翻译:100% 50%; }
/* 绿色旋转器:旋转然后平移(函数顺序)*/ .spinner-green { 变换:翻译(100%,50%); }
请参阅 Amit Sheen 的笔关键帧令牌 - 演示 12 [forked]。 您可以看到第一个旋转器(粉红色)在 kf-spin 旋转之前发生平移,因此它首先移动到其位置,然后旋转。第二个旋转器(绿色)获得一个在单独变换之后发生的 translate() 函数,因此元素首先旋转,然后相对于其当前角度移动,我们得到了宽轨道效果。 不,这不是一个错误。这只是我们在处理多个动画或多个变换时需要了解 CSS 并牢记的事情之一。如果需要,您还可以创建一组附加的 kf-spin-alt 关键帧,使用rotate() 函数旋转元素。 减少运动 当我们谈论替代关键帧时,我们不能忽视“无动画”选项。使用关键帧令牌的最大优点之一是可以嵌入可访问性,而且实际上很容易做到。通过在设计关键帧时考虑到可访问性,我们可以确保喜欢减少运动的用户获得更流畅、更少干扰的体验,而无需额外的工作或代码重复。 “简化运动”的确切含义可能会因一个动画和另一个动画以及不同项目而略有不同,但以下是需要记住的一些重要要点: 静音关键帧 虽然某些动画可以软化或减慢,但有些动画在请求减少运动时应该完全消失。脉冲动画就是一个很好的例子。为了确保这些动画不会在简化运动模式下运行,我们可以简单地将它们包装在适当的媒体查询中。
@media(更喜欢减少运动:无偏好){ @keyfrmaes kf-脉冲 { 从{ 比例:var(--kf-pulse-scale-from, 1); 不透明度:var(--kf-pulse-opacity-from, 1); } 到{ 比例:var(--kf-pulse-scale-to, 1); 不透明度:var(--kf-脉冲不透明度-to, 1); } } }
这可以确保将prefers-reduced-motion 设置为reduce 的用户不会看到动画,并且会获得符合其偏好的体验。 即时进入 有一些关键帧我们不能简单地删除,例如入口动画。价值必须改变,必须有活力;否则,该元素将不会具有正确的值。但在减速运动中,从初始值的这种转变应该是即时的。 为了实现这一点,我们将定义一组额外的关键帧,其中值立即跳转到最终状态。这些成为我们的默认关键帧。然后,我们将在媒体查询中添加常规关键帧,将偏好减少运动设置为无偏好,就像前面的示例一样。 /* 立即弹出以减少运动 */ @keyframes kf-zoom { 从,到{ 比例:var(--kf-zoom-to, 1); } }
@media(更喜欢减少运动:无偏好){ /* 原始缩放关键帧 */ @keyframes kf-zoom { 从{ 比例尺:var(--kf-zoom-from,0.8); } 到{ 比例:var(--kf-zoom-to, 1); } } }
这样,喜欢减少运动的用户将看到元素立即以其最终状态出现,而其他人则获得动画过渡。 软方法 在某些情况下,我们确实希望保持一些运动,但比原始动画更柔和、更平静。例如,我们可以用柔和的淡入代替反弹入口。
@keyframes kf-bounce { /* 软淡入以减少运动 */ }
@media(更喜欢减少运动:无偏好){ @keyframes kf-bounce { /* 原始弹跳关键帧 */ } }
现在,启用减少运动的用户仍然可以获得外观感,但没有弹跳或弹性动画的强烈运动。 构建块就位后,下一个问题是如何使它们成为实际工作流程的一部分。编写灵活的关键帧是一回事,但要使它们在大型项目中可靠,需要一些我必须艰难学习的策略。 实施策略和最佳实践 一旦我们拥有了可靠的关键帧标记库,真正的挑战是如何将它们带入日常工作中。
人们很想立即删除所有关键帧并宣布问题已解决,但在实践中我发现最好的结果来自逐步采用。从最常见的动画开始,例如淡入淡出或幻灯片。这些都是轻松的胜利,无需进行大量重写即可显示出立竿见影的价值。 命名是另一个值得关注的点。一致的前缀或命名空间可以清楚地表明哪些动画是令牌,哪些是本地一次性动画。它还可以防止意外碰撞,并帮助新团队成员一目了然地识别共享系统。 文档与代码本身一样重要。即使每个关键帧标记上方的简短注释也可以节省以后数小时的猜测时间。开发人员应该能够打开令牌文件,扫描他们需要的效果,并将使用模式直接复制到他们的组件中。 灵活性使得这种方法值得付出努力。通过公开合理的自定义属性,我们为团队提供了在不破坏系统的情况下调整动画的空间。同时,尽量不要过于复杂化。提供重要的旋钮,让其余的保持固执己见。 最后,记住可访问性。并非每个动画都需要减少运动替代方案,但很多动画都需要。尽早进行这些调整意味着我们以后不必再对其进行改造,并且它显示出我们的用户即使从未提及也会注意到的谨慎程度。
根据我的经验,将关键帧标记视为我们设计标记工作流程的一部分是它们坚持下去的原因。一旦它们就位,它们就不再感觉像特效,而是成为设计语言的一部分,是产品移动和响应方式的自然延伸。 总结 动画可能是构建界面中最有趣的部分之一,但如果没有结构,它们也可能成为最大的挫败感来源之一。通过将关键帧视为令牌,您可以将通常混乱且难以管理的内容转变为清晰、可预测的系统。 真正的价值不仅仅在于节省几行代码。相信当您使用淡入淡出、滑动、缩放或旋转时,您可以准确地知道它在整个项目中的表现。这是来自自定义属性的灵活性,而不是无休止的变化带来的混乱。它是内置于基础中的可访问性,而不是添加为事后的想法。 我已经看到这些想法在不同的团队和不同的代码库中发挥作用,而且模式总是相同的。 一旦标记就位,关键帧就不再是分散的技巧集合,而是成为设计语言的一部分。它们让产品感觉更有意、更一致、更有活力。 如果您从本文中学到一件事,那就是:动画值得我们对颜色、版式和间距给予同样的关注和结构。每次界面移动时,对关键帧令牌的少量投资都会得到回报。