重现 Apple Watch 呼吸 App 动画

Avatar of Geoff Graham
Geoff Graham

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 $200 免费信用额度!

Apple Watch 带有一个名为 Breathe 的内置应用程序,提醒您,嗯,呼吸。 事实上,它不仅仅是提醒您呼吸,但是需要提醒自己呼吸的想法让我发笑。 关键是,这个应用程序有一个非常棒的界面,带有一个很棒的动画。

图片由 Apple 支持 提供

我认为用原生 CSS 重现这个设计会很有趣。 我已经完成了这项工作,感觉非常接近。

查看 CodePen 上 Geoff Graham (@geoffgraham) 的作品 Apple Watch 呼吸 App 动画.

制作圆形

首先,我们需要一组圆形来组成看起来像花的图案。 应用程序本身会为计时器添加的每一分钟添加一个圆形,但我们将在这个演示中使用六个静态圆形。 感觉上,我们可以使用 ::before::after 来减少 HTML 标记,但我们可以保持简单。

<div class="circle"></div>
<div class="circle"></div>
<div class="circle"></div>
<div class="circle"></div>
<div class="circle"></div>
<div class="circle"></div>

我们将使每个圆形的完整尺寸为 125px,这是一个任意数字。 重要的是,圆形默认状态应全部叠放在彼此的顶部。 我们可以使用绝对定位来实现这一点。

.circle {
  border-radius: 50%;
  height: 125px;
  position: absolute;
  transform: translate(0, 0);
  width: 125px;
}

请注意,我们使用 transform 属性的 translate 函数来居中所有内容。 我最初尝试使用基本的 top, right, bottom, left 属性,但后来发现动画 translate 要平滑得多。 我最初还认为将圆形定位在完全展开的状态将是最好的起点,但后来发现以这种方式创建动画非常麻烦,因为它需要将每个动画重置为居中。 经验教训!

如果我们在此停止,屏幕上将不会显示任何内容,因为我们尚未设置 background 颜色。 我们将在稍后讨论应用程序中使用的漂亮花哨的颜色,但现在添加一个带有透明度的白色背景可能会有所帮助,以便在工作时看到正在发生的事情。

查看 CodePen 上 Geoff Graham (@geoffgraham) 的作品 Apple Watch 呼吸 App – 第一步.

我们需要一个容器!

您可能已经注意到,我们的圆形整齐地叠放,但距离视窗的实际中心很远。 我们需要将这些圆形包裹在一个父元素中,以便我们可以定位整个圆形。 此外,该容器将充当稍后脉动并旋转整个圆形的元素。 这是我必须用艰苦的方式学到的另一课,因为我固执地不想使用容器的额外标记,并认为我可以绕过它。

我们在这里将容器命名为 .watch-face,并将其设置为与单个圆形相同的宽度和高度。

<div class="watch-face">
  <div class="circle"></div>
  <div class="circle"></div>
  <div class="circle"></div>
  <div class="circle"></div>
  <div class="circle"></div>
  <div class="circle"></div>
</div>

现在,我们可以向 body 元素添加一些 flex,以便将所有内容居中。

body {
  background: #000;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100vh;
}

查看 CodePen 上 Geoff Graham (@geoffgraham) 的作品 Apple Watch 呼吸 App – 第二步.

接下来,动画圆形

在这一点上,我很想看到圆形以那种整齐的花卉叠加排列排列。 我知道,在没有看到它们定位的情况下,动画每个圆形的精确位置会很困难,所以我覆盖了每个圆形中的 transform 属性,以查看它们会落在哪里。

我们可以为每个圆形设置一个类,但使用 :nth-child 似乎更简单。

.circle:nth-child(1) {
  transform: translate(-35px, -50px);
}

/* Skipping 2-5 for brevity... */

.circle:nth-child(6) {
  transform: translate(35px, 50px);
}

我尝试了几次才找到合适的坐标。 它最终取决于圆形的尺寸,可能需要进行一些调整。

查看 CodePen 上 Geoff Graham (@geoffgraham) 的作品 Apple Watch 呼吸 App – 第步 3.

有了坐标,我们就可以注册动画。 我删除了应用于每个 :nth-childtransform 坐标,并将它们移动到关键帧中

@keyframes circle-1 {
  0% {
    transform: translate(0, 0);
  }
  100% {
    transform: translate(-35px, -50px);
  }
}

/* And so on... */

我必须承认,我使用的方法感觉很笨拙,因为每个圆形都有自己的动画。 最好有一个动画可以控制所有圆形,以便推动和重新居中圆形,但也许阅读这篇文章的其他人有想法,可以在评论区分享。

现在,我们可以将这些动画应用于每个 :nth-child,以代替 transform

.circle:nth-child(1) {
  animation: circle-1 4s ease alternate infinite;
}

/* And so on... */

请注意,我们将 animation-timing-function 设置为 ease,因为感觉很流畅……至少对我来说是! 我们还将 animation-direction 设置为 alternate,以便动画来回播放,并将 animation-iteration-count 设置为 inifinite,以便动画持续运行。

查看 CodePen 上 Geoff Graham (@geoffgraham) 的作品 Apple Watch 呼吸 App – 第步 4.

颜色,颜色,颜色!

哦,是的,让我们把它涂上颜色! 从我所能看到的信息来看,这个设计中只有两种颜色,不透明度使它看起来更像一个频谱。

左侧的圆形是绿色的,右侧的圆形是蓝色的。 我们可以选择奇数圆形来应用绿色,选择偶数圆形来应用蓝色。

.circle:nth-child(odd) {
  background: #61bea2;
}

.circle:nth-child(even) {
  background: #529ca0;
}

哦,别忘了从 .circle 元素中删除白色背景。 它不会有任何问题,但清理一下总是好的。 我必须承认,我在第一次做的时候忘记了这一点。

查看 CodePen 上 Geoff Graham (@geoffgraham) 的作品 Apple Watch 呼吸 App – 第步 5.

同样在这一点上,评论区中的其他人建议用 mix-blend-mode 替换 opacity,并使用 screen 值,以更佳的方式混合圆形的颜色。 我已经更新了演示和代码。

脉动和旋转

还记得我们创建的恼人的 .watch-face 容器吗? 好吧,我们可以对它进行动画处理,使圆形进出脉动,同时旋转整个圆形。

我完全忘记了 transform 函数可以链接在一起。 这使事情变得更干净,因为它允许我们在同一行上应用 scale()rotate()

@keyframes pulse {
  0% {
    transform: scale(.15) rotate(180deg);
  }
  100% {
    transform: scale(1);
  }
}

……并将它应用于 .watch-face 元素。

.watch-face {
  height: 125px;
  width: 125px;
  animation: pulse 4s cubic-bezier(0.5, 0, 0.5, 1) alternate infinite;
}

与圆形一样,我们希望动画双向运行并无限重复。 在这种情况下,当圆形叠放在一起时,缩放会缩小到一个非常小的尺寸,并且整个圆形在向外旋转一半后会返回。

我必须承认,在找到最流畅或最精确的动画的正确 animation-timing-function 方面,我并不是高手。 我玩了 cubic-bezier,找到了一些我认为感觉很棒的东西,但可能像 ease-in 这样的库存值也能达到同样的效果。

现在一起!

以下是在同一个演示中将所有内容压缩在一起。

查看 CodePen 上 Geoff Graham (@geoffgraham) 的作品 Apple Watch 呼吸 App 动画.

现在,请记住要呼吸,让我们都度过一个平静的一天。 ☮️