注意:前方存在极度糟糕的幽默感。我们会讨论一些实际的东西,但示例几乎都涉及僵尸和愚蠢的笑话。我已经警告过你了。
我会在讨论我学到的经验时链接到各个 Pens,但如果你想了解整个项目,请查看 Undead Institute 上的 60 天动画。我开始这个项目是为了在 2020 年 8 月 1 日结束,与我写的一本关于 CSS 动画、幽默和僵尸的书的出版日期一致——因为,很明显,如果你不挥舞你的网络技能并阻止末日,僵尸会毁灭世界。没有什么比移动的 HTML 元素更能伤害到僵尸群了!
我在整个项目中为自己制定了一些规则。
- 我会手写所有 CSS。(我是一个受虐狂。)
- 用户会启动所有动画。(我讨厌遇到已经进行到一半的动画。)
- 我会尽可能少地使用 JavaScript,并且绝不用于动画。(我只使用了一次 JavaScript,那就是用它来启动最终动画的音频。我并不反对 JavaScript,只是它不是我在这里想做的。)
经验 1:八十天很长。
嗯,标题不是说“六十”天吗?是的,但我最初的目标是做八十天,当第一天临近时,我只有不到二十个动画准备好了,每个动画的制作平均需要三天,我吓坏了,就改成了六十天。这给了我从开始日期起二十天的时间,以及少做二十个作品。

经验 1A:六十天仍然很长。
在有限的时间、想法和更有限的艺术技能下,要完成这么多动画,这确实很困难。虽然我想过缩短到三十天,但我很高兴我没有这样做。六十天让我成长,并迫使我更深入地了解 CSS 动画——以及扩展,CSS 本身——是如何工作的。我对自己后期创作的许多作品感到最自豪,因为我的技能提高了,我必须更加创新,并更努力地思考如何让作品变得有趣。当你用完所有简单选项后,真正的工作和最佳成果就开始了。(是的,最后变成了六十二天,因为我从 6 月 1 日开始,并想在 8 月 1 日做一个最终的动画。从 6 月 3 日开始感觉很恶心,也不对。)
所以,真正的经验 1:挑战自我。
经验 2:交互式动画很难,而且更难制作响应式。
如果你想要一个物体在屏幕上飞过并与另一个物体连接,或者看起来是触发另一个物体的移动,你必须使用所有标准的、不可灵活的单位,或者所有灵活的单位。
三个变量决定了动画元素在任何动画期间的时间和位置:持续时间、速度和距离。动画的持续时间在动画属性中设置,并且不能根据屏幕尺寸进行更改。动画时间函数决定了速度;屏幕尺寸也不能改变这一点。因此,如果距离随屏幕尺寸变化,则除了特定屏幕宽度和高度之外,时间将不一致。
看看 坦克!。在宽屏和窄屏尺寸下运行动画。虽然我让时间很接近,但如果你比较这两个,你会发现坦克在最后僵尸掉落时,相对于僵尸的位置不同。

为了避免这些时间问题,你可以使用固定单位和一个较大的数字,例如 2000 或 5000 像素或更多,这样动画将覆盖除了最大显示器之外的所有屏幕的宽度(或高度)。
经验 3:如果你想要一个响应式动画,请将所有内容放在(其中一个)视口单位中。
在单位比例上做一半(例如,将宽度和高度设置为像素,但将位置和移动设置为视口单位)会导致不可预测的结果。也不要同时使用 vw 和 vh,而要使用其中一个;哪个是占主导地位的方向。混合使用 vh 和 vw 单位会导致你的动画变得“古怪”,我认为这是技术术语。
以 超级僵尸化 为例。它混合了像素、vw 和 vh 单位。前提是超级僵尸向上飞,而“摄像机”跟随。超级僵尸撞到一个壁架上,并随着摄像机继续移动而落下,但如果你的屏幕足够高,你就不会理解这一点。

