经典卡通如何启发现代CSS动画:从Hanna-Barbera到代码实践

本文探讨了经典卡通动画(如Hanna-Barbera作品)的有限帧技术和背景循环如何影响现代CSS动画设计,通过具体代码示例展示如何用CSS实现滚动背景、元素复用和帧动画,提升网页的交互体验与叙事性。

Smashing Animations Part 1: How Classic Cartoons Inspire Modern CSS

Andy Clarke
May 7, 2025 · 14 min read · CSS, Animation, Design

你是否曾想过早期卡通动画的局限性如何与今天的网页设计相关?从循环背景到最小帧变化,这些复古动画技术与现代CSS有着惊人的相似之处。在这篇文章中,先驱作者和网页设计师Andy Clarke展示了如何将这些原则应用于艾美奖获奖作曲家Mike Worth的新网站,使用CSS制作引人入胜的趣味动画,将他的世界带入生活。

浏览器制造商很快为CSS添加了运动能力。首先是简单的:hover伪类,随后是两个状态之间的过渡。然后是跨一组@keyframes改变状态的能力,以及最近将关键帧与滚动位置链接的滚动驱动动画。

即使有了这些增加的功能,CSS动画仍然相对基础。它们让我想起了我小时候在电视上观看的Hanna-Barbera动画系列。

这些动画短片的预算低于真人或动画电影,也远低于William Hanna和Joseph Barbera在为MGM Cartoons制作Tom and Jerry短片时的预算。这意味着动画师需要开发技术来应对成本限制和当时的技术限制。

他们每秒使用更少的帧和更少的单元格。他们不是为每一帧使用不同的图像,而是将每一帧重复几次。他们通过缩放和叠加额外元素来尽可能频繁地重用单元格,以构建新场景。他们保持身体基本静止,并叠加眼睛、嘴巴和腿部来创造说话和行走的错觉。这些限制并没有降低这些卡通的质量,反而创造了一种魅力,这在最近更大预算和技术更先进的作品中往往缺乏。

Hanna-Barbera动画师开发的简单高效技术可以使用CSS实现。现代布局工具允许网页开发者分层元素。可缩放矢量图形(SVG)可以包含多个帧,开发者不需要借助JavaScript;他们可以使用CSS改变元素的不透明度、位置和可见性。但这样做的原因是什么?

动画将静态体验带入生活。它们可以通过引导人们的行动并在与设计交互时取悦或惊喜他们来改善可用性。经过仔细考虑,动画可以加强品牌并帮助讲述品牌故事。

设计:Andy Clarke, Stuff & Nonsense。Mike Worth的网站将于2025年6月推出,但你可以在CodePen上看到本文的示例。

介绍Mike Worth

我最近一直在为艾美奖获奖游戏作曲家Mike Worth创建一个新网站。他雇佣我创建一个大胆的复古风格设计,展示他的作品。我在整个网站中使用CSS动画,以在他观众浏览网站时取悦和惊喜他们。

Mike喜欢80年代和90年代的动画——尤其是迪士尼的Duck Tales。不出所料,我对卡通的品味可以追溯到更远的1960年代Hanna-Barbera节目,如Dastardly and Muttley in Their Flying Machines、Scooby-Doo、The Perils of Penelope Pitstop、Wacky Races,当然还有Yogi Bear。

因此,为了解释这个动画时代如何与CSS相关,我选择了1961年首次播出的The Yogi Bear Show的一集“Home Sweet Jellystone”。在这个故事中,Ranger Smith继承了一座豪宅,并且(剧透警告)离开了Jellystone。

剖析运动

在这一集中,Hanna-Barbera的技术在邮递员带着给Ranger Smith的电报到达时变得明显。摄像机横向平移背景艺术家Robert Gentle的风景画,以创造邮递员移动的错觉。

当场景持续时间超过Robert Gentle风景画的单次平移时,背景会循环,灌木和树木反复出现。

这可以使用单个元素和改变其背景图像位置的动画来重新创建:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@keyframes background-scroll {
  0% { background-position: 2750px 0; }
  100% { background-position: 0 0; }
}

div {
  overflow: hidden;
  width: 100vw;
  height: 540px;
  background-image: url("…");
  background-size: 2750px 540px;
  background-repeat: repeat-x;
  animation: background-scroll 5s linear infinite;
}

参见CodePen: Yogi Bear background image scroll [forked] by Andy Clarke

尽管执行得很漂亮,Robert Gentle的背景画通常非常简单。豪宅的内部背景快速掠过,以创造Ranger Smith冲过它的错觉,因此它需要很少的细节。

运动的经济性对于廉价高效地制作这些动画短片至关重要。邮递员的摩托车弹跳,只有他的头部位置和面部表情发生变化,这增加了一丝微妙的真实感。

同样,在Ranger Smith穿过他的豪宅的行走周期中,只有他的面部表情和腿部位置发生变化。他身体的其余部分保持静止。

