用 CSS 实现 AirDrop 动效

3,210 阅读6分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第8篇文章,点击查看活动详情

AirDrop 效果介绍

用过 Mac 电脑的同学都知道 AirDrop 可以在不同电脑和手机设备之间共享文件,功能非常强大,如下图所示:

我们今天来研究一下 AirDrop 视觉效果,带领大家一步步在网页中实现它。整个背景是一圈圈的圆形轨道,类似于行星围绕着太阳旋转,当发现新设备后,就会在轨道上展示该设备,并附加波纹扩散动效:

AirDrop 效果的本质

如果把上面的图中的文案和图片全部去掉之后,你会发现剩下的全都是同心圆,所以要做 AirDrop 效果,只要能做到以下三点即可:

  • 实现同心圆
  • 实现切角同心圆
  • 实现同心圆扩散效果

从零写一个AirDrop效果

实现同心圆

最简单的实现思路就是:有多少个圆我就写多少个 div,然后把它们给叠加起来。这种方式也没毛病,假设有 4 个同心圆,最里面的圆形半径是 100px,我们直接看代码,其中 HTML 结构很简单:

<body>
  <div class="circle-center">
    <div class="circle"></div>
    <div class="circle"></div>
    <div class="circle"></div>
    <div class="circle"></div>
  </div>
</body>

CSS 代码如下:

.circle-center {
  --radius: 100px;
  --diameter: calc(2 * var(--radius));
  position: fixed;
  bottom: calc(20px + var(--radius));
  left: 50%;
  width: 0;
  height: 0;
}

.circle {
  position: absolute;
  top: 0;
  left: 0;
  transform: translateX(-50%) translateY(-50%);
  border: 1px solid #ddd;
  border-radius: 50%;
  box-sizing: border-box;
}

.circle:nth-child(1) {
  width: var(--diameter);
  height: var(--diameter);
}

.circle:nth-child(2) {
  width: calc(2 * var(--diameter));
  height: calc(2 *var(--diameter));
}

.circle:nth-child(3) {
  width: calc(3*var(--diameter));
  height: calc(3*var(--diameter));
}

.circle:nth-child(4) {
  width: calc(4*var(--diameter));
  height: calc(4*var(--diameter));
}

这里用到了 CSS 变量,可以非常方便的修改半径来调整同心圆间距。我们看下效果:

是不是有那么一点感觉了?

实现切角同心圆

在 airdrop 的中心有一个缺角的红蓝相间的同心圆,然后把底部大约30度的扇形区块给切掉即可。接下来我们就来实现它。

有了第一步的基础,画一个红蓝相间的同心圆已经非常简单了,只需要去掉 border,添加 background-color 即可,大家可以自己尝试一下。

但是这里介绍另外一种利用 box-shadow 画同心圆的办法,只需要一个 div 即可。我们在 circle-center 里面增加 circle-ring 和 circle-text 两个节点分别用于创建切角同心圆和下面的文字:

<div class="circle-center">
  <div class="circle"></div>
  <div class="circle"></div>
  <div class="circle"></div>
  <div class="circle"></div>
  <div class="circle-ring"></div>
  <div class="circle-text">
    <div class="text-intro">”隔空投送“可让您与附近的用户立即共享</div>
    <div class="text-setting">允许这些人发现我:所有人 <img src="./arrow-down.svg" /></div>
  </div>
</div>

CSS 代码如下:

.circle-ring {
  --ring-width: 90px;
  --border-width: 6px;
  --gutter-width: 7px;
  --color: #286ad3;
  position: absolute;
  width: var(--ring-width);
  height: var(--ring-width);
  top: calc(var(--ring-width) / -2);
  left: calc(var(--ring-width) / -2);
  box-sizing: border-box;
  border-radius: 50%;
  background: var(--color);
  box-shadow: inset 0 0 0 var(--border-width) var(--color),
    inset 0 0 0 calc(var(--border-width) + var(--gutter-width)) white,
    inset 0 0 0 calc(2 * var(--border-width) + var(--gutter-width)) var(--color),
    inset 0 0 0 calc(2 * var(--border-width) + 2 * var(--gutter-width)) white,
    inset 0 0 0 calc(3 * var(--border-width) + 2 * var(--gutter-width)) var(--color),
    inset 0 0 0 calc(3 * var(--border-width) + 3 * var(--gutter-width)) white;
}

这里定义了一个半径为 100px 的圆,然后利用 box-shadow 添加蓝白相间的多个内阴影,其中 --border-width表示蓝色边框的宽度,--gutter-width表示白色间距的宽度,做成 CSS 变量也是方便调整,看下效果吧:

接下来我们得把底部做一个豁口才行,也有两种实现思路:

  • 利用 clip-path 切割图形
  • 用一个 div 画一个白色的扇形遮挡圆形底部

这里选择更为强大的 clip-path 来实现,只需要一行代码即可:

clip-path: polygon(45% 60%, 20% 100%, 0% 100%, 0 0, 100% 0, 100% 100%, 80% 100%, 55% 60%);

裁剪后的效果如下:

