用css动画写一个“灵动岛”玩玩吧

7,998 阅读5分钟

我正在参加「掘金·启航计划」

前言

最近苹果发布了iphone 14系列的新品,说起内容的话,大家印象最深的应该就是苹果所谓的“灵动岛”了吧,毕竟发布会上看着还是挺炫酷的呢,就像下面这样:

test13.gif

看完大家都控制不了自己的手了吧?

Suggestion (2).gif

买来结果变成了这样:

image.png

网络上顿时遍地吐槽,色差严重,虚假宣传...... Suggestion.gif

其实在晚上不开灯的时候用效果真的挺好的。👻

好了,咱们言归正传吧,别管啥色差严不严重,今天就用CSS动画实现一个Smart Island的效果,顺便补习一下CSS动画

如果有掘友不了解CSS动画,可以看看这个文章,大佬写的,个人觉得非常棒👍 深入浅出 CSS 动画 · Issue #141 · chokcoco/iCSS

实现Smart Island

新建smart-island.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>smart-island</title>
</head>
<body>
  <div id="phone">
   <div class="island"></div>
  </div>
</body>
</html>

iPhone壳样式

body设置为flex布局,并设置里面的内容居中显示:

body {
  display: flex;
  justify-content: center;
}

id为phone这个div,我准备使用一张iPhone的外壳背景图片,搞一个iPhone的样子。

#phone {
  width: 374px;
  height: calc(844px - 16px);
  text-align: center;
  background-size: contain;
  background-repeat: no-repeat;
  background-image: url(https://www.apple.com.cn/v/iphone-14-pro/a/images/overview/dynamic-island/dynamic_hw__btl4fomgspyu_large.png);
}

✅看下现在的样子:

image.png

覆盖iPhone的药丸

想要让iPhone的药丸动起来,那么我们就得自己实现一个药丸覆盖图片中的,再为它添加动画效果。把class为islanddiv设置成药丸的样子,这个很简单了,设置一下宽高,边框圆角,颜色即可。

.island {
  width: 104px;
  display: inline-block;
  height: 29px;
  border-radius: 18px;
  background-color: black;
  margin-top: 28px;
}

✅完了就像下面这样: image.png

变长动画

做完上面的操作,接着我们就可以来实现变长的动画了。

变长嘛,就是把宽度弄宽呗,简单:

.longer {
  animation: longer 800ms ease-in-out forwards;
}
@keyframes longer {
  0% {
  }
  100% {
    width: 208px;
  }
}

再界面上添加一个按钮,用来给药丸添加class。

<div>
    <button onclick="addClass('longer')">longer</button>
<div>

实现addClass

const isLand = document.querySelector('.island')

function addClass(cls) {
  isLand.classList.add(cls)
}

简单设置下按钮的样式

button{
  background: #fff;
  border: 1px solid #e0e0e0;
  line-height: 20px;
  border-radius: 4px;
}

✅好了,看下效果吧:

test14.gif 还可以,但是我们可以仔细的看下苹果的那个视频,它变长的时候是先变长,快结束时又变长了一点点,然后又缩回来了。调整下:

@keyframes longer {
  0% {
  }
  60% {
    width: 208px;
  }
  80% {
    transform: scaleX(1.06);
  }
  100% {
    transform: scaleX(1);
    width: 208px;
  }
}

✅再看看效果:

test14.gif 瞬间感觉更有逼格了。

哈哈哈呵呵呵.gif

分离动画

分离动画就是药丸后面分离出一个小圆那个动画。那我们是不是需要实现一个小圆,再给它添加动画效果❓

没错,那可以有两种方案:

  1. 给island节点设置after伪类,将伪类设置为小圆
  2. 新增一个节点,设置为小圆

笔者这里使用伪类去实现:

.island::after {
  height: 29px;
  content: ' ';
  width: 29px;
  right: 0;
  position: absolute;
  border-radius: 50%;
  background-color: black;
}