在我为他网站设计的废弃场景中,可以看到我为Mike Worth创建的猩猩冒险吉祥物驾车穿越景观。

Mike Worth的网站将于2025年6月推出,但你可以在CodePen上看到本文的示例。

我直接借鉴了Hanna-Barbera的弹跳和滚动技术,通过使用两个关键帧动画:background-scrollbumpy-ride。无限滚动背景的工作方式与之前相同:

1
2
3
4
@keyframes background-scroll {
  0% { background-position: 960px 0; }
  100% { background-position: 0 0; }
}

我通过动画改变关键帧的translate值来创建他颠簸行驶的外观:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@keyframes bumpy-ride {
  0% { translate: 0 0; }
  10% { translate: 0 -5px; }
  20% { translate: 0 3px; }
  30% { translate: 0 -3px; }
  40% { translate: 0 5px; }
  50% { translate: 0 -10px; }
  60% { translate: 0 4px; }
  70% { translate: 0 -2px; }
  80% { translate: 0 7px; }
  90% { translate: 0 -4px; }
  100% { translate: 0 0; }
}

figure {
  /* ... */
  animation: background-scroll 5s linear infinite;
}

img {
  /* ... */
  animation: bumpy-ride 1.5s infinite ease-in-out;
}

参见CodePen: Mike Worth background image scroll [forked] by Andy Clarke

正如Michelle Barker在2021年在Smashing Magazine上写道:

“在处理网页运动时,重要的是要考虑并非每个人以相同的方式体验它。对某些人来说可能感觉流畅和光滑的东西,对其他人来说可能是烦人或分散注意力的——或者更糟,引起恶心甚至癫痫发作。”
— Michelle Barker

你可以通过在使用者选择浏览器中减少运动时使用prefers-reduced-motion媒体查询关闭动画来防止这种情况发生:

1
2
3
@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; }
}

重用元素

由于每集的预算和制作时间有限,William Hanna和Joseph Barbera创建了一个简化的流程来制作他们的动画。他们每集使用少至2,000个单独绘图和仅几个背景画,通常在多集中重用它们。

观看这一集,你会看到这些树木在整个“Home Sweet Jellystone”中反复出现。在Yogi和Boo-Boo身后的轨道上,在灌木丛中,以及在这个Boo-Boo特写中放大:

动画师还经常将这些前景元素分层到这些背景画上,以创建各种新场景:

左:The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. 右:作者编辑。

在我从Mike Worth网站删除的场景中,我将这些岩石引入前景,为动画增加深度:

Mike Worth的网站将于2025年6月推出,但你可以在CodePen上看到本文的示例。

如果我使用位图图像,这将只需要一个额外的图像:

1
2
3
4
<figure>
  <img id="bumpy-ride" src="..." alt="" />
  <img id="apes-rock" src="..." alt="" />
</figure>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
figure {
  position: relative; 

  #bumpy-ride { ... }

  #apes-rock {
    position: absolute;
    width: 960px;
    left: calc(50% - 480px);
    bottom: 0;
  }
}

参见CodePen: Mike Worth layered animation [forked] by Andy Clarke

如果我继续开发这个场景,我可能会为那些岩石添加一个较慢的滚动动画,以引入视差效果,获得更大的真实感。

循环帧创建运动

为了满足有限的预算和生产计划,Hanna Barbera的动画师仔细规划了他们的动画,并聪明地只动画特定元素。虽然头部和面部表情使角色说话,他们的腿部变化使他们行走,但大多数角色的身体保持相对静止。因此,在整个Ranger Smith行走和说话穿过他的小屋的场景中,只有他的脸和腿部被动画化:

同样,当护林员阅读他的电报时,只有他的眼睛和嘴巴移动:

如果你想知道为什么Ranger Smith和Yogi Bear都戴领结和领带,这是为了掩盖他们动画头部和面部与静态身体之间的线条:

SVG提供了惊人的性能,并且在动画元素时提供了极大的灵活性。能够将一个SVG嵌入另一个SVG中,并使用CSS操作组和其他元素,使其成为动画的理想选择。

“我复制了Hanna-Barbera如何使Ranger Smith和其他角色的嘴巴移动,首先包括一个包含护林员身体和头部的组,这些在整个过程中保持静止。然后,我添加了六个更多组,每个组包含他嘴巴移动的一帧:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<svg>
  <!-- static elements -->
  <g>...</g>

  <!-- animation frames -->
  <g class="frame-1">...</g>
  <g class="frame-2">...</g>
  <g class="frame-3">...</g>
  <g class="frame-4">...</g>
  <g class="frame-5">...</g>
  <g class="frame-6">...</g>
</svg>

我使用CSS自定义属性来定义角色嘴巴移动的速度和动画中的帧数:

1
2
3
4
:root {
  --animation-duration: 1s;
  --frame-count: 6;
}

然后,我应用了一个关键帧动画来显示和隐藏每一帧:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@keyframes ranger-talking {
  0% { visibility: visible; }
  16.67% { visibility: hidden; }
  100% { visibility: hidden; }
}

