使用 CSS3 变换和动画制作钢铁侠的弧反应堆

Avatar of Kunal Sarkar
Kunal Sarkar

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 200 美元的免费积分!

好了,钢铁侠粉丝们,启动你们的代码编辑器吧! 我们将使用 CSS 制作钢铁侠战衣中的弧反应堆。 以下是最终效果:

查看 CodePen 上 Kunal Sarkar (@supersarkar) 编写的 钢铁侠的弧反应堆

全屏包装器

我们将在深色的全屏背景上制作我们的弧反应堆。 这是我们制作全屏包装器元素的代码

body {
  margin: 0;
}

.fullpage-wrapper {
  height: 100vh;
  background: radial-gradient(#353c44, #222931);
}

为什么我们在 body 上声明没有 margin?<body> 元素在用户代理样式表中默认设置了一些 margin。 这会阻止 <body> 内部的元素触及屏幕边缘。 由于我们希望我们的包装器覆盖整个屏幕,从边缘到边缘,因此我们通过将其设置为 0 来删除 <body> 元素上的默认 margin。

我们为 .fullpage-wrapper 提供了视口的高度。 我们不必指定宽度,因为 div 默认是全宽的。 我们也可以使用另一种方法,将元素的宽度和高度都设置为 100%,但这会带来一些可能的缺点,因为屏幕上添加了更多元素。 使用视口单位可以确保我们的包装器始终占据屏幕的整个垂直空间,无论它是什么或布局中添加了哪些其他元素。

我们还在包装器上使用了径向渐变,使用 radial-gradient() CSS 函数。 函数内的参数是颜色开始和结束点。 因此,背景的中心将更接近 #353c44,而边缘将更接近 #222931。 它是微妙的,但很好看。

居中反应堆容器

在我们开始创建反应堆之前,让我们为它创建一个容器并将其居中

.reactor-container {
  width: 300px;
  height: 300px;
  margin: auto;
  border: 1px dashed #888;
}

这给了我们一个 300px x 300px 的带虚线边框的盒子。margin: auto; 声明确保水平间距相等。

但为什么它不能垂直居中呢?

根据 CSS2 规范,如果我们在左右两侧设置自动 margin,则整个可用空间将被平均分配到左右 margin。 然而,顶部和底部 margin 的情况并非如此。 对于顶部和底部 margin,CSS2 规范 指出

如果 margin-topmargin-bottomauto,则其使用值将为 0

但是,我们有一个好消息。 Flexbox 布局遵循新的对齐规则,以下是 Flexbox 规范 的说法

在通过 justify-content 和 align-self 对齐之前,任何正的可用空间都将分配给该维度中的自动 margin。

这意味着如果正在考虑的元素显示为弹性项目,则 margin: auto; 将在两个方向上都有效。 让我们将包装器设为弹性容器,并将它的子元素设为弹性项目

.fullpage-wrapper {
  width: 100%;
  height: 100vh;
  background: radial-gradient(#353c44, #222931);
  display: flex;
}

就是这样,好多了。

还有许多其他方法可以居中 HTML 中的元素。 CSS-Tricks 上有一篇关于 居中元素的详细指南,可以了解更多信息。

反应堆核心:CSS 中的同心圆

我们知道,HTML 中的新元素是从左到右(对于从左到右的语言)或从上到下创建的。 元素从不重叠,除非您提供一些负 margin。

那么,我们如何显示同心圆呢? 我们将使用绝对定位。

position 属性的默认值为 static。 静态和相对定位遵循从上到下和从左到右的流程。 但是,绝对定位的元素不被视为文档流的一部分,并且可以使用 top、right、bottom 和 left 属性将其定位在任何位置。

让我们从创建最小的圆圈开始

<div class="fullpage-wrapper">
  <div class="reactor-container">
    <!-- the smallest circle -->
    <div class="core-inner"></div>
  </div>
</div>
.core-inner {
  position: absolute;
  width: 70px;
  height: 70px;
  border-radius: 50%;
  border: 5px solid #1b4e5f;
  background-color: #fff;
}

我们需要将这个 div 居中。 您可能很想将我们对反应堆元素使用的相同 Flexbox 概念也应用于此圆圈以将其居中。 但是,以下是 CSS2 规范 关于在绝对定位元素上设置 margin: auto; 的说法

如果三个 (topheightbottom) 中没有一个是 auto:如果 margin-topmargin-bottom 都为 auto,则在额外约束条件下求解方程式,即这两个 margin 获得相等的值。

这意味着如果绝对定位的元素的 topheightbottom 的任何值都非 auto,则将顶部和底部 margin 设置为 auto 将垂直居中元素。

水平居中的情况相同:如果绝对定位的元素的 leftwidthright 的任何值都非 auto,则将左右 margin 设置为 auto 将水平居中元素。

这意味着我们不需要 Flexbox 布局来居中具有已知高度和宽度的绝对定位元素。 我们只需要确保我们为 toprightbottomleft 提供除 auto 之外的值即可。 因此,我们将使用 0

.core-inner {
  position: absolute;
  width: 70px;
  height: 70px;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  margin: auto;
  border-radius: 50%;
  border: 5px solid #1b4e5f;
  background-color: #fff;
}

我们不想为所有将要拥有的同心圆重复这五行代码,因此让我们为此创建一个单独的类。 我们也不想为所有要显示为圆圈的 div 定义 border-radius: 50%;,因此我们也将为此创建一个类

.circle {
  border-radius: 50%;
}

.abs-center {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  margin: auto;

}

.core-inner {
  width: 70px;
  height: 70px;
  border: 5px solid #1b4e5f;
  background-color: #fff;
}

另外,将这些新类添加到我们的 .core-inner 元素中

<div class="fullpage-wrapper">
  <div class="reactor-container">
    <!-- the smallest circle -->
    <div class="core-inner circle abs-center"></div>
  </div>
</div>

好的,我们的同心圆已居中。 让我们让它发光。

但是 CSS 没有任何“glow”属性。

不用担心,我们有 box-shadow 属性。 我们将为阴影赋予明亮的颜色,而不是深色,以使阴影看起来像发光。 很聪明,不是吗?😉

让我们这样做

.core-inner {
  width: 70px;
  height: 70px;
  border: 5px solid #1b4e5f;
  background-color: #fff;
  box-shadow: 0px 0px 7px 5px #52fefe;
}

让我们休息一下,首先了解 box-shadow 的语法,因为我们将经常使用它。 以下是这些 box-shadow 值按顺序的含义

  • x-offset:我们要在右侧 (x 轴) 推动阴影多少。 负值将阴影推到左侧。
  • y-offset:我们要向上或向下 (y 轴) 推动阴影多少。
  • blur-radius:我们希望阴影有多模糊。
  • spread-radius:我们希望阴影扩散多少。
  • color:阴影的颜色。

稍微玩一下这些值,以查看它们在实时中的效果。

在现实生活中,阴影不仅会落在物体外部。 阴影也会落在物体上。 想象一下狗挖的坑,它里面会有阴影,对吧?

我们可以使用 box-sizing 属性中的 inset 关键字在元素内部添加阴影。 要为元素同时添加外部和内部阴影,我们只需用逗号分隔它们即可。 让我们对反应堆的内芯进行内外发光处理

.core-inner {
  width: 70px;
  height: 70px;
  border: 5px solid #1B4e5f;
  background-color: #fff;
  box-shadow: 0px 0px 7px 5px #52fefe, 0px 0px 10px 10px #52fefe inset;
}

现在它开始看起来科幻了!

让我们在顶部再创建一个圆圈。 我们希望内圆显示在其他圆圈之上,因此我们将在代码中*在*内圆*上方*添加新的圆圈 div

<div class="fullpage-wrapper">
  <div class="reactor-container">
    <!-- the second circle -->
    <div class="core-outer circle abs-center"></div>
    <!-- the smallest circle -->
    <div class="core-inner circle abs-center"></div>
  </div>
</div>

代码中较下面的元素,在屏幕上显示在上面。 如果我们将 core-outer 放在代码中的 core-inner 下方,则 core-inner 将不可见,因为 core-outer 将覆盖它。

让我们为 outer-code 设置样式。 外部核心将比内部核心稍大一些,我们也将为 core-outer 提供外部和内部发光

.core-outer {
  width: 120px;
  height: 120px;
  border: 1px solid #52fefe;
  background-color: #fff;
  box-shadow: 0px 0px 2px 1px #52fefe, 0px 0px 10px 5px #52fefe inset;
}

我想让你做一件事:看看阴影(发光),并尝试确定哪个阴影属于哪个圆圈。 有四个阴影和两个圆圈(到目前为止)。

要完成核心的设计,我们需要再添加一个圆圈,它将包裹内部和外部圆圈

<div class="fullpage-wrapper">
  <div class="reactor-container">
    <!-- the third circle -->
    <div class="core-wrapper circle abs-center"></div>
    <!-- the second circle -->
    <div class="core-outer circle abs-center"></div>
    <!-- the smallest circle -->
    <div class="core-inner circle abs-center"></div>
  </div>
</div>

这个圆圈会稍微大一点,并且也会有相同的阴影,我们将使用深色背景作为 core-wrapper

.core-wrapper {
  width: 180px;
  height: 180px;
  background-color: #073c4b;
  box-shadow: 0px 0px 5px 4px #52fefe, 0px 0px 6px 2px #52fefe inset;
}

创建反应堆线圈并使用 CSS3 变换旋转

我们有了反应堆的核心,现在我们需要一个围绕核心的通道。 实际上,我们可以通过绘制一个比 core-wrapper 略大的圆圈来创建圆形通道的错觉。 让我们来做吧

<div class="fullpage-wrapper">
  <div class="reactor-container">
    <!-- the largest circle -->
    <div class="tunnel circle abs-center"></div>
    <!-- the third circle -->
    <div class="core-wrapper circle abs-center"></div>
    <!-- the second circle -->
    <div class="core-outer circle abs-center"></div>
    <!-- the smallest circle -->
    <div class="core-inner circle abs-center"></div>
  </div>
</div>

…稍微宽一点,并为隧道添加相同的发光效果

.tunnel {
  width: 220px;
  height: 220px;
  background-color: #fff;
  box-shadow: 0px 0px 5px 1px #52fefe, 0px 0px 5px 4px #52fefe inset;
}

我们的隧道准备好了。

请确保您不要只是复制粘贴代码。 看一看圆圈的发光,并确定哪个发光属于哪个圆圈,无论是外部发光还是内嵌发光。

现在,我们需要在这个隧道上放置八个线圈。线圈是简单的矩形,但主要的挑战在于我们需要让线圈沿着隧道的圆形路径运行;而不是直线。

一种方法是创建八个小矩形,将它们的中心移动到反应堆的中心,并以递增的角度(以45deg的倍数)旋转每个线圈。

让我们不要复杂化,一次做一个矩形线圈。

<div class="fullpage-wrapper">
  <div class="reactor-container">
    <div class="tunnel circle abs-center"></div>
    <div class="core-wrapper circle abs-center"></div>
    <div class="core-outer circle abs-center"></div>
    <div class="core-inner circle abs-center"></div>
    <div class="coil-1"></div>
  </div>
</div>
.coil-1 {
  position: absolute;
  width: 30px;
  height: 26px;
  background-color: #073c4b;
  box-shadow: 0px 0px 5px #52fefe inset;
}

现在,我们想将这个线圈放置在隧道的顶部中心。就像这样

我们的反应堆容器是300px x 300px,所以中心距离顶部和左侧150px。隧道宽220px,所以它的半径将为110px。这给了我们线圈的顶部偏移量:150px - 110px

我们可以将线圈的左侧保持在150px,但由于我们的线圈宽30px,它会将线圈的中间向右偏移15px,这就是为什么我们需要从150px中减去15px来获得线圈的左侧偏移量。

我们可以自己计算这些值并输入,或者我们可以使用CSS的calc()函数。让我们使用CSS的calc()函数来计算线圈的顶部和左侧属性。

.coil-1 {
  position: absolute;
  width: 30px;
  height: 20px;
  top: calc(50% - 110px);
  left: calc(50% - 15px);
  background-color: #073c4b;
  box-shadow: 0px 0px 5px #52fefe inset;
}

如您所见,calc()函数以数学表达式作为参数并求解它。

现在我们需要八个这样的线圈,但它们必须位于隧道上。为此,正如我们所讨论的,我们可以简单地将八个线圈放置在同一个位置,然后将其原点转换为反应堆的中心,并以45度的增量旋转每个线圈。

我们需要更新线圈的原点,因为默认情况下它设置为线圈的中心;我们希望它位于反应堆的中心。

我们将使用transform-origin属性来设置线圈的原点。

.coil-1 {
  position: absolute;
  width: 30px;
  height: 20px;
  top: calc(50% - 110px);
  left: calc(50% - 15px);
  transform-origin: 15px 110px;
  background-color: #073c4b;
  box-shadow: 0px 0px 5px #52fefe inset;
}

transform-origin中的第一个值15px是距元素左上角的x-offset(水平距离),第二个值110px是距元素左上角的y-offset(垂直距离)。

线圈的原点现在位于反应堆的中心,让我们使用CSS3 transform属性将其旋转45度,看看会发生什么。

.coil-1 {
  position: absolute;
  width: 30px;
  height: 20px;
  top: calc(50% - 110px);
  left: calc(50% - 15px);
  transform-origin: 15px 110px;
  transform: rotate(45deg);
  background-color: #073c4b;
  box-shadow: 0px 0px 5px #52fefe inset;
}

太棒了!这正是我们想要的。

在创建所有八个线圈之前,让我们创建一个线圈容器div,它将包含所有八个线圈。

<div class="fullpage-wrapper">
  <div class="reactor-container">
    <div class="tunnel circle abs-center"></div>
    <div class="core-wrapper circle abs-center"></div>
    <div class="core-outer circle abs-center"></div>
    <div class="core-inner circle abs-center"></div>
    <!-- the coil container -->
    <div class="coil-container">
      <div class="coil coil-1"></div>
    </div>
  </div>
</div>

您会注意到我们还在“coil-1”元素中添加了一个类“coil”。我们将所有线圈的通用样式保留在“coil”类中,而各个线圈元素类将只包含它们的旋转角度。

.coil-container {
  position: relative;
  width: 100%;
  height: 100%;
}

.coil {
  position: absolute;
  width: 30px;
  height: 20px;
  top: calc(50% - 110px);
  left: calc(50% - 15px);
  transform-origin: 15px 110px;
  background-color: #073c4b;
  box-shadow: 0px 0px 5px #52fefe inset;
}

.coil-1 {
  transform: rotate(45deg);
}

输出将保持不变。

现在,让我们在.coil-container内制作所有八个线圈。

<div class="fullpage-wrapper">
  <div class="reactor-container">
    <div class="tunnel circle abs-center"></div>
    <div class="core-wrapper circle abs-center"></div>
    <div class="core-outer circle abs-center"></div>
    <div class="core-inner circle abs-center"></div>
    <!-- the coil container -->
    <div class="coil-container">
      <div class="coil coil-1"></div>
      <div class="coil coil-2"></div>
      <div class="coil coil-3"></div>
      <div class="coil coil-4"></div>
      <div class="coil coil-5"></div>
      <div class="coil coil-6"></div>
      <div class="coil coil-7"></div>
      <div class="coil coil-8"></div>
    </div>
  </div>
</div>

……并为所有线圈提供不同的旋转(以45度的增量)。

.coil {
  position: absolute;
  width: 30px;
  height: 20px;
  top: calc(50% - 110px);
  left: calc(50% - 15px);
  transform-origin: 15px 110px;
  background-color: #073c4b;
  box-shadow: 0px 0px 5px #52fefe inset;
}

.coil-1 {
  transform: rotate(0deg);
}

.coil-2 {
  transform: rotate(45deg);
}

.coil-3 {
  transform: rotate(90deg);
}

.coil-4 {
  transform: rotate(135deg);
}

.coil-5 {
  transform: rotate(180deg);
}

.coil-6 {
  transform: rotate(225deg);
}

.coil-7 {
  transform: rotate(270deg);
}

.coil-8 {
  transform: rotate(315deg);
}

我们的反应堆几乎准备好了。

使用CSS3动画为线圈添加动画

在钢铁侠的电弧反应堆中,线圈不会移动,但在我们的反应堆中会移动。我们将为线圈添加动画使其沿着隧道旋转,并将为此使用CSS3动画——无需JavaScript。

要创建动画,您需要知道要动画化的对象的初始状态和最终状态。我们通过使用@keyframes规则在CSS中定义这些初始状态和最终状态。

@keyframes reactor-anim {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

我们希望元素位于0度,并对其进行动画处理,直到它达到360度。我们将此动画命名为“reactor-anim”。

我们要动画化的元素是.coil-contailer。请注意,我们尚未定义要动画化的对象,我们只定义了动画的初始状态、最终状态和名称。

我们需要将元素链接到动画才能生效。我们通过在.coil-container上使用animation-name属性来实现。

.coil-container {
  position: relative;
  width: 100%;
  height: 100%;
  animation-name: reactor-anim;
  animation-duration: 3s;
}

请注意,我们还使用animation-duration属性指定了动画的持续时间。这定义了从使用@keyframes规则定义的“from”状态到“to”状态需要花费多长时间。

查看Kunal Sarkar在CodePen上创作的Pen Arc-Reactor-Ease-In (@supersarkar)。

我们需要在这里更改两件事:我们希望动画无限循环,并且我们希望动画是线性的。您可以看到动画开始时缓慢,然后加快,然后在结束时再次缓慢——此行为由动画的时间函数定义。

让我们进行这些更改。

.coil-container {
  position: relative;
  width: 100%;
  height: 100%;
  animation-name: reactor-anim;
  animation-duration: 3s;
  animation-iteration-count: infinite;
  animation-timing-function: linear;
}

我们使用animation-iteration-count属性将动画设置为infinite,并使用animation-timing-function使动画linearanimation-timing-function的默认值为ease

查看Kunal Sarkar在CodePen上创作的Pen Arc-Reactor-Linear-Infinite (@supersarkar)。

我们可以将所有这些动画属性组合起来……

animation-name: reactor-anim;
animation-duration: 3s;
animation-iteration-count: infinite;
animation-timing-function: linear;

……像这样组合成一个简写属性。

animation: 3s infinite linear reactor-anim;

反应堆容器的最终润色

我们的反应堆已准备就绪,现在让我们对.reactor-container进行一些最后的更改。首先,我们需要在反应堆后面放置一个深色圆圈。

<div class="fullpage-wrapper">
  <div class="reactor-container">
    <!-- dark circle behind the reactor -->
    <div class="reactor-container-inner circle abs-center"></div>
    <div class="tunnel circle abs-center"></div>
    <div class="core-wrapper circle abs-center"></div>
    <div class="core-outer circle abs-center"></div>
    <div class="core-inner circle abs-center"></div>
    <div class="coil-container">
      <div class="coil coil-1"></div>
      <div class="coil coil-2"></div>
      <div class="coil coil-3"></div>
      <div class="coil coil-4"></div>
      <div class="coil coil-5"></div>
      <div class="coil coil-6"></div>
      <div class="coil coil-7"></div>
      <div class="coil coil-8"></div>
    </div>
  </div>
</div>

让我们给它一个深色的背景并添加一些辉光。

.reactor-container-inner {
  height: 238px;
  width: 238px;
  background-color: rgb(22, 26, 27);;
  box-shadow: 0px 0px 4px 1px #52fefe;
}

查看Kunal Sarkar在CodePen上创作的Pen Arc-Reactor-Semi-Final (@supersarkar)。

看看深色背景和辉光是如何产生浮雕效果的?

接下来,让我们使.rotator-container变圆并为其添加一些阴影和边框,然后我们就完成了。

.reactor-container {
  width: 300px;
  height: 300px;
  margin: auto;
  border: 1px dashed #888;
  position: relative;
  border-radius: 50%;
  background-color: #384c50;
  border: 1px solid rgb(18, 20, 20);
  box-shadow: 0px 0px 32px 8px rgb(18, 20, 20), 0px 0px 4px 1px rgb(18, 20, 20) inset;
}

查看 CodePen 上 Kunal Sarkar (@supersarkar) 编写的 钢铁侠的弧反应堆

干杯!我们的电弧反应堆已准备就绪,甚至还带有一点动画作为额外奖励。为了提升它,我们可以探索使用自定义属性为我们的颜色和数值创建可重用变量,以便于维护。同样,我们可以考虑使用预处理器——例如Sass、Less或PostCSS——编写函数来为我们完成数学运算。希望在评论中看到这样的示例!