clip-path 的语法不做详细介绍,感兴趣的读者可以自行 Google 查询,它可以裁剪矩形、圆形、椭圆、多边形甚至指定的路径:

.div {
  clip-path: inset(100px 50px);
  clip-path: circle(50px at 0 100px);
	clip-path: ellipse(50px 60px at 0 10% 20%);
	clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
  clip-path: path('M15,45 A30,30,0,0,1,75,45 A30,30,0,0,1,135,45 Q135,90,75,130 Q15,90,15,45 Z');
  clip-path: url(#myPath);
}

最常用的使用场景就是裁剪图片,大家可以前往 css-tricks 网站体验,里面提供了一个非常好用的 demo,可以非常直观的看到 clip-path 属性的效果:

最后稍作调整,补上文字的样式:

.circle-text {
  position: absolute;
  top: 70px;
  left: 0;
  width: 400px;
  transform: translateX(-50%);
  text-align: center;
  font-size: 13px;
}

最终实现的页面已经跟 Mac 自带的非常接近了:

实现同心圆扩散效果

当 airdrop 发现设备的时候,会把设备放在同心圆的轨道上,也显示成一个圆形,而且呈现雷达扩散效果,这个应该怎么实现呢?

首先我们完善一下 HTML 结构,增加 wave 波动容器:

<div class="circle-center">
  <div class="circle"></div>
  <div class="circle"></div>
  <div class="circle"></div>
  <div class="circle"></div>
  <div class="circle-ring"></div>
  <div class="circle-text">
    <div class="text-intro">”隔空投送“可让您与附近的用户立即共享</div>
    <div class="text-setting">允许这些人发现我:所有人 <img src="./arrow-down.svg" /></div>
  </div>

  <div class="wave active">
    <div class="wave-avatar"></div>
    <div class="wave-animation">
      <div class="wave-circle"></div>
      <div class="wave-circle1"></div>
      <div class="wave-circle2"></div>
      <div class="wave-circle3"></div>
    </div>
    <div class="wave-text">iPhone14</div>
  </div>
</div>

其中:

  • wave 是设备容器
  • wave-avatar 用于放置中间的头像
  • wave-animation 用于实现波动效果

--wave-size变量来表示设备容器的宽度,同样也是参照 circle-center 进行绝对定位,wave 容器的 CSS 代码如下:

.wave {
  --wave-size: 90px;
  position: absolute;
  top: -300px;
  width: var(--wave-size);
  height: var(--wave-size);
  position: relative;
  text-align: center;
  overflow: visible;
  transform: translate(calc(var(--wave-size) / -2), calc(var(--wave-size) / -2));
}

wave-avatar 就非常简单了,依然采用绝对定位放置头像:

.wave-avatar {
  border-radius: 50%;
  position: absolute;
  width: var(--wave-size);
  height: var(--wave-size);
  top: 0;
  left: 0;
  z-index: 2;
  display: block;
  background-image: url(avatar.svg);
  background-size: cover;
}

最难实现的是 wave-animation 向外扩散的效果,我们先把容器准备好,初始状态是 4 个重叠在一起的 div,默认是没有动效的,当给 wave 增加 active 属性之后再让它动起来:

.wave-animation {
  position: absolute;
  width: var(--wave-size);
  height: var(--wave-size);
  top: 0;
  left: 0;
  display: none;
}

.wave-animation div {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

.wave.active .wave-animation {
  display: block;
}

最关键的一步来了,我们嵌套了 4 个重叠的圆,其中1个是白色背景,剩下3个是灰色背景,并且加了 circleChange 扩散动效,先贴代码:

.wave-circle,
.wave-circle1,
.wave-circle2,
.wave-circle3 {
  border-radius: 50%;
}

.wave-circle {
  background-color: white;
  z-index: 1;
}

.wave-circle1,
.wave-circle2,
.wave-circle3 {
  background-color: #efefef;
  animation-name: circleChange;
  animation-duration: 3s;
  animation-iteration-count: infinite;
  animation-timing-function: linear;
}

.wave-circle1 {
  animation-delay: 0s;
}

.wave-circle2 {
  animation-delay: 1s;
}

.wave-circle3 {
  animation-delay: 2s;
}

@keyframes circleChange {
  0% {
    transform: scale(1);
    opacity: 0.95;
  }

  25% {
    transform: scale(1.4);
    opacity: 0.75;
  }

  50% {
    transform: scale(1.8);
    opacity: 0.5;
  }

  75% {
    transform: scale(2.2);
    opacity: 0.25;
  }

  100% {
    transform: scale(2.6);
    opacity: 0.05;
  }
}

从单个圆的角度来看,是利用 scale 和 opacity 来做一个跟随时间放大且渐变的效果,如下图所示:

但是有 3 个圆同时播放这个动画,并且依次延迟 0 秒、1 秒和 2 秒,最终就形成了向外渐变扩散的效果。最后再补上文案样式:

.wave-text {
  position: absolute;
  bottom: -30px;
  width: 100%;
}

我们最终的效果就出来啦:

动图效果如下:

怎么样,有没有一种丝滑的感觉?到此为止,我们的网页版 AirDrop 就完成啦,欢迎大家点赞、评论、分享!