这也意味着,如果你需要一个物体从顶部进入——就像我在 这里只有我们人类 中做的那样——你必须将 vw 高度设置得足够高,以确保忍者僵尸在大多数纵横比下不可见。
经验 3A:在 SVG 元素内部使用像素单位进行移动。
所有这些说的是,在 SVG 元素内部变换元素不应该使用视口单位。SVG 标签有它们自己的比例宇宙。SVG“像素”将在 SVG 元素内部保持与所有其他 SVG 元素子元素的比例,而视口单位则不会。因此,在 SVG 元素内部使用像素单位进行变换,但在其他地方使用视口单位。
经验 4:SVG 在运行时缩放得很糟糕。
对于动画,例如 糟糕…,我将僵尸的 SVG 图片放大到原来的五倍,但这使得边缘变得模糊。[对着“可缩放”矢量图形挥舞拳头。]
/* Original code resulting in fuzzy edges */
.zombie {
transform: scale(1);
width: 15vw;
}
.toggle-checkbox:checked ~ .zombie {
animation: 5s ease-in-out 0s reverseshrinkydink forwards;
}
@keyframes reverseshrinkydink {
0% {
transform: scale(1);
}
100% {
transform: scale(5);
}
}
我学会了将它们的尺寸设置为在动画结束时生效的最终尺寸,然后使用缩放变换将它们缩小到动画开始时的尺寸。
/* Revised code */
.zombie {
transform: scale(0.2);
width: 75vw;
}
.toggle-checkbox:checked ~ .zombie {
animation: 5s ease-in-out 0s reverseshrinkydink forwards;
}
@keyframes reverseshrinkydink {
0% {
transform: scale(0.2);
}
100% {
transform: scale(1);
}
}
简而言之,修改后的代码将从缩小版本的图像移动到全宽度和高度。浏览器始终以 1 的比例渲染,在 1 的比例下,边缘清晰锐利。因此,我将比例从 1 到 5 更改为从 0.2 到 1。

经验 5:坐标轴不是一个普遍的真理。
元素的坐标轴与其本身保持同步,而不是页面。在translateX
之前进行 90 度旋转会将translateX
的方向从水平变为垂直。在 这里只有我们人类… 2中,我使用 180 度旋转来翻转僵尸。但是正 Y 值将忍者移动到顶部,负 Y 值将忍者移动到底部(与正常情况相反)。注意旋转可能会如何影响后续的变换。

经验 6. 将复杂的动画分解成同心元素,以方便调整。
在创建多个方向移动的复杂动画时,添加包装 div,或者更确切地说,父元素,并单独为每个元素设置动画,将减少冲突的变换,并防止你变成一团糟。
例如,在 Space Cadet中,我使用了三种不同的变换。第一个是僵尸宇航员的上下运动。第二个是横跨屏幕的移动。第三个是旋转。与其尝试在一个变换中完成所有操作,我添加了两个包裹元素,并在每个元素上进行一个动画(我也保住了我的头发……至少一部分)。这有助于避免上一课中讨论的轴问题,因为我在最内层的元素上执行了旋转,使它的父级和祖父母级轴保持原位。

第七课:SVG 和 CSS 变换相同。
某些路径、组和其他 SVG 元素可能已经定义了变换。它可能来自优化算法,或者可能是插图软件生成代码的方式。如果 SVG 中的路径、组或任何元素已经具有 SVG 变换,则移除该变换将重置元素,通常与图形的其余部分相比,将元素重置到奇怪的位置或大小。
由于 SVG 和 CSS 变换相同,因此你所做的任何 CSS 变换都会替换 SVG 变换,这意味着你的 CSS 变换将从那个奇怪的位置或大小开始,而不是在 SVG 中设置的位置或大小。
你可以将 SVG 元素中的变换复制到你的 CSS 中,并将其设置为 CSS 中的起始位置(当然要先将其更新为 CSS 语法)。然后,你可以在 CSS 动画中修改它。
例如,在 Uhhh, Yeah… 中,我对Office Space的致敬,不死族 Lumbergh 的右上臂(#arm2 元素)在原始 SVG 代码中有一个变换。
<path id="arm2" fill="#91c1a3" fill-rule="nonzero" d="M0 171h9v9H0z" transform="translate(0 -343) scale(4 3.55)"/>

将该变换移动到 CSS 中,像这样
<path id="arm2" fill="#91c1a3" fill-rule="nonzero" d="M0 171h9v9H0z"/>
#arm2 {
transform: translate(0, -343px) scale(4, 3.55);
}
… 然后我可以创建一个动画,不会意外地重置位置和比例
.toggle-checkbox:checked ~ .z #arm2 {
animation: 6s ease-in-out 0.15s arm2move forwards;
}
@keyframes arm2move {
0%, 100% {
transform: translate(0, -343px) scale(4, 3.55);
}
40%, 60% {
transform: translate(0, -403px) scale(4, 3.55);
}
50% {
transform: translate(0, -408px) scale(4, 3.55);
}
}
当生成 SVG 代码的工具尝试将变换“简化”为矩阵时,此过程会更难。虽然你可以通过将矩阵变换复制到 CSS 中来重新创建它,但这是一项艰巨的任务。如果你能够取一个矩阵变换并操作它以按你想要的方式进行缩放、旋转或平移,那么你比我更优秀——这可能总是正确的。
或者,你可以使用平移、旋转和缩放来重新创建矩阵变换,但如果路径很复杂,你及时重新创建它的可能性很小,而且你不会发现自己身处困境。
最后一个也是最简单的选择是将元素包裹在一个组(<g>)标签中。为其添加一个类或 ID 以便于 CSS 访问,并变换组本身,从而像上一课中讨论的那样分离变换。
transform-origin
保持理智
第八课:在变换 SVG 的一部分时,通过使用 CSS transform-origin
属性移动变换发生的点。如果你尝试旋转一只手臂——就像我在 Clubbin’ It 中做的那样——你的动画如果从肩膀的中心旋转手臂,看起来会更自然,但该路径的自然变换原点位于左上角。使用 transform-origin
来修复此问题,以获得更流畅、更自然的感觉……你知道那种非常自然的像素艺术风格……

