纯 CSS 狼人 是我几周前完成的一个小项目。 它是一个使用 CSS 3D 变换和动画的实验。
受 FPS 演示 和另一个 Wolfenstein CodePen 的启发,我决定构建自己的版本。 它松散地基于最初的 Wolfenstein 3D 游戏的第 1 集 - 第 9 层。
编辑:此游戏故意需要一些快速反应才能避免游戏结束屏幕。
这是一个通关视频
简而言之,我的项目不过是一个精心编排的 CSS 长动画。 另外还有一些 复选框技巧 实例。
:checked ~ div { animation-name: spin; }
环境由 3D 网格面组成,动画主要是简单的 3D 平移和旋转。 没什么真正花哨的。
然而,有两个问题特别棘手,难以解决
- 每当玩家点击敌人时,播放“武器射击”动画。
- 当快速移动的 Boss 受到最后一击时,进入戏剧性的慢动作。
从技术层面来说,这意味着
- 当下一个复选框被选中时,重新播放动画。
- 当选中复选框时,减慢动画速度。
事实上,我的项目中这两个问题都没有得到真正的解决! 我最终要么使用变通方法,要么干脆放弃。
另一方面,经过一番挖掘,我最终找到了这两个问题的关键:改变正在运行的 CSS 动画的属性。 在本文中,我们将进一步探讨这个主题
- 很多交互式示例。
- 剖析:每个示例是如何工作的(或不工作的)?
- 幕后:浏览器如何处理动画状态?
让我 “扔砖头”。
问题 1:重新播放动画
第一个例子:“只是一个复选框”
我的第一直觉是“再加一个复选框”,但行不通
每个复选框都可以单独工作,但不能同时工作。 如果一个复选框已经被选中,另一个就失效了。
这是它如何工作(或“不工作”)的
<div>
的animation-name
默认情况下是none
。- 用户点击一个复选框,
animation-name
变成spin
,动画从头开始。 - 一段时间后,用户点击另一个复选框。 一个新的 CSS 规则生效,但
animation-name
仍然是spin
,这意味着没有添加或删除动画。 动画简单地继续播放,就像什么都没发生一样。
第二个例子:“克隆动画”
一种有效的方法是克隆动画
#spin1:checked ~ div { animation-name: spin1; }
#spin2:checked ~ div { animation-name: spin2; }
这是它如何工作的
animation-name
最初是none
。- 用户点击“旋转!”,
animation-name
变成spin1
。 动画spin1
从头开始,因为它刚刚被添加。 - 用户点击“再次旋转!”,
animation-name
变成spin2
。 动画spin2
从头开始,因为它刚刚被添加。
请注意,在步骤 #3 中,由于 CSS 规则的顺序,spin1
被删除。 如果“再次旋转!”首先被选中,它将不起作用。
第三个例子:“追加相同的动画”
另一种有效的方法是“追加相同的动画”
#spin1:checked ~ div { animation-name: spin; }
#spin2:checked ~ div { animation-name: spin, spin; }
这与前面的例子类似。 你实际上可以这样理解这个行为
#spin1:checked ~ div { animation-name: spin1; }
#spin2:checked ~ div { animation-name: spin2, spin1; }
请注意,当选中“再次旋转!”时,旧的正在运行的动画成为新列表中的第二个动画,这可能不直观。 一个直接的结果是:如果 animation-fill-mode
是 forwards
,这个技巧将不起作用。 这里有一个演示
如果你想知道为什么会出现这种情况,以下是一些线索
animation-fill-mode
默认情况下是none
,这意味着“如果动画没有播放,则没有任何效果”。animation-fill-mode: forwards;
意味着“动画播放完毕后,必须永远停留在最后一个关键帧上”。spin1
的决定始终覆盖spin2
的决定,因为spin1
出现在列表中靠后。- 假设用户点击“旋转!”,等待一个完整的旋转,然后点击“再次旋转!”。 此时。
spin1
已经完成,而spin2
刚刚开始。
讨论
经验法则:你不能“重新开始”现有的 CSS 动画。 相反,你想添加并播放一个新的动画。 这可能被 W3C 规范 确认
一旦动画开始,它将持续到结束或动画名称被删除为止。
现在比较最后两个例子,我认为在实践中,“克隆动画”通常效果更好,尤其是在可以使用 CSS 预处理器的情况下。
问题 2:慢动作
有人可能会认为,减慢动画速度只是设置更长的 animation-duration
的问题
div { animation-duration: 0.5s; }
#slowmo:checked ~ div { animation-duration: 1.5s; }
的确,这行得通
…… 还是不行?
稍微调整一下,应该更容易看到问题。
是的,动画速度减慢了。 而且,看起来不好。 只要你切换复选框,那只狗(几乎)总是会“跳”。 此外,那只狗似乎跳到随机位置,而不是初始位置。 为什么会这样?
如果我们引入两个“阴影元素”,会更容易理解
两个阴影元素都运行相同的动画,但 animation-duration
不同。 而且它们不受复选框的影响。
当您切换复选框时,元素只是立即在两个阴影元素的状态之间切换。
引用 W3C 规范
动画运行期间对动画属性值的更改将应用于动画开始时的这些值。
这遵循 无状态 设计,它允许浏览器轻松确定动画值。 实际计算在 这里 和 这里 进行描述。
另一种尝试
一个想法是暂停当前动画,然后添加一个更慢的动画,从那里接管
div {
animation-name: spin1;
animation-duration: 2s;
}
#slowmo:checked ~ div {
animation-name: spin1, spin2;
animation-duration: 2s, 5s;
animation-play-state: paused, running;
}
所以它有效
…… 还是不行?
当您点击“慢动作!”时,它确实会减慢速度。 但是,如果你等待一个完整的圆圈,你会看到一个“跳跃”。 事实上,它总是跳到点击“慢动作!”时所处的位置。
原因是我们没有定义 from
关键帧——而且我们不应该定义。 当用户点击“慢动作!”时,spin1
在某个位置暂停,而 spin2
从完全相同的位置开始。 我们无法事先预测那个位置…… 或者可以?
一个有效的解决方案
我们可以! 通过使用自定义属性,我们可以捕获第一个动画中的角度,然后将其传递给第二个动画
div {
transform: rotate(var(--angle1));
animation-name: spin1;
animation-duration: 2s;
}
#slowmo:checked ~ div {
transform: rotate(var(--angle2));
animation-name: spin1, spin2;
animation-duration: 2s, 5s;
animation-play-state: paused, running;
}
@keyframes spin1 {
to {
--angle1: 360deg;
}
}
@keyframes spin2 {
from {
--angle2: var(--angle1);
}
to {
--angle2: calc(var(--angle1) + 360deg);
}
}
注意:本例中使用了 @property
,它 并非所有浏览器都支持。
“完美”的解决方案
前一个解决方案有一个缺陷:“退出慢动作”效果不好。
这里有一个更好的解决方案
在这个版本中,慢动作可以无缝地进入或退出。也不使用任何实验性功能。那么它是一个完美的解决方案吗? 是也不是。
这个解决方案类似于“换挡”
- 档位:有两个
<div>
。一个是另一个的父元素。两者都有spin
动画,但animation-duration
不同。元素的最终状态是两个动画的累加。 - 换档:开始时,只有一个
<div>
的动画正在运行。另一个是暂停的。当复选框切换时,两个动画会交换它们的状态。
虽然我非常喜欢这个结果,但它有一个问题:它是spin
动画的一个巧妙的利用,对于其他类型的动画来说,它通常是行不通的。
一个实用的解决方案(使用 JS)
对于一般的动画,可以使用一些 JavaScript 来实现慢动作功能。
快速解释一下
- 使用自定义属性来跟踪动画进度。
- 当复选框切换时,“重新开始”动画。
- JS 代码计算出正确的
animation-delay
,以确保无缝过渡。如果你不熟悉animation-delay
的负值,我推荐阅读这篇文章。
你可以将这个解决方案看作是“重新开始动画”和“换挡”方法的混合。
这里重要的是正确跟踪动画进度。如果@property
不可用,可以采用变通方法。例如,这个版本使用z-index
来跟踪进度。
旁注:最初,我也尝试过创建一个纯 CSS 版本,但没有成功。虽然不能百分百确定,但我认为这是因为animation-delay
是不可动画的。
这是一个使用最少 JavaScript 的版本。只有“进入慢动作”有效。
如果你能够创建一个可工作的纯 CSS 版本,请告诉我!
任何动画的慢动作(使用 JS)
最后,我想分享一个适用于(几乎)任何动画的解决方案,即使是包含多个复杂@keyframes
的动画。
基本上,你需要添加一个动画进度跟踪器,然后仔细计算新动画的animation-delay
。但是,有时获取正确的值可能很棘手(但可行)。
例如
animation-timing-function
不是linear
。animation-direction
不是normal
。animation-name
中有多个值,它们具有不同的animation-duration
和animation-delay
。
这种方法在这里也进行了描述,用于Web Animations API。
致谢
我在遇到纯 CSS 项目后开始走上这条路。有些是精致的艺术品,有些是复杂的装置。我最喜欢的那些涉及 3D 对象,例如这个弹跳球和这个装箱立方体。
一开始,我完全不知道这些是如何制作的。后来,我从Ana Tudor 的这篇很棒的教程中学到了很多。
事实证明,使用 CSS 构建和动画化 3D 对象与使用Blender没有什么不同,只是味道略有不同。
结论
在这篇文章中,我们研究了当animate-*
属性改变时 CSS 动画的行为。特别是,我们研究了“重新播放动画”和“动画慢动作”的解决方案。
希望你发现这篇文章很有趣。请告诉我你的想法!
很有趣,感谢分享您的经验。您可能对
document.getAnimations()
和animation.playbackRate
感兴趣,它们可以解决问题 #2。谢谢!如果有 CSS 的对应物,将会更方便。
它可能在CSS Animations 2中定义。
不错!