效果预览

按下右侧的“点击预览”按钮可以在当前页面预览,点击链接可以全屏预览。

https://codepen.io/comehope/pen/eLMKJG

可交互视频

此视频是可以交互的,你可以随时暂停视频,编辑视频中的代码。

请用 chrome, safari, edge 打开观看。

https://scrimba.com/p/pEgDAM/cdDRmH9

源代码下载

每日前端实战系列的全部源代码请从 github 下载:

https://github.com/comehope/front-end-daily-challenges

代码解读

定义 dom,容器中包含 10 个 div 子元素,每个 div 中包含 1 个 span 元素:

<figure class="container">
<div><span></span></div>
<div><span></span></div>
<div><span></span></div>
<div><span></span></div>
<div><span></span></div>
<div><span></span></div>
<div><span></span></div>
<div><span></span></div>
<div><span></span></div>
<div><span></span></div>
</figure>

居中显示:

body {
margin: 0;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background-color: lightyellow;
}

定义容器的尺寸和样式:

.container {
width: 400px;
height: 400px;
background: linear-gradient(45deg, tomato, gold);
border-radius: 3%;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}

画出容器里的 1 个元素,它有一个外壳 div,里面是一个白色的小方块 span

.container {
position: relative;
} .container div {
position: absolute;
width: inherit;
height: inherit;
display: flex;
align-items: center;
justify-content: center;
} .container div span {
position: absolute;
width: 40px;
height: 40px;
background-color: white;
}

为容器中的元素定义下标变量,并让元素的外壳依次旋转,围合成一个圆形,其中 outline 是辅助线:

.container div {
outline: 1px dashed black;
transform: rotate(calc((var(--n) - 1) * 36deg));
} .container div:nth-child(1) { --n: 1; }
.container div:nth-child(2) { --n: 2; }
.container div:nth-child(3) { --n: 3; }
.container div:nth-child(4) { --n: 4; }
.container div:nth-child(5) { --n: 5; }
.container div:nth-child(6) { --n: 6; }
.container div:nth-child(7) { --n: 7; }
.container div:nth-child(8) { --n: 8; }
.container div:nth-child(9) { --n: 9; }
.container div:nth-child(10) { --n: 10; }

至此,子元素绘制完成,接下来开始写动画脚本。
引入 GSAP 库:

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.0.2/TweenMax.min.js"></script>

定义一个变量,代表子元素选择器:

let elements = '.container div span';

声明一个时间线对象:

let animation = new TimelineMax();

先设定入场方式为由小(第1帧)变大(第2帧),其中并没有第 2 帧的代码,它是隐含在语义中的:

animation.from(elements, 1, {scale: 0});

让子元素变成竖长条,向四周散开(第3帧):

animation.from(elements, 1, {scale: 0})
.to(elements, 1, {y: '-100px', scaleX: 0.25});

让竖长条旋转着变成小方块(第4帧):

animation.from(elements, 1, {scale: 0})
.to(elements, 1, {y: '-100px', scaleX: 0.25})
.to(elements, 1, {scaleY: 0.25, rotation: 180});

让小方块变成横长条,围成一个圆形(第5帧):

animation.from(elements, 1, {scale: 0})
.to(elements, 1, {y: '-100px', scaleX: 0.25})
.to(elements, 1, {scaleY: 0.25, rotation: 180})
.to(elements, 1, {scaleX: 1});

注意,因 scrimba 在录制过多帧时会崩溃,所以第 6 帧至第 11 帧没有在视频中体现。
让圆形向内收敛,同时线条变细(第6帧):

animation.from(elements, 1, {scale: 0})
.to(elements, 1, {y: '-100px', scaleX: 0.25})
.to(elements, 1, {scaleY: 0.25, rotation: 180})
.to(elements, 1, {scaleX: 1})
.to(elements, 1, {y: '-60px', scaleY: 0.1});

让线条向左摆动(第7帧):

animation.from(elements, 1, {scale: 0})
.to(elements, 1, {y: '-100px', scaleX: 0.25})
.to(elements, 1, {scaleY: 0.25, rotation: 180})
.to(elements, 1, {scaleX: 1})
.to(elements, 1, {y: '-60px', scaleY: 0.1})
.to(elements, 1, {x: '-30px'});

再让线条向右摆动(第8帧):

animation.from(elements, 1, {scale: 0})
.to(elements, 1, {y: '-100px', scaleX: 0.25})
.to(elements, 1, {scaleY: 0.25, rotation: 180})
.to(elements, 1, {scaleX: 1})
.to(elements, 1, {y: '-60px', scaleY: 0.1})
.to(elements, 1, {x: '-30px'})
.to(elements, 1, {x: '30px'});

再把横线变为竖线,造型与第 3 帧相似,只是线更细,更向内收敛(第9帧):