这里别忘了给island添加position: relative;

这个时候我们看不到效果,因为小圆和药丸叠在一起了,接下来添加动画。药丸不动,只要有个缩放的效果即可,改变小圆的right,让小圆可见。

/* 分离 */

/* 药丸 */
.separate {
  animation: separate-left 800ms ease-in-out forwards;
}
/* 小圆 */
.separate::after {
  animation: separate-right 800ms ease-in-out forwards;
}

@keyframes separate-left {
  0% {
    width: 208px;
  }
  40% {
    transform: scaleX(1.06);
    width: 104px;
  }
  100% {
    transform: scaleX(1);
    width: 104px;
  }
}

@keyframes separate-right {
  0% {
    right: 0;
  }
  40% {
    transform: scaleX(1.06);
  }
  100% {
    transform: scaleX(1);
    right: -35px;
  }
}

✅看下效果:

test14.gif

合并动画

合并动画就是把小圆和药丸合并到一起,恢复分离前的样子,那不就是把小圆的right改回来么,so easy😂

/* 合并 */
.merge {
  animation: merge-left 800ms ease-in-out forwards;
}
.merge::after {
  animation: merge-right 800ms ease-in-out forwards;
}
@keyframes merge-left {
  0% {
  }
  40% {
    transform: scaleX(1.06);
  }
  100% {
    transform: scaleX(1);
  }
}
@keyframes merge-right {
  0% {
    right: -35px;
  }
  40% {
    transform: scaleX(1.06);
  }
  100% {
    transform: scaleX(1);
    right: 29px;
  }
}

✅看看效果

test14.gif

变大动画-1

为啥是-1,因为变大的动画有两种,一个高度小一些,一个高度大一些。我们先来实现小一些这个动画。很简单了,就是改变宽度高度还有边框圆角。

/* larger-s */
.larger-s {
  animation: larger-s 800ms ease-in-out forwards;
}
@keyframes larger-s {
  0% {
  }
  60% {
    width: 298px;
    height: 44px;
    border-radius: 20px;
  }
  80% {
    transform: scaleX(1.04);
  }
  100% {
    transform: scaleX(1);
    width: 298px;
    height: 44px;
    border-radius: 20px;
  }
}

✅看看效果

test14.gif

变大动画-2

✅看看效果

/* larger-l */
.larger-l {
  animation: larger-l 800ms ease-in-out forwards;
}
@keyframes larger-l {
  0% {
  }
  60% {
    width: 298px;
    height: 160px;
    border-radius: 40px;
  }
  80% {
    transform: scaleX(1.04);
  }
  100% {
    transform: scaleX(1);
    width: 298px;
    height: 160px;
    border-radius: 40px;
  }
}

test14.gif

把动画连起来播放

截至目前,我们实现了五个简单的动画效果,点击一个按钮执行一个,感觉傻傻的,我们把五个动画连起来播放,一个接一个的播放。

const isLand = document.querySelector('.island')
let index = 0
const animations = ['longer', 'separate', 'merge', 'larger-s', 'larger-l']

function start() {
  if(index > animations.length - 1) return
  isLand.classList.add(animations[index])
  index++
}

isLand.addEventListener('animationend', (e) => {
  if(e.target === isLand && !['separate-right', 'merge-right'].includes(e.animationName)) {
    setTimeout(() => {
      start()
    }, 1000)
  }
})

现在只需要一个开始按钮了。

<button onclick="start()">START▶️</button>

✅最终效果 test14.gif

✅完整代码

总结

本文通过CSS动画实现了类似iPhone灵动岛的动画效果,虽然看着复杂,但把动画拆分开来,还是非常简单的,就是一些宽高、圆角的改变,左右移动(当然还存在一些瑕疵)。和那些CSS动画高手玩的花样根本不是一个级别。不过用来练习CSS动画还是非常好的,代码熟练度都是敲出来的。学技术还是不能停留在看,还是得多实践。

show me the code