变换原点在缩放时也很有用,就像我在 Mustachioed Oops 中做的那样,或者在旋转嘴巴动作时,比如 Super Tasty 中的恐龙的下巴。如果你不改变原点,变换将使用 SVG 元素左上角的原点。
第九课:精灵动画可以响应式
我最终为这个项目做了很多精灵动画(即,使用多个增量帧并在它们之间快速切换,使角色看起来在移动)。我在一个宽文件中创建了图像,将它们作为背景图像添加到一个与单个帧大小相同的元素中,使用 background-size
将背景图像设置为图像的宽度,并隐藏了溢出。然后,我使用 background-position
和动画时序函数 step()
来遍历图像;例如:Post-Apocalyptic Celebrations。
在项目开始之前,我一直使用不可变的图像。我会将它们缩小一点,以便至少有一点响应式空间,但我认为你无法使其完全灵活的宽度。但是,如果你使用 SVG 作为背景图像,你就可以使用视窗单位来随着屏幕大小的变化来缩放元素。唯一的问题是背景位置。但是,如果你对它使用视窗单位,它将保持同步。在 Finally, Alone with my Sandwich… 中查看它
第九课 A:在创建响应式精灵动画时,使用视窗单位来设置图像的背景大小
正如我在整个项目中所学到的那样,使用单一类型的单位几乎总是最好的方法。最初,我使用百分比来设置精灵的背景大小。数学很简单(100% * (步骤数量 + 1)
),并且在大多数情况下都很好用。但是,在较长的动画中,精确的帧跟踪可能会出错,并且可能会显示错误的精灵帧的一部分。随着精灵中添加的帧数量增加,问题也会加剧。
我不确定这到底是什么原因,但我认为这是由于在精灵表长度上累积的舍入误差造成的(偏移量随着帧数的增加而增加)。
对于我的最终动画,It Ain’t Over Till the Zombie Sings,我让一只恐龙张开嘴,露出一个唱着歌的僵尸维京人(同时背景中有激光发射,当然还有跳舞、手风琴演奏和从大炮中发射的僵尸)。是的,我知道怎么开派对……一场极客派对。
恐龙和维京人是该项目中最长的精灵动画之一。但是,当我使用百分比来设置背景大小时,跟踪在 Safari 中的某些尺寸下会出错。在动画结束时,来自不同帧的恐龙鼻子的一部分会出现在右边,而左边也会缺失类似的部分。

这非常令人沮丧,因为在 Chrome 中它似乎运行良好,我认为我在 Safari 中修复了它,结果查看稍微不同的屏幕尺寸时,又发现帧不匹配了。但是,如果我使用一致的单位——即,background-size
、帧宽度和 background-position
使用 vw——一切都运行良好。再次强调,这归结于使用一致的单位!
第十课:邀请人们参与项目。

虽然我在这个过程中学到了很多东西,但我大部分时间都在撞墙(通常是直到墙破了或者我的头破了……我分不清)。虽然这是一种方法,但即使你头很硬,最终也会头疼。邀请其他人参与你的项目,无论是为了获得建议、指出你忽略的明显盲点、提供反馈、帮助完成项目,还是仅仅是为了鼓励你在范围愚蠢而任意地大的时候继续下去。
所以,让我将这节课付诸实践。你的想法是什么?你将如何用 CSS 动画来阻止僵尸群?你将承担哪些愚蠢而任意地大的项目来挑战自己?
我真的很喜欢最后一个关于不要独自开始一个无限范围项目的建议……合作和鼓励是成功的关键!
感谢分享