🥣七夕多泪,UI让我画个碗

4,924 阅读16分钟

争将世上无期别,换得年年一度来。 —— 李商隐·七夕

又是一年七夕来,本该是与心上人幽会kfc-crazy-Thursday的时候,奈何这世道不懂温情也就罢了,竟然还要上班!

image.png

想在小圈子里诉说一下心中的郁郁之情,没想到UI直接给我丢过来一个“碗”!!

image.png

好家伙,胸有闷气无处释放,这可是你找上门来了,那就恕在下得罪了!!


🐌:什么需求。

U:经理想要的是......做好以后放在......就行了。

🐌:那我要干嘛。

U:你就让这个蓄水池里的水得动起来,然后有数据过来就有水滴滴下来,滴到碗里就行了,下面那个底座旋转起来就行了,最好就是每一圈都交叉旋转。

🐌:能贴图么。

U:不能做吗,那我问问...

🐌:好嘞,没问题!


作为一名合格的前端开发,漂亮UI辛辛苦苦画的图,怎么用“不行”二字就草草打发呢,如此一来岂不是寒了UI的心?

言归正传,作为一名写惯了大屏的底层码农,当看到这张图,第一反应就是用echarts来搞定,这不一眼就可以拆成三部分吗,碗壁,水波和底座

image.png

只要一步步来,一块一块解决,就能搞定,也没那么...麻烦嘛,对吧~

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); // 由于060是从最上面开始的,所以碗壁是斜的,我们需要做一定的旋转
    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', // 阴影颜色
            },
        },
    ]
})

🤩效果图:

image.png

如此便可画出我想要的这个碗壁(看着简单,其实琢磨了好久,这该死的胜负欲😌);

一些配置项的解释都在代码里,不清楚效果的可以一个个改了看看差别。

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, // 关闭水球图外框
        },
    ]
})

🤩效果图:

wavewater.gif

值得一提的是水球图的radius,直接影响了水球的大小,同时需要匹配碗壁的大小,在两张echarts图容器大小相同的情况下,因为前面碗壁的内圈radius为92%,所以水球图的radius我设置为93%,有一点重叠,这样视觉上看起来更加包裹,当然92%直接贴合也可以,可能会多一条界线,看个人喜好而定。

4. 底座

重中之重来啦,就是这个底座的设计,对于echarts使用不多的同学一看就觉得好高级,其实熟知echarts仪表盘gauge使用方法的同学就知道,这不过是一个个仪表盘一圈一圈绕出来的而已。

废话不多说,我们先来分析这个设计图

image.png

首先他是一个近大远小的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效果图就出来了:

image.png

然后我们用同样的方法来绘制出其他三层

温馨提示:代码量大,可以跳过,只看思路,思路在最下面喔

// 中间层
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,
                }
            },
        },
    ]
})

来一张全家福:

image.png


整体底座绘制思路

最外圈🌀:由两部分组成,三分之一为实线,其余三分之二为分割线绘成的小格子,实线的宽度略小于分割线,且外圈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)
                               

最终我们的底座终于“活”了🎉🎉🎉:

base.gif

底座“躺平”(近大远小)

已经到这了,咱们的碗也接近尾声了。

我们设计图中这个底座是看起来就像“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°
}

躺平后的底座:

baseDown.gif

🤔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);
}

这样看起来是不是好很多了?

3Dbase.gif

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);
}

🎊🎊🎊大功告成!!!

total.gif


🐌:你看如何?

U:嗯~好像看起来还不错呢

🐌:ok,打完收工!

U:等等,我的水滴呢

🐌:图呢

U:等着,我给你画!

🐌:SVG喔~


image.png

0. 水滴下坠

好吧,一个合格的......算了直接来吧

先说一下思路:

首先我们需要创建元素,把他设置为相对定位,然后通过设置不同的left属性来让水滴出现在不同的位置,通过写简单css动画的方式让水滴出现到下坠再到消失;

既然要随机,要出现,要循环,也就意味着需要定时器、appendChildremoveChild来实现了。

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;
    }
}

这样我们的水滴就会自动下落并且自动消失啦

最终效果图(其实就是封面那张)

waterBowl.gif

温馨提示:在vue中,如果是在css添加了scoped的情况下,可能会出现添加的水滴元素并不具有.raindrop.raindrop-text的样式名,因为元素是直接生成和挂载的,没有样式后面加的[data12345abcde]之类的东西,所以需要给这两个公共样式进行样式穿透👌


七夕就这么过了,甭管有没有对象,new一个就完了。

山水一程,就此别过。

image.png