前端每日实战:133# 视频演示如何用 CSS 和 GSAP 创作有多个关键帧的连续动画
效果预览
按下右侧的“点击预览”按钮可以在当前页面预览,点击链接可以全屏预览。
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 创作有多个关键帧的连续动画的更多相关文章
- 前端每日实战:134# 视频演示如何用 CSS 和 GSAP 创作一个树枝发芽的 loader
效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/LJmpXZ 可交互视频 此视频是可 ...
- 前端每日实战:144# 视频演示如何用 D3 和 GSAP 创作一个集体舞动画
效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/gdVObN 可交互视频 此视频是可 ...
- 前端每日实战:136# 视频演示如何用 D3 和 GSAP 创作一个横条 loader
效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/pOZKWJ 可交互视频 此视频是可 ...
- 前端每日实战:89# 视频演示如何用 CSS 和 D3 创作旋臂粒子动画
效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/xJrOqd 可交互视频 此视频是可 ...
- 前端每日实战:141# 视频演示如何用 CSS 的 Grid 布局创作一枚小狗邮票
效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/BOeEYV 可交互视频 此视频是可 ...
- 前端每日实战:142# 视频演示如何用 CSS 的 Grid 布局创作一枚小鸡邮票
效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/mGZbmQ 可交互视频 此视频是可 ...
- 前端每日实战:116# 视频演示如何用 CSS 和原生 JS 开发一个监控网络连接状态的页面
效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/oPjWvw 可交互视频 此视频是可 ...
- 前端每日实战:157# 视频演示如何用纯 CSS 创作一个棋盘错觉动画(实际上每一行都是平行的)
效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/VEyoGj 可交互视频 此视频是可 ...
- 前端每日实战:158# 视频演示如何用纯 CSS 创作一个雨伞 toggle 控件
效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/pxLbjv 可交互视频 此视频是可 ...
随机推荐
- windows下后台启动PHP,Nginx,Redis(使用RunHiddenConsole)
启动命令(红色代码可选): 启动PHP RunHiddenConsole D:/phpStudy/PHPTutorial/php/php-5.6.27-nts/php-cgi.exe -b 127.0 ...
- LGP6146题解
思维僵化了,习惯按照右端点排序,没想到是按照左端点排序... 考虑从左到右依次加入线段,考虑贡献. 设前 \(i\) 条线段的答案为 \(dp[i]\). 考虑两种情况: 不加,贡献为 \(dp[i- ...
- C#序列化和反序列化(json)
一,什么是Json? json是存储和交换文本信息的方法,类似xml.但是json比xml更小,更快,j更易于解析.并且json采用完全独立于语言的文本格式(即不依赖于各种编程语言),这些特性使jso ...
- Java基础——自动装箱与拆箱
装箱:把基本数据类型转换为对应的包装类类型 //装箱Integer i=Integer.ValuesOf(100);//自动装箱integer i1=100;//能够直接赋值成功的称为自动装箱,也调用 ...
- # Redhat7 安装 yum源
第一步:先卸载原来的yum rpm -qa |grep yum 查看原来是否安装 yum-3.4.3-118.el7.noarch yum-utils-1.1.31-24.el7.noarch yum ...
- 爬虫系列之Scrapy框架
一 scrapy框架简介 1 介绍 (1) 什么是Scrapy? Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架,非常出名,非常强悍.所谓的框架就是一个已经被集成了各种功能(高性能 ...
- MVC与MVVM?
model-数据层 view-视图层 controller-控制层 MVC的目的是实现M和V的分离,单向通信,必须通过C来承上启下 MVVM中通过VM(vue中的实例化对象)的发布者-订阅者模式实现双 ...
- httpRunner使用小结
1.每个系统可以给所有相关接口准备一份完整的主流程数据,这样就不用每执行一条用例就要先执行很多前置用例2.每条用例在设计之初,关于使用的前置数据,以及条件判断的数据值,以及设置的前提条件数据值,尽量保 ...
- vue集成webpack,遭遇 SyntaxError: Unknown word
这个错误根本和我的项目八竿子打不着,错误原因是配置了 css 的rule,将 这个rule注释掉,正常运行没有问题, 可是我却有强迫症,既然处理 node_modules 文件里才出现的错误,那么我就 ...
- java-第三方工具去做一些校验
推荐大家使用第三方 jar 的工具类去做判空.比如:从 Map 中取一个 key 的值,可以用 MapUtils 这个类:对字符串判空使用 StringUtils 这个类:对集合进行判空使用 Coll ...