animation.from(elements, 1, {scale: 0})
.to(elements, 1, {y: '-100px', scaleX: 0.25})
.to(elements, 1, {scaleY: 0.25, rotation: 180})
.to(elements, 1, {scaleX: 1})
.to(elements, 1, {y: '-60px', scaleY: 0.1})
.to(elements, 1, {x: '-30px'})
.to(elements, 1, {x: '30px'})
.to(elements, 1, {x: '0', scaleX: 0.1, scaleY: 1});

再把竖线变为横线,造型与第 5 帧相似,但线短一些(第10帧):

animation.from(elements, 1, {scale: 0})
.to(elements, 1, {y: '-100px', scaleX: 0.25})
.to(elements, 1, {scaleY: 0.25, rotation: 180})
.to(elements, 1, {scaleX: 1})
.to(elements, 1, {y: '-60px', scaleY: 0.1})
.to(elements, 1, {x: '-30px'})
.to(elements, 1, {x: '30px'})
.to(elements, 1, {x: '0', scaleX: 0.1, scaleY: 1})
.to(elements, 1, {scaleX: 0.5, scaleY: 0.1})

横线稍向外扩散,变为圆点(第11帧):

animation.from(elements, 1, {scale: 0})
.to(elements, 1, {y: '-100px', scaleX: 0.25})
.to(elements, 1, {scaleY: 0.25, rotation: 180})
.to(elements, 1, {scaleX: 1})
.to(elements, 1, {y: '-60px', scaleY: 0.1})
.to(elements, 1, {x: '-30px'})
.to(elements, 1, {x: '30px'})
.to(elements, 1, {x: '0', scaleX: 0.1, scaleY: 1})
.to(elements, 1, {scaleX: 0.5, scaleY: 0.1})
.to(elements, 1, {y: '-80px', scaleY: 0.5, borderRadius: '50%'});

让圆点变形为竖线,并向内收缩,这个变化的距离长,所以动画时间也要长一些(第12帧):

animation.from(elements, 1, {scale: 0})
.to(elements, 1, {y: '-100px', scaleX: 0.25})
.to(elements, 1, {scaleY: 0.25, rotation: 180})
.to(elements, 1, {scaleX: 1})
.to(elements, 1, {y: '-60px', scaleY: 0.1})
.to(elements, 1, {x: '-30px'})
.to(elements, 1, {x: '30px'})
.to(elements, 1, {x: '0', scaleX: 0.1, scaleY: 1})
.to(elements, 1, {scaleX: 0.5, scaleY: 0.1})
.to(elements, 1, {y: '-80px', scaleY: 0.5, borderRadius: '50%'})
.to(elements, 1, {y: '-10px', scaleX: 0.1, scaleY: 0.5, borderRadius: '0%', rotation: 0});

让竖线从中心向外快速扩散,扩散前稍停片刻,好像线条都被发射出一样(第13帧):

animation.from(elements, 1, {scale: 0})
.to(elements, 1, {y: '-100px', scaleX: 0.25})
.to(elements, 1, {scaleY: 0.25, rotation: 180})
.to(elements, 1, {scaleX: 1})
.to(elements, 1, {y: '-60px', scaleY: 0.1})
.to(elements, 1, {x: '-30px'})
.to(elements, 1, {x: '30px'})
.to(elements, 1, {x: '0', scaleX: 0.1, scaleY: 1})
.to(elements, 1, {scaleX: 0.5, scaleY: 0.1})
.to(elements, 1, {y: '-80px', scaleY: 0.5, borderRadius: '50%'})
.to(elements, 1, {y: '-10px', scaleX: 0.1, scaleY: 0.5, borderRadius: '0%', rotation: 0})
.to(elements, 1, {y: '-300px', delay: 0.5});

用时间尺度缩放函数让动画播放速度加快一倍:

animation.from(elements, 1, {scale: 0})
.to(elements, 1, {y: '-100px', scaleX: 0.25})
.to(elements, 1, {scaleY: 0.25, rotation: 180})
.to(elements, 1, {scaleX: 1})
.to(elements, 1, {y: '-60px', scaleY: 0.1})
.to(elements, 1, {x: '-30px'})
.to(elements, 1, {x: '30px'})
.to(elements, 1, {x: '0', scaleX: 0.1, scaleY: 1})
.to(elements, 1, {scaleX: 0.5, scaleY: 0.1})
.to(elements, 1, {y: '-80px', scaleY: 0.5, borderRadius: '50%'})
.to(elements, 1, {y: '-10px', scaleX: 0.1, scaleY: 0.5, borderRadius: '0%', rotation: 0})
.to(elements, 1, {y: '-300px', delay: 0.5})
.timeScale(2);

修改声明时间线的代码,使动画重复播放:

let animation = new TimelineMax({repeat: -1, repeatDelay: 1});

至此,动画完成。
隐藏容器外的内容,并删掉辅助线;

