使用CSS和JS实现灵动岛动画
Alplune 摸鱼ing

使用 CSSJavaScript 实现 iPhone 14 Pro “灵动岛” 动画


背景:

在我十八岁的最后一天 9.8 号,苹果公司召开秋季新品发布会,推出了 iPhone 14 等一系列产品。

动图

和大家一样,第一眼看到的时候我也觉得很惊艳,这丝滑带来的高级感,好灵动 😮!觉得厨子真是巧夺天工。作为一名前端学习者,我最感兴趣的当然是灵动岛动画的实现。经过一定的了解和学习后,那么让我们进入今天的主题,如何用 CSS 和 Javascript 来浅浅灵动一下。


相关的前端知识

本次分享会涉及前端三大件:

  1. HTML 定义了网页的内容
  2. CSS 描述了网页的布局
  3. JavaScript 控制了网页的行为

让我们开始吧!

首先我们先用 HTML 和 CSS 做出手机的静态页面

HTML 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="./style.css" />
<title>Document</title>
</head>

<body>
<div class="iphone14Pro">
<div class="dynamic-island"></div>
</div>
</body>
</html>

CSS 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
.iphone14Pro {
position: relative;
margin: auto;
width: 974px;
height: 876px;
overflow: hidden;
background-image: url(https://www.apple.com.cn/v/iphone-14-pro/a/images/overview/dynamic-island/dynamic_hw__btl4fomgspyu_large.png);
}

.dynamic-island {
width: 320px;
margin-top: 72px;
margin: 72px auto 0;
background-color: red;
height: 80px;
border-radius: 40px;
position: relative;
}
此处因为类名为dynamic-island的div内没有内容,所以用::before
::after没有区别
.dynamic-island::before {
position: absolute;
content: " ";
right: 0;
width: 80px;
height: 100%;
border-radius: 80px;
background-color: #272729;
}

这样我们就得到了半部 iPhone 14Pro啦!(bushi)

image-20220929230637769

距离灵动岛只需要一点点 CSSJS 就可以啦 😁


一些 Web 动画的知识

目前 web 动画在实现时主要有两种方式:CSS/ JS

但是独立使用任何一个都不是最好的解决方案

只用 CSS3 时,我们不好控制整个动画进程,如简单的点击触发动画,循环轮播整套动画等。

只用 JS ,由于 JS 单线程的特性,所以在动画渲染时,性能不如 CSS3,可能出现不流畅情况。


在 CSS3 中,也有两种实现灵动岛这样的过渡动画的方法: animation 和 transition
animation 属性

animatio 是很多属性的简写,我们简单介绍了解一下:

1
2
animation: name duration timing-function delay iteration-count direction
fill-mode;

name: 代表一个由@keyframes定义的动画序列的名字

duration : 动画持续时间

timing-function : 定义动画在每一动画周期中执行的节奏:CSS ease 各种效果的区别(ease 与 ease-in-out 的区别)_哔哩哔哩_bilibili

delay : 动画开始前的延迟时间

iteration-count: 动画播放次数

direction: 指示动画是否反向播放

fill-mode: 设置动画在执行之前和之后对应的样式选择

transition 属性

transition 也是是很多属性的简写,我们简单介绍了解一下:

transition-property: 指定应用过渡属性的名称, 默认为 all

transition-duration: 过度动画的持续时间

transition-timing-function: 和上面的 timing-function 类似

transition-delay: 和上面的 delay 类似

下面我们会分别使用这两个属性来实现动画效果


灵动岛的具体实现

我们今天简单实现下面四个常用的动画效果

1.后台有音乐,倒计时,开热点,打电话等活动时,灵动岛变成长条

① 我们先用 transition 属性来实现
只需要在灵动岛的 CSS 上加上一个 transition 属性

1
2
3
4
5
6
transition:    width    cubic-bezier(0.3, 2, 1.0, 0.9)  0.8s;
/* 灵动岛宽度变化时 遵循上面这个贝塞尔曲线函数 并在0.8s内完成 */

然后我们用鼠标停在灵动岛模拟后台有活动
.dynamic-island:hover {width: 50vw;}
让我们看看效果

​ ② 我们用 animation 属性来实现

​ 因为是给鼠标悬停时加动画,animation 的属性加在 hover 的伪类选择器上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.dynamic-island:hover {
animation: long 800ms ease-in-out forwards;
/* 当鼠标停在灵动岛上,触发名为long的动画,
0.8s内完成 且结束后定在最后一帧*/
}
下面我们定义long 的动画 @keyframes long {
0% {
}

60% {
width: 50vw;
}

80% {
transform: scaleX(1.04);
}

100% {
transform: scaleX(1);
width: 50vw;
}
}

我们不难发现,要让动画有 “灵动感” ,动画的结尾往往表现出一种”超过预期大小,然后又缩回“ 的效果,在一伸一缩之中给了观众灵动的感觉。

对比 transition 和 animation

他们虽然都能做出“灵动感”,但是前者的实现会相对困难一些,虽然代码看起来简洁,但是贝塞尔曲线来描述理想的动画并没有直接用 animation 的定义关键帧写起来直观~

所以下面的动画效果都用 animation 实现(肯定不是我偷懒 🙃)


2.后台同时有两个活动的时候,灵动岛会分成两个部分(三个以上只显示两个)

​ 我们用点击灵动岛给灵动岛加类名的方式,让灵动岛在不同动画间切换

​ 灵动岛分成两部分的原理是把里面藏着的一个小球弹出,本身也做一个放大缩小的动感

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
.divide {
animation: divide-left 800ms ease-in-out forwards;
}

@keyframes divide-left {
0% {
}

40% {
transform: scaleX(1.1);
}

100% {
transform: scaleX(1);
}
}

.divide::before {
animation: divide-right 800ms ease-in-out forwards;
}

@keyframes divide-right {
0% {
}

40% {
transform: scaleX(1.1);
}

100% {
transform: scaleX(1);
right: -100px;
}
}

3.关掉只剩一个后台任务时,两部分融合

与分开成两部分类似,逆过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
.fusion {
animation: fusion-left 800ms ease-in-out forwards;
}

@keyframes fusion-left {
0% {
}

40% {
transform: scaleX(1.1);
}

100% {
transform: scaleX(1);
}
}

.fusion::before {
animation: fusion-right 800ms ease-in-out forwards;
}

@keyframes fusion-right {
0% {
right: -100px;
}

40% {
transform: scaleX(1.1);
}

100% {
transform: scaleX(1);
right: 0;
}
}

4.长按唤出小组件的状态

原理是把灵动岛变宽变长,小圆球设置为 display:none

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
.bigger {
animation: bigger 800ms ease-in-out forwards;
}

@keyframes bigger {
0% {
}

60% {
width: 81vw;
height: 400px;
border-radius: 100px;
}

80% {
transform: scaleX(1.04);
}

100% {
width: 81vw;
height: 400px;
border-radius: 100px;
transform: scaleX(1);
}
}

.bigger::before {
display: none;
}

JS 代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const dynamicIsland = document.querySelector(".dynamic-island");

let index = 0;
animationList = ["long", "normal", "divide", "fusion", "bigger", "reset"];

dynamicIsland.addEventListener("click", () => {
setTimeout(() => {
if (index <= animationList.length - 1) {
dynamicIsland.classList.add(animationList[index]);
index++;
} else {
index = 0;
dynamicIsland.classList.remove(
"long",
"normal",
"divide",
"fusion",
"bigger",
"reset"
);
dynamicIsland.classList.add(animationList[index]);
}
}, 100);
});

总结:

iPhone 14 Pro 好不好用,灵动岛的实际体验到底好不好,我并没有资格评价(没用过)

但是灵动岛这种新颖的交互方式和大胆创新的勇气,都值得我们学习

好了今天分享就到这里,相信大家就算没有买 iPhone 14,也灵动了一把 😊。

代码地址:Mingaaaaaaa/Dynamic-Island (github.com)