[class*="frame"] {
  visibility: hidden;
  animation: ranger-talking var(--animation-duration) infinite;
}

最后设置一个延迟,使每一帧在正确的时间可见:

1
2
3
4
5
6
7
8
9
.frame-1 {
  animation-delay: calc(var(--animation-duration) * 0 / var(--frame-count));
}

/* ... */

.frame-6 {
  animation-delay: calc(var(--animation-duration) * 5 / var(--frame-count));
}

参见CodePen: Ranger Smith talking [forked] by Andy Clarke

与Mike Worth合作

当Mike Worth和我坐下来讨论合作时,我们都理解既没有预算也没有时间为他网站创建一个简短的动画卡通。我们也知道视频将是完全动画制作的正确格式,但我们热衷于探索CSS如何将原本是静态图像的内容带入生活。因此,这引出了为什么以及何时使用CSS动画的问题。

环境动画

微妙的环境动画有助于网站的氛围,并帮助讲故事,而不会分散其内容或功能。

对于Mike Worth的关于页面插图,我将光束照射到石板上,为原本平坦的图像增加深度。在我的SVG中,我添加了一个光的路径,并将其不透明度降低到.25

1
2
3
4
<svg>
  <!-- ... -->
  <path class="light-shaft" fill="#F1DCA9" opacity=".25" d=""/>
</svg>

然后,我定义了一个SVG滤镜来模糊我的光束边缘,并将其链接到我的路径:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<defs>
  <filter id="light-shaft" width="100%" height="100%" x="0" y="0" filterUnits="objectBoundingBox">
  <feGaussianBlur in="SourceGraphic" stdDeviation="20"/>
  </filter>
</defs>

<svg>
  <!-- ... -->
  <path class="light-shaft" filter="url(#light-shaft)"  />
</svg>

最后,我添加了一个微妙的环境动画,旋转光束并创造更自然的感觉:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@keyframes shaft-rotate {
  0% { rotate: 2deg; }
  50% { rotate: -2deg; }
  100% { rotate: 2deg; }
}

.light-shaft {
  animation: shaft-rotate 20s infinite;
  transform-origin: 100% 0;
}

参见CodePen: Mike Worth’s about page animation [forked] by Andy Clarke

你能为Mike的导航提供更多光线吗?

参见CodePen: Light up Mike Worth about page [forked] by Andy Clarke

交互动画

就像:hover伪类在有人与元素交互时提供有价值的视觉反馈一样,CSS动画可以在人与设计之间建立更深的联系。

我在Mike Worth的评论页面插图中包含了一个复活节彩蛋交互。大红按钮打开和关闭台灯,这让Mike的猩猩吉祥物非常懊恼,他正试图研究他的地图。为了实现这一点,我在SVG插图上应用了一个data-属性:

1
2
3
<svg  data-lights="lights-on">
  <!-- ... -->
</svg>

并添加了一个红色按钮供任何好奇的访问者按下:

1
2
3
4
5
<a href="javascript:void(0);" id="light-switch" title="Lights on/off">
  <path fill="#0a0908" d="..."/>
  <ellipse fill="#9c1621"  />
  <path fill="#fff" d="..."/>
</a>

当有人按下那个红色按钮时,灯熄灭,这是通过将SVG的data-属性值从lights-on更改为lights-off来实现的。

插图中的几个元素在台灯打开时会亮起。为了实现这一点,我为这些特定项目应用了一个类值:

1
<path class="lamp-glow" />

并使用data-attribute值在有人按下灯按钮时切换发光:

1
2
3
4
5
6
7
8
9
[data-lights="lights-on"] .lamp-glow {
  opacity: 1;
  transition: opacity .25s linear;
}

[data-lights="lights-off"] .lamp-glow {
  opacity: .25;
  transition: opacity .25s linear;
}

当有人打开灯时,它似乎在随机间隔闪烁。我首先为闪烁元素应用了一个类值:

1
<path class="lamp-flicker" />

然后,在灯关闭时隐藏它们:

1
2
3
[data-lights="lights-off"] .lamp-flicker {
  visibility: hidden;
}

最后,我创建了一个关键帧动画,在看似随机的间隔闪烁灯光的透明度:

1
2
3
4
5
6
7
8
@keyframes lamp-flicker {
  0%, 19.9%, 22%, 62.9%, 64%, 64.9%, 70%, 100% { opacity: 1; }
  20%, 21.9%, 63%, 63.9%, 65%, 69.9% { opacity: .5; }
}

[data-lights="lights-on"] .lamp-flicker {
  animation: lamp-flicker 3s 3s linear infinite;
}

动画还可以诱使人们更深入地探索设计,所以我让桌子上的水晶头骨振动,以暗示有更多东西可以发现:

1
2
3
<a href="/easter-egg">
  <g id="crystal-skull">...</g>
</a>
1
2
3
@keyframes crystal-skull-vibrate {
  0% { translate: 0 0; }
  20% { translate
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计