.container {
overflow: hidden;
} .container div {
/* outline: 1px dashed black; */
}

最后,装饰一下页面的角落:

body {
overflow: hidden;
} body::before,
body::after {
content: '';
position: absolute;
width: 60vmin;
height: 60vmin;
border-radius: 50%;
background: radial-gradient(
transparent 25%,
gold 25%, gold 50%,
tomato 50%
);
} body::before {
left: -30vmin;
bottom: -30vmin;
} body::after {
right: -30vmin;
top: -30vmin;
}

大功告成!

前端每日实战:133# 视频演示如何用 CSS 和 GSAP 创作有多个关键帧的连续动画的更多相关文章

  1. 前端每日实战:134# 视频演示如何用 CSS 和 GSAP 创作一个树枝发芽的 loader

    效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/LJmpXZ 可交互视频 此视频是可 ...

  2. 前端每日实战:144# 视频演示如何用 D3 和 GSAP 创作一个集体舞动画

    效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/gdVObN 可交互视频 此视频是可 ...

  3. 前端每日实战:136# 视频演示如何用 D3 和 GSAP 创作一个横条 loader

    效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/pOZKWJ 可交互视频 此视频是可 ...

  4. 前端每日实战:89# 视频演示如何用 CSS 和 D3 创作旋臂粒子动画

    效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/xJrOqd 可交互视频 此视频是可 ...

  5. 前端每日实战:141# 视频演示如何用 CSS 的 Grid 布局创作一枚小狗邮票

    效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/BOeEYV 可交互视频 此视频是可 ...

  6. 前端每日实战:142# 视频演示如何用 CSS 的 Grid 布局创作一枚小鸡邮票

    效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/mGZbmQ 可交互视频 此视频是可 ...

  7. 前端每日实战:116# 视频演示如何用 CSS 和原生 JS 开发一个监控网络连接状态的页面

    效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/oPjWvw 可交互视频 此视频是可 ...

  8. 前端每日实战:157# 视频演示如何用纯 CSS 创作一个棋盘错觉动画(实际上每一行都是平行的)

    效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/VEyoGj 可交互视频 此视频是可 ...

  9. 前端每日实战:158# 视频演示如何用纯 CSS 创作一个雨伞 toggle 控件

    效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/pxLbjv 可交互视频 此视频是可 ...

随机推荐

  1. QUIC协议详解

    声明 本文可以自由转载但需注明原始链接.本文为本人原创,作者LightningStar,原文发表在博客园.本文主体内容参考论文[1]完成. 介绍 QUIC,发音同quick,是"Quick ...

  2. Excel VBA中写SQL,这些问题的方法你一定要牢记

    小爬之前的文章 [Excel VBA中写SQL,这些问题你一定为此头痛过]中详细讲诉了一些常见的VBA 中使用SQL遇到的问题,这里再补充两个常见的问题场景及对应的解决方案,希望你们看了后能够思路开阔 ...

  3. LeetCode-048-旋转图像

    旋转图像 题目描述:给定一个 n × n 的二维矩阵 matrix 表示一个图像.请你将图像顺时针旋转 90 度. 你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵.请不要 使用另一个矩 ...

  4. python+selenium+pycharm使用

    上一章节讲了安装python及selenium,该章节讲解下使用pycharm编辑器进行代码编写 下载地址:http://www.jetbrains.com/pycharm/download/#sec ...

  5. Kotlin笔记小结(For Java Developer)

    这篇文章为kotlin学习记录,主要针对的是自己的知识盲区,不适用于新手. 文中所有demo均来自于kotlin官网 类型 整形 Type Size (bits) Min value Max valu ...

  6. Applied Social Network Analysis in Python 相关笔记3

    如果是option2的话,答案选A. 这里节点s,从左边的选择,节点t从右边选择. 这里计算还是用以前的值,不用更新过的值.

  7. django的request对象方法初识

    1:request.post 拿到的是post请求发送过来的数据,可以将其看作是一个个的键值对 使用get方法可以通过key拿到值,如果该值是一个列表的话,get方法只能拿到列表的最后一个值,使用ge ...

  8. Java的jstack命令使用详解

    jstack命令简介 jstack(Java Virtual Machine Stack Trace)是JDK提供的一个可以生成Java虚拟机当前时刻的线程快照信息的命令行工具.线程快照一般被称为th ...

  9. BFC优化?

    块格式化上下文, 特性: 使 BFC 内部浮动元素不会到处乱跑: 和浮动元素产生边界.

  10. 什么是内部类? Static Nested Class 和 Inner Class的不同?

    内部类就是在一个类的内部定义的类,内部类中不能定义静态成员. 内部类作为其外部类的一个成员,因此内部类可以直接访问外部类的成员.但有一点需要指出:静态成员不能访问非静态成员,因此静态内部类不能访问外部 ...