争将世上无期别,换得年年一度来。 —— 李商隐·七夕
又是一年七夕来,本该是与心上人幽会kfc-crazy-Thursday
的时候,奈何这世道不懂温情也就罢了,竟然还要上班!
想在小圈子里诉说一下心中的郁郁之情,没想到UI直接给我丢过来一个“碗”!!
好家伙,胸有闷气无处释放,这可是你找上门来了,那就恕在下得罪了!!
🐌:什么需求。
U:经理想要的是......做好以后放在......就行了。
🐌:那我要干嘛。
U:你就让这个蓄水池里的水得动起来,然后有数据过来就有水滴滴下来,滴到碗里就行了,下面那个底座旋转起来就行了,最好就是每一圈都交叉旋转。
🐌:能贴图么。
U:不能做吗,那我问问...
🐌:好嘞,没问题!
作为一名合格的前端开发,漂亮UI辛辛苦苦画的图,怎么用“不行”二字就草草打发呢,如此一来岂不是寒了UI的心?
言归正传,作为一名写惯了大屏的底层码农,当看到这张图,第一反应就是用echarts来搞定,这不一眼就可以拆成三部分吗,碗壁,水波和底座
只要一步步来,一块一块解决,就能搞定,也没那么...麻烦嘛,对吧~
1. 容器
既然决定用echarts来搞定这个需求了,那首先我们就需要把echarts引入进来,不论是使用npm
还是直接引入cdn
的方式,抑或是直接去下载压缩包的方法都可行,这里就不在赘述了。
要画这个碗,还得让他动起来,初步思路是将碗的三个部分单独写出来,然后通过定位的方式拼接起来。
那么第一步我们就需要给整个碗设置一个容器,我们就给他起名叫bowlWrapper
,并且给他一定的宽高,添加上绝对定位。
<div id='bowlWrapper'></div>
#bowlWrapper {
position: relative;
width: 400px;
height: 500px;
}
2. 碗壁
但凡用过echarts的同学肯定看一眼就能想出n+1个画出这个碗壁的方法,既然如此,那在下就必须用一些不常用的方法了,否则怎么能体现出在下的独到之处呢?👻
在下选择的是使用柱状图,原因是柱状图更方便把圆角做出来,然后让柱状图弯成这一道弧线就可以了。那怎么让柱状图弯起来成了一个问题,这里我想到的是使用echarts的极坐标polar
来实现。
极坐标,南极北极的极。就像经纬度一样,polar
的实现就是把你的南极点(北极点)标出来,平时用的柱状图横纵坐标就会像经纬度一样排列,这时我们把柱状图的柱子放在类似纬度的坐标轴上,他就会像一个圆一样弯起来。
说干就干,咱们的碗壁也需要一个容器,同时给容器宽高和相对定位,之后再进行移动布局
<div class="bowl">
<div class='chart' id='bowlChart'></div>
</div>
.bowl {
position: absolute;
top: 0;
left: 0;
width: 400px;
height: 400px;
transform: rotate(70deg); // 由于0到60是从最上面开始的,所以碗壁是斜的,我们需要做一定的旋转
border-radius: 50%;
overflow: hidden;
}
// 获取碗壁元素
let bowlChart = echarts.init(document.getElementById('bowlChart'));
// 设置碗壁echarts配置项
bowlChart.setOption({
polar: {
radius: ['92%', '100%'], // 柱状图最内和最外边,类似于饼图做圆环的radius
center: ['50%', '50%'], // 极坐标中心点
},
// 角度轴指示器,就像是柱状图弯曲的轨道,通过max来设置绕一整圈的数字
angleAxis: {
max: 100,
show: false, // 轨道不显示
},
// 极坐标系配置,类似于xAxis和yAxis,这里我们刻度之类的全都不要,所以设置为false
radiusAxis: {
type: 'category',
show: true,
axisLabel: false,
axisLine: false,
axisTick: false,
},
series: [
{
type: 'bar',
roundCap: true, // 开启柱状图圆角
data: [60], // 柱子长度为60,因为角度指示器的max为100,所以柱子是0.6圈
coordinateSystem: 'polar', // 坐标系使用极坐标系
// 柱子配置
itemStyle: {
color: '#041A22', // 颜色
borderWidth: 2, // 边框宽度
borderColor: '#00a757', // 边框色
shadowOffsetX: 0, // x轴阴影偏移量
shadowOffsetY: 0, // y轴阴影偏移量
shadowBlur: 8, // 阴影模糊度
shadowColor: '#01FE85', // 阴影颜色
},
},
]
})
🤩效果图:
如此便可画出我想要的这个碗壁(看着简单,其实琢磨了好久,这该死的胜负欲😌);
一些配置项的解释都在代码里,不清楚效果的可以一个个改了看看差别。
3. 水波
这个就非常的明显了,水球图嘛,写过的都知道(废话,不写过怎么知道),这个就没必要描述太多了叭,直接动手?
该说还得说,水球图就没有那么多花里胡哨,但是并不是echarts原生就配备了,我们需要装一个依赖库 echarts-liquidfill
,安装方法同echarts
,成功即可。
同样我们需要一个水波容器,并且给定宽高和相对定位。
<div class="water">
<div class='chart' id='waterChart'></div>
</div>
.water {
position: absolute;
top: 0;
left: 0;
width: 400px;
height: 400px;
}
.chart {
width: 100%;
height: 100%;
}
// 获取水波元素
let waterChart = echarts.init(document.getElementById('waterChart'));
// 设置水波echart配置项
waterChart.setOption({
series: [
{
type: 'liquidFill', // 系列类型选liquidFill,当然这个需要先安装库,否则会报错
radius: '93%', // 水球图外圈百分比
center: ['50%', '50%'], // 中心点
color: [ // 水波的颜色,包括偏移量,色阶和渐变方式
{
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: '#00a757',
opacity: 0.2
},
{
offset: 1,
color: '#012a60',
opacity: 0.2
}
],
},
],
data: [0.33, 0.28, 0.3], // 波浪数量和高度,数据数量匹配波浪数量,数值取0~1
backgroundStyle: {
color: 'transparent', // 背景色透明
},
label: false, // 关闭说明文字
outline: false, // 关闭水球图外框
},
]
})
🤩效果图:
值得一提的是水球图的radius,直接影响了水球的大小,同时需要匹配碗壁的大小,在两张echarts图容器大小相同的情况下,因为前面碗壁的内圈radius为92%,所以水球图的radius我设置为93%,有一点重叠,这样视觉上看起来更加包裹,当然92%直接贴合也可以,可能会多一条界线,看个人喜好而定。
4. 底座
重中之重来啦,就是这个底座的设计,对于echarts使用不多的同学一看就觉得好高级,其实熟知echarts仪表盘gauge
使用方法的同学就知道,这不过是一个个仪表盘一圈一圈绕出来的而已。
废话不多说,我们先来分析这个设计图
首先他是一个近大远小的echarts图,这就考验css功底了;
其次从大图中我们可以看到,他的内外其实一共有四层,有高度,同样通过css可以解决;
最后就是需要旋转,还要每一圈之间交叉旋转。
那就来吧,不论是旋转还是3d视觉效果,首先我们都需要这张echarts图。通过分析我们知道他需要四层,那么最简单的方式就是我们写四个echarts图,然后通过css来修改高度。
我们创建四个容器,分别放四个echart图,设置宽高和相对定位
<div class="base-bottom">
<div class='chart' id='baseBottomChart'></div>
</div>
<div class="base-middle">
<div class='chart' id='baseMiddleChart'></div>
</div>
<div class="base-top">
<div class='chart' id='baseTopChart'></div>
</div>
<div class="base-topmost">
<div class='chart' id='baseTopmostChart'></div>
</div>
.base-bottom,
.base-middle,
.base-top,
.base-topmost {
position: absolute;
top: 0;
left: 0;
background-color: transparent;
width: 400px;
height: 400px;
z-index: 1;
}
我们先来制作最底层,也是最大的那一层
温馨提示:代码量比较大且重复较多,不需要全部看完,有点浪费时间
说一下逻辑:
第一个逻辑是用分割线分割出一个个小方块,第二个是直接用宽的分割线来做方块
因为设计图中小方块之前是透明的
如果用第一个逻辑,那分割线透明就等于没分割,所以选第二个;
// 获取元素
let baseBottomChart = echarts.init(document.getElementById('baseBottomChart'));
// 设置配置项
baseBottomChart.setOption({
series: [
// 最外圈格子
{
type: 'gauge', // 系列为仪表盘
radius: '85%', // 外圈起始位置
clockwise: false, // 是否顺时针
startAngle: '0', // 起始角度
endAngle: '240', // 结束角度
splitNumber: 35, // 分割数量
axisTick: false,
axisLabel: false,
// 走第二个逻辑把这个线变透明
axisLine: {
show: true,
lineStyle: {
color: [
[0, 'transparent'],
[1, 'transparent'],
],
width: 10,
}
},
// 分割线设置宽度和高度
splitLine: {
show: true,
length: 15, // 这个是每一格的高度
lineStyle: {
color: '#11976d',
width: 10, // 每一格的宽度
}
},
},
// 最外圈线
{
type: 'gauge',
radius: '84%',
clockwise: false,
startAngle: '245',
endAngle: '355',
splitNumber: 0,
detail: '',
axisTick: false,
splitLine: false,
axisLabel: false,
axisLine: {
show: true,
lineStyle: {
color: [
[0, '#11976d'],
[1, '#11976d']
],
width: 10,
}
},
},
// 次外圈虚点
{
type: 'gauge',
radius: '72%',
clockwise: false,
startAngle: '0',
endAngle: '354',
splitNumber: 60,
axisTick: false,
axisLabel: false,
axisLine: {
show: true,
lineStyle: {
color: [
[0, 'transparent'],
[1, 'transparent'],
],
}
},
splitLine: {
show: true,
length: 3,
lineStyle: {
color: '#12917e',
width: 3,
}
},
},
// 中间细线
{
type: 'gauge',
radius: '60%',
clockwise: false,
startAngle: '0',
endAngle: '359.99',
splitNumber: 0,
detail: '',
axisTick: false,
splitLine: false,
axisLabel: false,
axisLine: {
show: true,
lineStyle: {
color: [
[0, '#124d3d'],
[1, '#124d3d']
],
width: 3,
}
},
},
// 中间齿轮格子
{
type: 'gauge',
radius: '54%',
clockwise: false,
startAngle: '0',
endAngle: '358',
splitNumber: 35,
axisTick: false,
axisLabel: false,
axisLine: {
show: true,
lineStyle: {
color: [
[0, 'transparent'],
[1, 'transparent'],
],
width: 10,
}
},
splitLine: {
show: true,
length: 12,
lineStyle: {
color: '#124d3d',
width: 10,
}
},
},
// 中间齿轮线
{
type: 'gauge',
radius: '49%',
clockwise: false,
startAngle: '0',
endAngle: '359.99',
splitNumber: 0,
detail: '',
axisTick: false,
splitLine: false,
axisLabel: false,
axisLine: {
show: true,
lineStyle: {
color: [
[0, '#124d3d'],
[1, '#124d3d']
],
width: 13,
}
},
},
]
})
最后我们的底层echart效果图就出来了:
然后我们用同样的方法来绘制出其他三层
温馨提示:代码量大,可以跳过,只看思路,思路在最下面喔
// 中间层
let baseMiddleChart = echarts.init(document.getElementById('baseMiddleChart'));
baseMiddleChart.setOption({
series: [
// 锅底
{
type: 'gauge',
radius: '43%',
clockwise: false,
startAngle: '0',
endAngle: '359.99', // 为什么不是360,因为360 = 0
splitNumber: 0,
detail: '',
axisTick: false,
splitLine: false,
axisLabel: false,
axisLine: {
show: true,
lineStyle: {
color: [
[0, '#11976d'],
[1, '#11976d']
],
width: 12,
}
},
},
// 锅耳
{
type: 'gauge',
radius: '50%',
clockwise: false,
startAngle: '0',
endAngle: '359.99',
splitNumber: 0,
detail: '',
axisTick: false,
splitLine: false,
axisLabel: false,
axisLine: {
show: true,
lineStyle: {
color: [
[0, 'transparent'],
[0.20, 'transparent'],
[0.21, '#11976d'],
[0.29, '#11976d'],
[0.30, 'transparent'],
[0.70, 'transparent'],
[0.71, '#11976d'],
[0.79, '#11976d'],
[0.80, 'transparent'],
[1, 'transparent']
],
width: 16,
}
},
},
]
})
// 顶层
let baseTopChart = echarts.init(document.getElementById('baseTopChart'));
baseTopChart.setOption({
series: [
// 次内圈线
{
type: 'gauge',
radius: '32%',
clockwise: false,
startAngle: '0',
endAngle: '359.99',
splitNumber: 0,
detail: '',
axisTick: false,
splitLine: false,
axisLabel: false,
axisLine: {
show: true,
lineStyle: {
color: [
[0, '#76978e'],
[1, '#76978e']
],
width: 2,
}
},
},
]
})
// 最顶层
let baseTopmostChart = echarts.init(document.getElementById('baseTopmostChart'));
baseTopmostChart.setOption({
series: [
// 最内圈格子
{
type: 'gauge',
radius: '28%',
clockwise: false,
startAngle: '0',
endAngle: '359.99',
splitNumber: 35,
axisTick: false,
axisLabel: false,
axisLine: {
show: true,
lineStyle: {
color: [
[0, 'transparent'],
[1, 'transparent'],
],
width: 10,
}
},
splitLine: {
show: true,
length: 3,
lineStyle: {
color: '#76978e',
width: 3,
}
},
},
// 最内圈线
{
type: 'gauge',
radius: '28%',
clockwise: false,
startAngle: '82',
endAngle: '195',
splitNumber: 0,
detail: '',
axisTick: false,
splitLine: false,
axisLabel: false,
axisLine: {
show: true,
lineStyle: {
color: [
[0, '#76978e'],
[1, '#76978e']
],
width: 3,
}
},
},
]
})
来一张全家福:
整体底座绘制思路
最外圈🌀:由两部分组成,三分之一为实线,其余三分之二为分割线绘成的小格子,实线的宽度略小于分割线,且外圈radius
属性也略小,差不多居中对齐,颜色为#11976d
;
次外圈🌀:由较小宽度和高度的分割线组成,颜色为#12917e
;
中圈实线🌀:宽度较小的实线构成,颜色为#124d3d
;
中圈齿轮🌀:由两部分组成,分别是359.99°的实线和359.99°的分割线格子,适当调整splitNumber
和分割线宽度
来让格子不那么密集,同时实线的最外圈radus
要小于齿轮,遵循实线宽度 + 实线radius > 分割线的高度+分割线最外圈radius > 实线宽度
的原则,颜色为#124d3d
;
中圈锅子(暂时叫他锅子吧)🌀:是由一个锅圈和两个锅耳朵组成,锅圈就是一圈实线构成,比较简单,而锅耳朵是由一圈较粗,同时颜色设置部分透明,部分不透明的实线构成,上图中锅耳朵的颜色为以下代码,要让锅耳朵更大或者更小,只要调整中间数据即可,颜色为#11976d
;
color: [
[0, 'transparent'],
[0.20, 'transparent'],
[0.21, '#11976d'],
[0.29, '#11976d'],
[0.30, 'transparent'],
[0.70, 'transparent'],
[0.71, '#11976d'],
[0.79, '#11976d'],
[0.80, 'transparent'],
[1, 'transparent']
],
内圈细线🌀:较细实线,颜色为#76978e
;
最内圈🌀:同最外圈原理,如果要实线朝不同方向,调整角度即可,这里相比与最外圈较为容易的是,因为虚线和实线同宽,所以虚线可以设置为359.99°,仅调整实线角度就行,颜色为#76978e
;
底座旋转实现
既然图已经画完了,接下来就是要让这个底座旋转起来,由于需求是想让每个环交叉旋转,也就意味着我们不能去旋转整张画布,这里我提供和践行的思路是修改每个圈的起始角度(startAngle)和结束角度(endAngle) ,如果由更好的方法欢迎补充👍🏻;
既然是修改角度,那就需要一个控制变化角度的变量,还要一个定时器来每隔一段时间修改这个变量,这个一段时间我设置的是10微秒,因为视觉暂留......🧐,好吧,在下胡说的,就是单纯觉得看起来比较丝滑;
增加度数为逆时针旋转,减去度数为顺时针旋转,代码只贴了实例,其他旋转效果以此类推;
// 逆时针旋转的度数
let leftRotSize = 0;
// 绘制底座echart的方法,因为需要频繁重绘这张echarts
function drawBottomChart() {
// 底层
let baseBottomChart = echarts.init(document.getElementById('baseBottomChart'));
baseBottomChart.setOption({
series: [
// 最外圈格子
{
// ...,
startAngle: `${0 + leftRotSize}`, // 加为逆时针旋转
endAngle: `${240 + leftRotSize}`,
// ...,
},
// ...,
// 次外圈虚点
{
startAngle: `${0 - leftRotSize * 2}`, // 减为顺时针旋转,乘2表示速度为两倍
endAngle: `${354 - leftRotSize * 2}`, // 即变为每10微秒移动0.2度,345同理
},
// ... 其他以此类推
]
});
// 中间层
let baseMiddleChart = echarts.init(document.getElementById('baseMiddleChart'));
baseMiddleChart.setOption({
// ...以上类推
});
// 顶层
let baseTopChart = echarts.init(document.getElementById('baseTopChart'));
baseTopChart.setOption({
// ...类推
});
// 最顶层
let baseTopmostChart = echarts.init(document.getElementById('baseTopmostChart'));
baseTopmostChart.setOption({
// ...推啊
});
};
// 定时器,每10微秒增加0.1度,并重绘echarts图
setInterval(() => {
drawBottomChart();
leftRotSize === 360 ? (leftRotSize = 0) : (leftRotSize += 0.1);
}, 10)
最终我们的底座终于“活”了🎉🎉🎉:
底座“躺平”(近大远小)
已经到这了,咱们的碗也接近尾声了。
我们设计图中这个底座是看起来就像“3D”效果一样躺在碗的下面,这个时候就很考验同学的css
功底了~
没错!就是css的transform
转换!!!
......
哦哦哦没说清楚,是“3D”的transform
转换,要是忘记了的同学记得趁此机会去👉**复习一下**👈3D部分。
多说无益,结果最重要:
我们给底座的容器添加3D转换的相关属性,让他沿中心点X轴旋转72°,这个72°可是经过咱们UI首肯的,起码在我这看着还可以;
.base-bottom,
.base-middle,
.base-top,
.base-topmost {
position: absolute;
top: 220px;
left: 0;
background-color: transparent;
width: 400px;
height: 400px;
z-index: 1;
transform-origin: center; // 转换原点设为中心
transform: rotateX(72deg); // 旋转72°
}
躺平后的底座:
🤔emmmm...说不上丑吧,但是......好像怎么看都不像3D的感觉,甚至有点感觉是朝下的,难道转反了?改成-72°试试?
......
其实我都试过了,没啥区别,看起来不像3D是因为它本身就是一张纸一个平面被烫平了,而根据设计图,我们需要将中间几层的高度拔高,这也是前面咱们将中间基层抽离出来单独写的主要原因。
我们把不同层的Y轴偏移量进行修改如下,记得不要忘记rotateX
,因为transform
是进行覆盖的。
.base-middle {
transform: translateY(-10px) rotateX(72deg);
}
.base-top {
transform: translateY(-15px) rotateX(72deg);
}
.base-topmost {
transform: translateY(-18px) rotateX(72deg);
}
这样看起来是不是好很多了?
5. 合体!!
好,洋洋洒洒凑了这么多字数,画了一圈又一圈,终于到我们最后合体的时候了!通过定位将我们的碗壁、水波和底座分别放到合适的位置
这里只贴css代码了,别的真的有点长,兄弟们自己调整吧
.chart {
width: 100%;
height: 100%;
}
#bowlWrapper {
position: relative;
width: 400px;
height: 500px;
margin: 200px auto 0;
}
.bowl {
position: absolute;
top: 0;
left: 0;
width: 400px;
height: 400px;
transform: rotate(70deg);
border-radius: 50%;
overflow: hidden;
z-index: 3;
}
.water {
position: absolute;
top: 0;
left: 0;
width: 400px;
height: 400px;
z-index: 2;
}
.base-bottom,
.base-middle,
.base-top,
.base-topmost {
position: absolute;
top: 220px;
left: 0;
background-color: transparent;
width: 400px;
height: 400px;
z-index: 1;
transform-origin: center;
transform: rotateX(72deg);
}
.base-middle {
transform: translateY(-10px) rotateX(72deg);
}
.base-top {
transform: translateY(-15px) rotateX(72deg);
}
.base-topmost {
transform: translateY(-18px) rotateX(72deg);
}
🎊🎊🎊大功告成!!!
🐌:你看如何?
U:嗯~好像看起来还不错呢
🐌:ok,打完收工!
U:等等,我的水滴呢
🐌:图呢
U:等着,我给你画!
🐌:SVG喔~
0. 水滴下坠
好吧,一个合格的......算了直接来吧
先说一下思路:
首先我们需要创建元素,把他设置为相对定位,然后通过设置不同的left
属性来让水滴出现在不同的位置,通过写简单css动画的方式让水滴出现到下坠再到消失;
既然要随机,要出现,要循环,也就意味着需要定时器、appendChild
和removeChild
来实现了。
talk...code...
怎么说来着?
// 雨滴svg图,这里是直接把svg图的代码扣下来了,其实直接使用svg图也是可以的
let raindropSvg = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16.442" height="24.664" viewBox="0 0 16.442 24.664"><defs><style>.a{fill:#231815;}.b{fill:url(#a);}.c{fill:#fff;}.d{fill:#006735;}.e{fill:#68ebac;}.f{filter:url(#b);}</style><linearGradient id="a" x1="0.5" x2="0.5" y2="1" gradientUnits="objectBoundingBox"><stop offset="0" stop-color="#01ff85"></stop><stop offset="0.541" stop-color="#01bf63"></stop><stop offset="1" stop-color="#012e18"></stop></linearGradient><filter id="b"><feOffset></feOffset><feGaussianBlur stdDeviation="3.5" result="c"></feGaussianBlur><feFlood flood-color="#00ff85" result="d"></feFlood><feComposite operator="out" in="SourceGraphic" in2="c"></feComposite><feComposite operator="in" in="d"></feComposite><feComposite operator="in" in2="SourceGraphic"></feComposite></filter></defs><g transform="translate(-213.9 -65.7)"><path class="a" d="M222.122,90.364a8.223,8.223,0,0,1-7.291-12.025l6.842-12.375a.514.514,0,0,1,.9,0L229.4,78.328a8.224,8.224,0,0,1-7.283,12.036Zm0-23.088-6.387,11.548a7.1,7.1,0,0,0-.807,3.318,7.194,7.194,0,1,0,14.389,0,7.08,7.08,0,0,0-.816-3.329Z" transform="translate(0 0)"></path><g data-type="innerShadowGroup"><path class="b" d="M240.307,84.3l-6.836,12.364a7.708,7.708,0,1,0,13.67,0Z" transform="translate(-18.185 -18.087)"></path><g class="f" transform="matrix(1, 0, 0, 1, 213.9, 65.7)"><path class="c" d="M240.307,84.3l-6.836,12.364a7.708,7.708,0,1,0,13.67,0Z" transform="translate(-232.08 -83.79)"></path></g></g><path class="d" d="M222.122,90.364a8.223,8.223,0,0,1-7.291-12.025l6.842-12.375a.514.514,0,0,1,.9,0L229.4,78.328a8.224,8.224,0,0,1-7.283,12.036Zm0-23.088-6.387,11.548a7.1,7.1,0,0,0-.807,3.318,7.194,7.194,0,1,0,14.389,0,7.08,7.08,0,0,0-.816-3.329Z" transform="translate(0 0)"></path><path class="e" d="M249.473,93.319,244.489,84.3l-6.836,12.364a7.714,7.714,0,0,0-.752,2.207,16.3,16.3,0,0,0,12.331-5.558" transform="translate(-22.366 -18.087)"></path><path class="d" d="M218.812,81.3a.518.518,0,0,1-.394-.182.507.507,0,0,1-.113-.422,8.116,8.116,0,0,1,.8-2.353l6.842-12.375a.514.514,0,0,1,.9,0l4.985,9.019a.512.512,0,0,1-.2.7.639.639,0,0,1-.259.063A16.9,16.9,0,0,1,218.812,81.3Zm7.586-14.02-6.387,11.548a7.1,7.1,0,0,0-.557,1.427,15.73,15.73,0,0,0,11.209-5.257Z" transform="translate(-4.276 0)"></path></g></svg>`
// 雨滴名称列表
let raindropNameList = ['小雨点', '小水珠', '小水滴', '小雨滴', '水滴'];
// 获取容器元素
let bowlWrapper = document.getElementById('bowlWrapper');
// 每秒添加一个水滴
setInterval(() => {
// 水滴容器
let raindropWrap = document.createElement('div');
raindropWrap.className = 'raindrop'; // 添加公共样式名
raindropWrap.innerHTML = raindropSvg; // 水滴svg,如果是使用svg图,就是要去修改水滴容器的背景
// 随机出现宽度,在下这里是left设置为50-300可以让水滴进到碗里,所以这样写随机数
raindropWrap.style.left = (Math.floor(Math.random() * 250) + 50) + 'px';
// 水滴文字
let raintext = document.createElement('span');
// 通过随机数自动选择雨滴名称列表的某一项
raintext.innerText = raindropNameList[Math.floor((Math.random() * raindropNameList.length))];
raintext.className = 'raindrop-text'
// 给容器添加文字
raindropWrap.appendChild(raintext);
// 把容器挂载到蓄水池容器上
bowlWrapper.appendChild(raindropWrap);
setTimeout(() => {
// 删除已经走完动画的水滴元素
bowlWrapper.removeChild(raindropWrap)
}, 5000);
}, 1000);
.raindrop {
position: absolute;
display: flex;
flex-direction: column;
align-items: center;
animation: dropRain 5s linear;
z-index: 1;
opacity: 0; // 开始状态最好设置全透明,否则可能动画走完会有一瞬间水滴在顶部停留
}
.raindrop-text {
padding-top: 5px;
font-family: "Microsoft YaHei";
font-weight: bold;
font-size: 14px;
color: #fff;
}
/* 雨滴落下动画 */
@keyframes dropRain {
0% {
top: 0;
opacity: 0;
}
5% {
opacity: 1;
}
95% {
opacity: 1;
}
100% {
top: 270px; // 最终高度尽量设置为进入碗的水波以后,看起来就像融入水里一样
opacity: 0;
}
}
这样我们的水滴就会自动下落并且自动消失啦
最终效果图(其实就是封面那张)
温馨提示:在vue中,如果是在css添加了scoped的情况下,可能会出现添加的水滴元素并不具有
.raindrop
和.raindrop-text
的样式名,因为元素是直接生成和挂载的,没有样式后面加的[data12345abcde]之类的东西,所以需要给这两个公共样式进行样式穿透👌
七夕就这么过了,甭管有没有对象,new一个就完了。
山水一程,就此别过。