效果预览

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

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. python 镜像仓库获取最新版本号

    #/bin/python# -*- coding: utf-8 -*-import requestsfrom urllib import parsefrom requests.auth import ...

  2. js 把x,y轴两个数组变成[[x,y],[x,y]]的二维数组

    例如有X轴数组xarr=[2006,2007,2008],Y轴数组yarr=[12,15,18],代码如下: //调用 mergexy([2006,2007,2008],[12,15,18]); // ...

  3. MySQL第五讲

    内容回顾 单表操作 """ 1.配置文件先统一设置成utf8 \s 2.无论你怎么改都没有生效 你的机器上不止一个mysql文件 C有一个 D有一个 3.百度搜索 sho ...

  4. 矩池云安装gdal五种解决方案

    1.最快最靠谱的是conda conda install gdal 命令行conda/pip search gdal查看版本,选择合适的版本,例如:conda search gdal 命令行conda ...

  5. jQuery-ajax-天气接口

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  6. JDK 线程池

    JDK 线程池 线程池参数 在JDK的4种线程池之前, 先介绍一下线程池的几个参数 corePoolSize 线程池的核心线程数量, maximumPoolSize 线程池的最大线程数量 keepAl ...

  7. 6. 堪比JMeter的.Net压测工具 - Crank 实战篇 - 收集诊断跟踪信息与如何分析瓶颈

    目录 堪比JMeter的.Net压测工具 - Crank 入门篇 堪比JMeter的.Net压测工具 - Crank 进阶篇 - 认识yml 堪比JMeter的.Net压测工具 - Crank 进阶篇 ...

  8. 『现学现忘』Docker基础 — 28、Docker容器数据卷介绍

    目录 1.什么是Docker容器数据卷 2.数据卷的作用 3.数据卷的使用 1.什么是Docker容器数据卷 Docker容器数据卷,即Docker Volume(卷). 当Docker容器运行的时候 ...

  9. 活用Windows Server 2008系统的几种安全功能

    与传统操作系统相比,Win2008系统的安全防范功能更加强大,安全防护能力自然也是高人一等,我们只要在平时善于使用该系统新增的各项安全防范功能,完全可以实现更高级别的安全保护目的.现在,本文就为大家贡 ...

  10. redis持久化 RDB与AOF

    redis持久化 RDB与AOF RDB与AOF区别 rdb: 基于快照的持久化,速度更快,一般用做备份,主从复制也是依赖于rdb持久化功能 aof:以追加的方式记录redis操作日志的文件,可以最大 ...